summaryrefslogtreecommitdiff
path: root/shells/mksh
diff options
context:
space:
mode:
Diffstat (limited to 'shells/mksh')
-rw-r--r--shells/mksh/Makefile24
-rw-r--r--shells/mksh/distinfo6
-rw-r--r--shells/mksh/files/Build.sh2837
-rw-r--r--shells/mksh/files/FAQ2HTML.sh136
-rw-r--r--shells/mksh/files/check.pl1363
-rw-r--r--shells/mksh/files/check.t13910
-rw-r--r--shells/mksh/files/dot.mkshrc675
-rw-r--r--shells/mksh/files/edit.c5726
-rw-r--r--shells/mksh/files/emacsfn.h116
-rw-r--r--shells/mksh/files/eval.c2111
-rw-r--r--shells/mksh/files/exec.c1872
-rw-r--r--shells/mksh/files/expr.c1234
-rw-r--r--shells/mksh/files/exprtok.h123
-rw-r--r--shells/mksh/files/funcs.c3657
-rw-r--r--shells/mksh/files/histrap.c1630
-rw-r--r--shells/mksh/files/jehanne.c36
-rw-r--r--shells/mksh/files/jobs.c1962
-rw-r--r--shells/mksh/files/lalloc.c193
-rw-r--r--shells/mksh/files/lex.c1820
-rw-r--r--shells/mksh/files/lksh.1354
-rw-r--r--shells/mksh/files/main.c2150
-rw-r--r--shells/mksh/files/mirhash.h226
-rw-r--r--shells/mksh/files/misc.c2633
-rw-r--r--shells/mksh/files/mksh.17102
-rw-r--r--shells/mksh/files/mksh.faq620
-rw-r--r--shells/mksh/files/os2.c585
-rw-r--r--shells/mksh/files/rlimits.opt111
-rw-r--r--shells/mksh/files/sh.h2904
-rw-r--r--shells/mksh/files/sh_flags.opt194
-rw-r--r--shells/mksh/files/shf.c1322
-rw-r--r--shells/mksh/files/strlcpy.c53
-rw-r--r--shells/mksh/files/syn.c1187
-rw-r--r--shells/mksh/files/tree.c1176
-rw-r--r--shells/mksh/files/var.c2244
-rw-r--r--shells/mksh/files/var_spec.h80
-rw-r--r--shells/mksh/patches/manual-Build.sh29
-rw-r--r--shells/mksh/patches/manual-funcs.c29
37 files changed, 62413 insertions, 17 deletions
diff --git a/shells/mksh/Makefile b/shells/mksh/Makefile
index d4760535dd5..ce06512a2d8 100644
--- a/shells/mksh/Makefile
+++ b/shells/mksh/Makefile
@@ -1,21 +1,20 @@
-# $NetBSD: Makefile,v 1.39 2020/05/19 15:20:07 nia Exp $
+# $NetBSD: Makefile,v 1.40 2020/07/06 10:11:34 jperkin Exp $
DISTNAME= mksh-R59b
PKGNAME= ${DISTNAME:S/-R/-/}
CATEGORIES= shells
-MASTER_SITES= http://www.mirbsd.org/MirOS/dist/mir/mksh/
-MASTER_SITES+= http://pub.allbsd.org/MirOS/dist/mir/mksh/
-SITES.${DISTNAME}.cat1.gz= ${MASTER_SITES:S/dist/cats/}
+MASTER_SITES= # maintained locally
+DISTFILES= # empty
-EXTRACT_SUFX= .tgz
-
-MAINTAINER= ahoka@NetBSD.org
+MAINTAINER= jperkin@pkgsrc.org
HOMEPAGE= http://mirbsd.de/mksh
COMMENT= MirBSD Korn Shell
LICENSE= miros
+BOOTSTRAP_PKG= yes
+NO_CHECKSUM= yes
+
WRKSRC= ${WRKDIR}/mksh
-WRKBUILD?= ${WRKSRC}
PKG_SHELL= bin/mksh
@@ -23,18 +22,21 @@ LIBS.Interix+= -lcrypt
INSTALLATION_DIRS= bin ${PKGMANDIR}/man1 share/examples/mksh
+do-extract:
+ ${CP} -R ${FILESDIR} ${WRKSRC}
+
do-build:
- cd ${WRKBUILD} && ${SETENV} ${MAKE_ENV} LIBS=${LIBS:Q} \
+ cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} LIBS=${LIBS:Q} \
${TOOLS_SHELL} ${WRKSRC}/Build.sh -r
do-install:
- ${INSTALL_PROGRAM} ${WRKBUILD}/mksh ${DESTDIR}${PREFIX}/bin/mksh
+ ${INSTALL_PROGRAM} ${WRKSRC}/mksh ${DESTDIR}${PREFIX}/bin/mksh
${INSTALL_MAN} ${WRKSRC}/mksh.1 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1/
${INSTALL_DATA} ${WRKSRC}/dot.mkshrc ${DESTDIR}${PREFIX}/share/examples/mksh/
# Uncomment this if you want to run the regression tests
#USE_TOOLS+= perl
#do-test:
-# ${WRKBUILD}/test.sh -v
+# ${WRKSRC}/test.sh -v
.include "../../mk/bsd.pkg.mk"
diff --git a/shells/mksh/distinfo b/shells/mksh/distinfo
deleted file mode 100644
index 0bb162f8c18..00000000000
--- a/shells/mksh/distinfo
+++ /dev/null
@@ -1,6 +0,0 @@
-$NetBSD: distinfo,v 1.34 2020/05/19 15:20:07 nia Exp $
-
-SHA1 (mksh-R59b.tgz) = b3fbbcdaf5029011e3ec42ed5426a1b0567c4654
-RMD160 (mksh-R59b.tgz) = d898ba5a55f194e2c373734e4640025ea27fe936
-SHA512 (mksh-R59b.tgz) = 4ae330a79a09d2dd989116b1a836ab7f179d920eb34c97ea5da7d1434361911a93ba77ca47c5e473e5a5ce1877f2a2e919a807bb6139ec6c89c87969054d021d
-Size (mksh-R59b.tgz) = 440055 bytes
diff --git a/shells/mksh/files/Build.sh b/shells/mksh/files/Build.sh
new file mode 100644
index 00000000000..8070938e42c
--- /dev/null
+++ b/shells/mksh/files/Build.sh
@@ -0,0 +1,2837 @@
+#!/bin/sh
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.756 2020/05/16 22:53:03 tg Exp $'
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+# 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+# 2020
+# mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# People analysing the output must whitelist conftest.c for any kind
+# of compiler warning checks (mirtoconf is by design not quiet).
+#
+# Used environment documentation is at the end of this file.
+
+LC_ALL=C; LANGUAGE=C
+export LC_ALL; unset LANGUAGE
+
+case $ZSH_VERSION:$VERSION in
+:zsh*) ZSH_VERSION=2 ;;
+esac
+
+if test -n "${ZSH_VERSION+x}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+fi
+
+if test -d /usr/xpg4/bin/. >/dev/null 2>&1; then
+ # Solaris: some of the tools have weird behaviour, use portable ones
+ PATH=/usr/xpg4/bin:$PATH
+ export PATH
+fi
+
+nl='
+'
+safeIFS=' '
+safeIFS=" $safeIFS$nl"
+IFS=$safeIFS
+allu=QWERTYUIOPASDFGHJKLZXCVBNM
+alll=qwertyuiopasdfghjklzxcvbnm
+alln=0123456789
+alls=______________________________________________________________
+
+test_n() {
+ test x"$1" = x"" || return 0
+ return 1
+}
+
+test_z() {
+ test x"$1" = x""
+}
+
+case `echo a | tr '\201' X` in
+X)
+ # EBCDIC build system
+ lfcr='\n\r'
+ ;;
+*)
+ lfcr='\012\015'
+ ;;
+esac
+
+genopt_die() {
+ if test_z "$1"; then
+ echo >&2 "E: invalid input in '$srcfile': '$line'"
+ else
+ echo >&2 "E: $*"
+ echo >&2 "N: in '$srcfile': '$line'"
+ fi
+ rm -f "$bn.gen"
+ exit 1
+}
+
+genopt_soptc() {
+ optc=`echo "$line" | sed 's/^[<>]\(.\).*$/\1/'`
+ test x"$optc" = x'|' && return
+ optclo=`echo "$optc" | tr $allu $alll`
+ if test x"$optc" = x"$optclo"; then
+ islo=1
+ else
+ islo=0
+ fi
+ sym=`echo "$line" | sed 's/^[<>]/|/'`
+ o_str=$o_str$nl"<$optclo$islo$sym"
+}
+
+genopt_scond() {
+ case x$cond in
+ x)
+ cond=
+ ;;
+ x*' '*)
+ cond=`echo "$cond" | sed 's/^ //'`
+ cond="#if $cond"
+ ;;
+ x'!'*)
+ cond=`echo "$cond" | sed 's/^!//'`
+ cond="#ifndef $cond"
+ ;;
+ x*)
+ cond="#ifdef $cond"
+ ;;
+ esac
+}
+
+do_genopt() {
+ srcfile=$1
+ test -f "$srcfile" || genopt_die Source file \$srcfile not set.
+ bn=`basename "$srcfile" | sed 's/.opt$//'`
+ o_hdr='/* +++ GENERATED FILE +++ DO NOT EDIT +++ */'
+ o_gen=
+ o_str=
+ o_sym=
+ ddefs=
+ state=0
+ exec <"$srcfile"
+ IFS=
+ while IFS= read line; do
+ IFS=$safeIFS
+ case $state:$line in
+ 2:'|'*)
+ # end of input
+ o_sym=`echo "$line" | sed 's/^.//'`
+ o_gen=$o_gen$nl"#undef F0"
+ o_gen=$o_gen$nl"#undef FN"
+ o_gen=$o_gen$ddefs
+ state=3
+ ;;
+ 1:@@)
+ # start of data block
+ o_gen=$o_gen$nl"#endif"
+ o_gen=$o_gen$nl"#ifndef F0"
+ o_gen=$o_gen$nl"#define F0 FN"
+ o_gen=$o_gen$nl"#endif"
+ state=2
+ ;;
+ *:@@*)
+ genopt_die ;;
+ 0:/\*-|0:\ \**|0:)
+ o_hdr=$o_hdr$nl$line
+ ;;
+ 0:@*|1:@*)
+ # start of a definition block
+ sym=`echo "$line" | sed 's/^@//'`
+ if test $state = 0; then
+ o_gen=$o_gen$nl"#if defined($sym)"
+ else
+ o_gen=$o_gen$nl"#elif defined($sym)"
+ fi
+ ddefs="$ddefs$nl#undef $sym"
+ state=1
+ ;;
+ 0:*|3:*)
+ genopt_die ;;
+ 1:*)
+ # definition line
+ o_gen=$o_gen$nl$line
+ ;;
+ 2:'<'*'|'*)
+ genopt_soptc
+ ;;
+ 2:'>'*'|'*)
+ genopt_soptc
+ cond=`echo "$line" | sed 's/^[^|]*|//'`
+ genopt_scond
+ case $optc in
+ '|') optc=0 ;;
+ *) optc=\'$optc\' ;;
+ esac
+ IFS= read line || genopt_die Unexpected EOF
+ IFS=$safeIFS
+ test_z "$cond" || o_gen=$o_gen$nl"$cond"
+ o_gen=$o_gen$nl"$line, $optc)"
+ test_z "$cond" || o_gen=$o_gen$nl"#endif"
+ ;;
+ esac
+ done
+ case $state:$o_sym in
+ 3:) genopt_die Expected optc sym at EOF ;;
+ 3:*) ;;
+ *) genopt_die Missing EOF marker ;;
+ esac
+ echo "$o_str" | sort | while IFS='|' read x opts cond; do
+ IFS=$safeIFS
+ test_n "$x" || continue
+ genopt_scond
+ test_z "$cond" || echo "$cond"
+ echo "\"$opts\""
+ test_z "$cond" || echo "#endif"
+ done | {
+ echo "$o_hdr"
+ echo "#ifndef $o_sym$o_gen"
+ echo "#else"
+ cat
+ echo "#undef $o_sym"
+ echo "#endif"
+ } >"$bn.gen"
+ IFS=$safeIFS
+ return 0
+}
+
+if test x"$BUILDSH_RUN_GENOPT" = x"1"; then
+ set x -G "$srcfile"
+ shift
+fi
+if test x"$1" = x"-G"; then
+ do_genopt "$2"
+ exit $?
+fi
+
+echo "For the build logs, demonstrate that /dev/null and /dev/tty exist:"
+ls -l /dev/null /dev/tty
+
+v() {
+ $e "$*"
+ eval "$@"
+}
+
+vv() {
+ _c=$1
+ shift
+ $e "\$ $*" 2>&1
+ eval "$@" >vv.out 2>&1
+ sed "s^${_c} " <vv.out
+}
+
+vq() {
+ eval "$@"
+}
+
+rmf() {
+ for _f in "$@"; do
+ case $_f in
+ *.1|*.faq|*.ico) ;;
+ *) rm -f "$_f" ;;
+ esac
+ done
+}
+
+tcfn=no
+bi=
+ui=
+ao=
+fx=
+me=`basename "$0"`
+orig_CFLAGS=$CFLAGS
+phase=x
+oldish_ed=stdout-ed,no-stderr-ed
+
+if test -t 1; then
+ bi=''
+ ui=''
+ ao=''
+fi
+
+upper() {
+ echo :"$@" | sed 's/^://' | tr $alll $allu
+}
+
+# clean up after ac_testrun()
+ac_testdone() {
+ eval HAVE_$fu=$fv
+ fr=no
+ test 0 = $fv || fr=yes
+ $e "$bi==> $fd...$ao $ui$fr$ao$fx"
+ fx=
+}
+
+# ac_cache label: sets f, fu, fv?=0
+ac_cache() {
+ f=$1
+ fu=`upper $f`
+ eval fv=\$HAVE_$fu
+ case $fv in
+ 0|1)
+ fx=' (cached)'
+ return 0
+ ;;
+ esac
+ fv=0
+ return 1
+}
+
+# ac_testinit label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+# returns 1 if value was cached/implied, 0 otherwise: call ac_testdone
+ac_testinit() {
+ if ac_cache $1; then
+ test x"$2" = x"!" && shift
+ test x"$2" = x"" || shift
+ fd=${3-$f}
+ ac_testdone
+ return 1
+ fi
+ fc=0
+ if test x"$2" = x""; then
+ ft=1
+ else
+ if test x"$2" = x"!"; then
+ fc=1
+ shift
+ fi
+ eval ft=\$HAVE_`upper $2`
+ shift
+ fi
+ fd=${3-$f}
+ if test $fc = "$ft"; then
+ fv=$2
+ fx=' (implied)'
+ ac_testdone
+ return 1
+ fi
+ $e ... $fd
+ return 0
+}
+
+# pipe .c | ac_test[n] [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+ac_testnnd() {
+ if test x"$1" = x"!"; then
+ fr=1
+ shift
+ else
+ fr=0
+ fi
+ ac_testinit "$@" || return 1
+ cat >conftest.c
+ vv ']' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN conftest.c $LIBS $ccpr"
+ test $tcfn = no && test -f a.out && tcfn=a.out
+ test $tcfn = no && test -f a.exe && tcfn=a.exe
+ test $tcfn = no && test -f conftest.exe && tcfn=conftest.exe
+ test $tcfn = no && test -f conftest && tcfn=conftest
+ if test -f $tcfn; then
+ test 1 = $fr || fv=1
+ else
+ test 0 = $fr || fv=1
+ fi
+ vscan=
+ if test $phase = u; then
+ test $ct = gcc && vscan='unrecogni[sz]ed'
+ test $ct = hpcc && vscan='unsupported'
+ test $ct = pcc && vscan='unsupported'
+ test $ct = sunpro && vscan='-e ignored -e turned.off'
+ fi
+ test_n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr
+ return 0
+}
+ac_testn() {
+ ac_testnnd "$@" || return
+ rmf conftest.c conftest.o ${tcfn}* vv.out
+ ac_testdone
+}
+
+# ac_ifcpp cppexpr [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+ac_ifcpp() {
+ expr=$1; shift
+ ac_testn "$@" <<-EOF
+ #include <unistd.h>
+ extern int thiswillneverbedefinedIhope(void);
+ int main(void) { return (isatty(0) +
+ #$expr
+ 0
+ #else
+ /* force a failure: expr is false */
+ thiswillneverbedefinedIhope()
+ #endif
+ ); }
+EOF
+ test x"$1" = x"!" && shift
+ f=$1
+ fu=`upper $f`
+ eval fv=\$HAVE_$fu
+ 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
+ add_cppflags -DHAVE_$fu=$fv
+}
+
+ac_test() {
+ ac_testn "$@"
+ ac_cppflags
+}
+
+# ac_flags [-] add varname cflags [text] [ldflags]
+ac_flags() {
+ if test x"$1" = x"-"; then
+ shift
+ hf=1
+ else
+ hf=0
+ fi
+ fa=$1
+ vn=$2
+ f=$3
+ ft=$4
+ fl=$5
+ test x"$ft" = x"" && ft="if $f can be used"
+ save_CFLAGS=$CFLAGS
+ CFLAGS="$CFLAGS $f"
+ save_LDFLAGS=$LDFLAGS
+ test_z "$fl" || LDFLAGS="$LDFLAGS $fl"
+ if test 1 = $hf; then
+ ac_testn can_$vn '' "$ft"
+ else
+ ac_testn can_$vn '' "$ft" <<-'EOF'
+ /* evil apo'stroph in comment test */
+ #include <unistd.h>
+ int main(void) { return (isatty(0)); }
+ EOF
+ #'
+ fi
+ eval fv=\$HAVE_CAN_`upper $vn`
+ test_z "$fl" || test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS
+ test 11 = $fa$fv || CFLAGS=$save_CFLAGS
+}
+
+# ac_header [!] header [prereq ...]
+ac_header() {
+ if test x"$1" = x"!"; then
+ na=1
+ shift
+ else
+ na=0
+ fi
+ hf=$1; shift
+ hv=`echo "$hf" | tr -d "$lfcr" | tr -c $alll$allu$alln $alls`
+ echo "/* NeXTstep bug workaround */" >x
+ for i
+ do
+ case $i in
+ _time)
+ echo '#if HAVE_BOTH_TIME_H' >>x
+ echo '#include <sys/time.h>' >>x
+ echo '#include <time.h>' >>x
+ echo '#elif HAVE_SYS_TIME_H' >>x
+ echo '#include <sys/time.h>' >>x
+ echo '#elif HAVE_TIME_H' >>x
+ echo '#include <time.h>' >>x
+ echo '#endif' >>x
+ ;;
+ *)
+ echo "#include <$i>" >>x
+ ;;
+ esac
+ done
+ echo "#include <$hf>" >>x
+ echo '#include <unistd.h>' >>x
+ echo 'int main(void) { return (isatty(0)); }' >>x
+ ac_testn "$hv" "" "<$hf>" <x
+ rmf x
+ test 1 = $na || ac_cppflags
+}
+
+addsrcs() {
+ if test x"$1" = x"!"; then
+ fr=0
+ shift
+ else
+ fr=1
+ fi
+ eval i=\$$1
+ test $fr = "$i" && case " $SRCS " in
+ *\ $2\ *) ;;
+ *) SRCS="$SRCS $2" ;;
+ esac
+}
+
+
+curdir=`pwd` srcdir=`dirname "$0" 2>/dev/null`
+case x$srcdir in
+x)
+ srcdir=.
+ ;;
+*\ *|*" "*|*"$nl"*)
+ echo >&2 Source directory should not contain space or tab or newline.
+ echo >&2 Errors may occur.
+ ;;
+*"'"*)
+ echo Source directory must not contain single quotes.
+ exit 1
+ ;;
+esac
+dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\([^"]*\)".*$/\1/p' "$srcdir/sh.h"`
+add_cppflags -DMKSH_BUILDSH
+
+e=echo
+r=0
+eq=0
+pm=0
+cm=normal
+optflags=-std-compile-opts
+check_categories=
+last=
+tfn=
+legacy=0
+textmode=0
+ebcdic=false
+
+for i
+do
+ case $last:$i in
+ c:dragonegg|c:llvm)
+ cm=$i
+ last=
+ ;;
+ c:*)
+ echo "$me: Unknown option -c '$i'!" >&2
+ exit 1
+ ;;
+ o:*)
+ optflags=$i
+ last=
+ ;;
+ :-c)
+ last=c
+ ;;
+ :-E)
+ ebcdic=true
+ ;;
+ :-G)
+ echo "$me: Do not call me with '-G'!" >&2
+ exit 1
+ ;;
+ :-g)
+ # checker, debug, valgrind build
+ add_cppflags -DDEBUG
+ CFLAGS="$CFLAGS -g3 -fno-builtin"
+ ;;
+ :-j)
+ pm=1
+ ;;
+ :-L)
+ legacy=1
+ ;;
+ :+L)
+ legacy=0
+ ;;
+ :-M)
+ cm=makefile
+ ;;
+ :-O)
+ optflags=-std-compile-opts
+ ;;
+ :-o)
+ last=o
+ ;;
+ :-Q)
+ eq=1
+ ;;
+ :-r)
+ r=1
+ ;;
+ :-T)
+ textmode=1
+ ;;
+ :+T)
+ textmode=0
+ ;;
+ :-v)
+ echo "Build.sh $srcversion"
+ echo "for mksh $dstversion"
+ exit 0
+ ;;
+ :*)
+ echo "$me: Unknown option '$i'!" >&2
+ exit 1
+ ;;
+ *)
+ echo "$me: Unknown option -'$last' '$i'!" >&2
+ exit 1
+ ;;
+ esac
+done
+if test_n "$last"; then
+ echo "$me: Option -'$last' not followed by argument!" >&2
+ exit 1
+fi
+
+test_n "$tfn" || if test $legacy = 0; then
+ tfn=mksh
+else
+ tfn=lksh
+fi
+if test -d $tfn || test -d $tfn.exe; then
+ echo "$me: Error: ./$tfn is a directory!" >&2
+ exit 1
+fi
+rmf a.exe* a.out* conftest.c conftest.exe* *core core.* ${tfn}* *.bc *.dbg \
+ *.ll *.o *.gen *.cat1 Rebuild.sh lft no signames.inc test.sh x vv.out *.htm
+
+SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
+SRCS="$SRCS lex.c main.c misc.c shf.c syn.c tree.c var.c"
+
+if test $legacy = 0; then
+ check_categories="$check_categories shell:legacy-no int:32"
+else
+ check_categories="$check_categories shell:legacy-yes"
+ add_cppflags -DMKSH_LEGACY_MODE
+fi
+
+if $ebcdic; then
+ add_cppflags -DMKSH_EBCDIC
+fi
+
+if test $textmode = 0; then
+ check_categories="$check_categories shell:textmode-no shell:binmode-yes"
+else
+ check_categories="$check_categories shell:textmode-yes shell:binmode-no"
+ add_cppflags -DMKSH_WITH_TEXTMODE
+fi
+
+if test x"$srcdir" = x"."; then
+ CPPFLAGS="-I. $CPPFLAGS"
+else
+ CPPFLAGS="-I. -I'$srcdir' $CPPFLAGS"
+fi
+test_z "$LDSTATIC" || if test_z "$LDFLAGS"; then
+ LDFLAGS=$LDSTATIC
+else
+ LDFLAGS="$LDFLAGS $LDSTATIC"
+fi
+
+if test_z "$TARGET_OS"; then
+ x=`uname -s 2>/dev/null || uname`
+ test x"$x" = x"`uname -n 2>/dev/null`" || TARGET_OS=$x
+fi
+if test_z "$TARGET_OS"; then
+ 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 case $_f in *.1|*.faq|*.ico) ;; *) rm -f "$_f" ;; esac; done'
+
+# Evil hack
+if test x"$TARGET_OS" = x"Android"; then
+ check_categories="$check_categories android"
+ TARGET_OS=Linux
+fi
+
+# Evil OS
+if test x"$TARGET_OS" = x"Minix"; then
+ echo >&2 "
+WARNING: additional checks before running Build.sh required!
+You can avoid these by calling Build.sh correctly, see below.
+"
+ cat >conftest.c <<'EOF'
+#include <sys/types.h>
+const char *
+#ifdef _NETBSD_SOURCE
+ct="Ninix3"
+#else
+ct="Minix3"
+#endif
+;
+EOF
+ ct=unknown
+ vv ']' "${CC-cc} -E $CFLAGS $CPPFLAGS $NOWARN conftest.c | grep ct= | tr -d \\\\015 >x"
+ sed 's/^/[ /' x
+ eval `cat x`
+ rmf x vv.out
+ case $ct in
+ Minix3|Ninix3)
+ echo >&2 "
+Warning: you set TARGET_OS to $TARGET_OS but that is ambiguous.
+Please set it to either Minix3 or Ninix3, whereas the latter is
+all versions of Minix with even partial NetBSD(R) userland. The
+value determined from your compiler for the current compilation
+(which may be wrong) is: $ct
+"
+ TARGET_OS=$ct
+ ;;
+ *)
+ echo >&2 "
+Warning: you set TARGET_OS to $TARGET_OS but that is ambiguous.
+Please set it to either Minix3 or Ninix3, whereas the latter is
+all versions of Minix with even partial NetBSD(R) userland. The
+proper value couldn't be determined, continue at your own risk.
+"
+ ;;
+ esac
+fi
+
+# Configuration depending on OS revision, on OSes that need them
+case $TARGET_OS in
+NEXTSTEP)
+ test_n "$TARGET_OSREV" || TARGET_OSREV=`hostinfo 2>&1 | \
+ grep 'NeXT Mach [0-9][0-9.]*:' | \
+ sed 's/^.*NeXT Mach \([0-9][0-9.]*\):.*$/\1/'`
+ ;;
+QNX|SCO_SV)
+ test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
+ ;;
+esac
+
+# Configuration depending on OS name
+case $TARGET_OS in
+386BSD)
+ : "${HAVE_CAN_OTWO=0}"
+ add_cppflags -DMKSH_NO_SIGSETJMP
+ add_cppflags -DMKSH_TYPEDEF_SIG_ATOMIC_T=int
+ ;;
+AIX)
+ add_cppflags -D_ALL_SOURCE
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+BeOS)
+ case $KSH_VERSION in
+ *MIRBSD\ KSH*)
+ oswarn="; it has minor issues"
+ ;;
+ *)
+ oswarn="; you must recompile mksh with"
+ oswarn="$oswarn${nl}itself in a second stage"
+ ;;
+ esac
+ # BeOS has no real tty either
+ add_cppflags -DMKSH_UNEMPLOYED
+ add_cppflags -DMKSH_DISABLE_TTY_WARNING
+ # BeOS doesn't have different UIDs and GIDs
+ add_cppflags -DMKSH__NO_SETEUGID
+ ;;
+BSD/OS)
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+Coherent)
+ oswarn="; it has major issues"
+ add_cppflags -DMKSH__NO_SYMLINK
+ check_categories="$check_categories nosymlink"
+ add_cppflags -DMKSH__NO_SETEUGID
+ add_cppflags -DMKSH_DISABLE_TTY_WARNING
+ ;;
+CYGWIN*)
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+Darwin)
+ add_cppflags -D_DARWIN_C_SOURCE
+ ;;
+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
+ add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
+ # define MKSH__NO_PATH_MAX to use Hurd-only functions
+ add_cppflags -DMKSH__NO_PATH_MAX
+ ;;
+GNU/kFreeBSD)
+ case $CC in
+ *tendracc*) ;;
+ *) add_cppflags -D_GNU_SOURCE ;;
+ esac
+ add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
+ ;;
+Haiku)
+ add_cppflags -DMKSH_ASSUME_UTF8
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=0
+ ;;
+Harvey)
+ add_cppflags -D_POSIX_SOURCE
+ add_cppflags -D_LIMITS_EXTENSION
+ add_cppflags -D_BSD_EXTENSION
+ add_cppflags -D_SUSV2_SOURCE
+ add_cppflags -D_GNU_SOURCE
+ add_cppflags -DMKSH_ASSUME_UTF8
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=0
+ add_cppflags -DMKSH__NO_SYMLINK
+ check_categories="$check_categories nosymlink"
+ add_cppflags -DMKSH_NO_CMDLINE_EDITING
+ add_cppflags -DMKSH__NO_SETEUGID
+ oswarn=' and will currently not work'
+ add_cppflags -DMKSH_UNEMPLOYED
+ add_cppflags -DMKSH_NOPROSPECTOFWORK
+ # these taken from Harvey-OS github and need re-checking
+ add_cppflags -D_setjmp=setjmp -D_longjmp=longjmp
+ : "${HAVE_CAN_NO_EH_FRAME=0}"
+ : "${HAVE_CAN_FNOSTRICTALIASING=0}"
+ : "${HAVE_CAN_FSTACKPROTECTORSTRONG=0}"
+ ;;
+HP-UX)
+ ;;
+Interix)
+ ccpc='-X '
+ ccpl='-Y '
+ add_cppflags -D_ALL_SOURCE
+ : "${LIBS=-lcrypt}"
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+IRIX*)
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+Jehanne)
+ add_cppflags -DMKSH_ASSUME_UTF8
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=0
+ add_cppflags -DMKSH__NO_SYMLINK
+ check_categories="$check_categories nosymlink"
+ add_cppflags -DMKSH_NO_CMDLINE_EDITING
+ add_cppflags -DMKSH_DISABLE_REVOKE_WARNING
+ add_cppflags '-D_PATH_DEFPATH=\"/cmd\"'
+ add_cppflags '-DMKSH_DEFAULT_EXECSHELL=\"/cmd/mksh\"'
+ add_cppflags '-DMKSH_DEFAULT_PROFILEDIR=\"/cfg/mksh\"'
+ add_cppflags '-DMKSH_ENVDIR=\"/env\"'
+ SRCS="$SRCS jehanne.c"
+ ;;
+Linux)
+ case $CC in
+ *tendracc*) ;;
+ *) add_cppflags -D_GNU_SOURCE ;;
+ esac
+ add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
+ : "${HAVE_REVOKE=0}"
+ ;;
+LynxOS)
+ oswarn="; it has minor issues"
+ ;;
+midipix)
+ add_cppflags -D_GNU_SOURCE
+ # their Perl (currently…) identifies as os:linux âą
+ check_categories="$check_categories os:midipix"
+ ;;
+MidnightBSD)
+ ;;
+Minix-vmd)
+ add_cppflags -DMKSH__NO_SETEUGID
+ add_cppflags -DMKSH_UNEMPLOYED
+ add_cppflags -D_MINIX_SOURCE
+ oldish_ed=no-stderr-ed # no /bin/ed, maybe see below
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+Minix3)
+ add_cppflags -DMKSH_UNEMPLOYED
+ 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_*)
+ add_cppflags -DMKSH_ASSUME_UTF8=0
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=1
+ # almost same as CYGWIN* (from RT|Chatzilla)
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ # broken on this OE (from ir0nh34d)
+ : "${HAVE_STDINT_H=0}"
+ ;;
+NetBSD)
+ ;;
+NEXTSTEP)
+ add_cppflags -D_NEXT_SOURCE
+ add_cppflags -D_POSIX_SOURCE
+ : "${AWK=gawk}"
+ : "${CC=cc -posix}"
+ add_cppflags -DMKSH_NO_SIGSETJMP
+ # NeXTstep cannot get a controlling tty
+ add_cppflags -DMKSH_UNEMPLOYED
+ case $TARGET_OSREV in
+ 4.2*)
+ # OpenStep 4.2 is broken by default
+ oswarn="; it needs libposix.a"
+ ;;
+ esac
+ ;;
+Ninix3)
+ # similar to Minix3
+ add_cppflags -DMKSH_UNEMPLOYED
+ add_cppflags -DMKSH_NO_LIMITS
+ # but no idea what else could be needed
+ oswarn="; it has unknown issues"
+ ;;
+OpenBSD)
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+OS/2)
+ add_cppflags -DMKSH_ASSUME_UTF8=0
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=1
+ HAVE_TERMIOS_H=0
+ HAVE_MKNOD=0 # setmode() incompatible
+ oswarn="; it is being ported"
+ check_categories="$check_categories nosymlink"
+ : "${CC=gcc}"
+ : "${SIZE=: size}"
+ SRCS="$SRCS os2.c"
+ add_cppflags -DMKSH_UNEMPLOYED
+ add_cppflags -DMKSH_NOPROSPECTOFWORK
+ add_cppflags -DMKSH_NO_LIMITS
+ add_cppflags -DMKSH_DOSPATH
+ if test $textmode = 0; then
+ x='dis'
+ y='standard OS/2 tools'
+ else
+ x='en'
+ y='standard Unix mksh and other tools'
+ fi
+ echo >&2 "
+OS/2 Note: mksh can be built with or without 'textmode'.
+Without 'textmode' it will behave like a standard Unix utility,
+compatible to mksh on all other platforms, using only ASCII LF
+(0x0A) as line ending character. This is supported by the mksh
+upstream developer.
+With 'textmode', mksh will be modified to behave more like other
+OS/2 utilities, supporting ASCII CR+LF (0x0D 0x0A) as line ending
+at the cost of deviation from standard mksh. This is supported by
+the mksh-os2 porter.
+
+] You are currently compiling with textmode ${x}abled, introducing
+] incompatibilities with $y.
+"
+ ;;
+OS/390)
+ add_cppflags -DMKSH_ASSUME_UTF8=0
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=1
+ : "${CC=xlc}"
+ : "${SIZE=: size}"
+ add_cppflags -DMKSH_FOR_Z_OS
+ add_cppflags -D_ALL_SOURCE
+ oswarn='; EBCDIC support is incomplete'
+ ;;
+OSF1)
+ HAVE_SIG_T=0 # incompatible
+ 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)
+ 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
+ HAVE_ISSET_MKSH_ASSUME_UTF8=1
+ HAVE_ISOFF_MKSH_ASSUME_UTF8=0
+ add_cppflags -DMKSH__NO_SYMLINK
+ check_categories="$check_categories nosymlink"
+ add_cppflags -DMKSH_NO_CMDLINE_EDITING
+ add_cppflags -DMKSH__NO_SETEUGID
+ oswarn=' and will currently not work'
+ add_cppflags -DMKSH_UNEMPLOYED
+ # this is for detecting kencc
+ add_cppflags -DMKSH_MAYBE_KENCC
+ ;;
+PW32*)
+ HAVE_SIG_T=0 # incompatible
+ oswarn=' and will currently not work'
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+QNX)
+ add_cppflags -D__NO_EXT_QNX
+ add_cppflags -D__EXT_UNIX_MISC
+ case $TARGET_OSREV in
+ [012345].*|6.[0123].*|6.4.[01])
+ oldish_ed=no-stderr-ed # oldish /bin/ed is broken
+ ;;
+ esac
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+SCO_SV)
+ case $TARGET_OSREV in
+ 3.2*)
+ # SCO OpenServer 5
+ add_cppflags -DMKSH_UNEMPLOYED
+ ;;
+ 5*)
+ # SCO OpenServer 6
+ ;;
+ *)
+ oswarn='; this is an unknown version of'
+ oswarn="$oswarn$nl$TARGET_OS ${TARGET_OSREV}, please tell me what to do"
+ ;;
+ esac
+ : "${HAVE_SYS_SIGLIST=0}${HAVE__SYS_SIGLIST=0}"
+ ;;
+skyos)
+ oswarn="; it has minor issues"
+ ;;
+SunOS)
+ add_cppflags -D_BSD_SOURCE
+ add_cppflags -D__EXTENSIONS__
+ ;;
+syllable)
+ add_cppflags -D_GNU_SOURCE
+ add_cppflags -DMKSH_NO_SIGSUSPEND
+ oswarn=' and will currently not work'
+ ;;
+ULTRIX)
+ : "${CC=cc -YPOSIX}"
+ add_cppflags -DMKSH_TYPEDEF_SSIZE_T=int
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+UnixWare|UNIX_SV)
+ # SCO UnixWare
+ : "${HAVE_SYS_SIGLIST=0}${HAVE__SYS_SIGLIST=0}"
+ ;;
+UWIN*)
+ ccpc='-Yc,'
+ ccpl='-Yl,'
+ tsts=" 3<>/dev/tty"
+ oswarn="; it will compile, but the target"
+ oswarn="$oswarn${nl}platform itself is very flakey/unreliable"
+ : "${HAVE_SETLOCALE_CTYPE=0}"
+ ;;
+_svr4)
+ # generic target for SVR4 Unix with uname -s = uname -n
+ # this duplicates the * target below
+ oswarn='; it may or may not work'
+ test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
+ ;;
+*)
+ oswarn='; it may or may not work'
+ test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
+ ;;
+esac
+
+: "${HAVE_MKNOD=0}"
+
+: "${AWK=awk}${CC=cc}${NROFF=nroff}${SIZE=size}"
+test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \
+ echo | $NROFF -c >/dev/null 2>&1 && NROFF="$NROFF -c"
+
+# 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 '|' "sw_vers >&2"
+ vv '|' "system_profiler -detailLevel mini SPSoftwareDataType SPHardwareDataType >&2"
+ vv '|' "/bin/sh --version >&2"
+ vv '|' "xcodebuild -version >&2"
+ vv '|' "uname -a >&2"
+ vv '|' "sysctl kern.version hw.machine hw.model hw.memsize hw.availcpu hw.ncpu hw.cpufrequency hw.byteorder hw.cpu64bit_capable >&2"
+ vv '|' "sysctl hw.cpufrequency hw.byteorder hw.cpu64bit_capable hw.ncpu >&2"
+ ;;
+IRIX*)
+ vv '|' "uname -a >&2"
+ vv '|' "hinv -v >&2"
+ ;;
+OSF1)
+ vv '|' "uname -a >&2"
+ vv '|' "/usr/sbin/sizer -v >&2"
+ ;;
+SCO_SV|UnixWare|UNIX_SV)
+ vv '|' "uname -a >&2"
+ vv '|' "uname -X >&2"
+ ;;
+*)
+ vv '|' "uname -a >&2"
+ ;;
+esac
+test_z "$oswarn" || echo >&2 "
+Warning: mksh has not yet been ported to or tested on your
+operating system '$TARGET_OS'$oswarn. If you can provide
+a shell account to the developer, this may improve; please
+drop us a success or failure notice or even send in diffs,
+at the very least, complete logs (Build.sh + test.sh) will help.
+"
+$e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao on $TARGET_OS ${TARGET_OSREV}..."
+
+#
+# Start of mirtoconf checks
+#
+$e $bi$me: Scanning for functions... please ignore any errors.$ao
+
+#
+# Compiler: which one?
+#
+# notes:
+# - ICC defines __GNUC__ too
+# - GCC defines __hpux too
+# - LLVM+clang defines __GNUC__ too
+# - nwcc defines __GNUC__ too
+CPP="$CC -E"
+$e ... which compiler type seems to be used
+cat >conftest.c <<'EOF'
+const char *
+#if defined(__ICC) || defined(__INTEL_COMPILER)
+ct="icc"
+#elif defined(__xlC__) || defined(__IBMC__)
+ct="xlc"
+#elif defined(__SUNPRO_C)
+ct="sunpro"
+#elif defined(__neatcc__)
+ct="neatcc"
+#elif defined(__lacc__)
+ct="lacc"
+#elif defined(__ACK__)
+ct="ack"
+#elif defined(__BORLANDC__)
+ct="bcc"
+#elif defined(__WATCOMC__)
+ct="watcom"
+#elif defined(__MWERKS__)
+ct="metrowerks"
+#elif defined(__HP_cc)
+ct="hpcc"
+#elif defined(__DECC) || (defined(__osf__) && !defined(__GNUC__))
+ct="dec"
+#elif defined(__PGI)
+ct="pgi"
+#elif defined(__DMC__)
+ct="dmc"
+#elif defined(_MSC_VER)
+ct="msc"
+#elif defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__)
+ct="adsp"
+#elif defined(__IAR_SYSTEMS_ICC__)
+ct="iar"
+#elif defined(SDCC)
+ct="sdcc"
+#elif defined(__PCC__)
+ct="pcc"
+#elif defined(__TenDRA__)
+ct="tendra"
+#elif defined(__TINYC__)
+ct="tcc"
+#elif defined(__llvm__) && defined(__clang__)
+ct="clang"
+#elif defined(__NWCC__)
+ct="nwcc"
+#elif defined(__GNUC__)
+ct="gcc"
+#elif defined(_COMPILER_VERSION)
+ct="mipspro"
+#elif defined(__sgi)
+ct="mipspro"
+#elif defined(__hpux) || defined(__hpua)
+ct="hpcc"
+#elif defined(__ultrix)
+ct="ucode"
+#elif defined(__USLC__)
+ct="uslc"
+#elif defined(__LCC__)
+ct="lcc"
+#elif defined(MKSH_MAYBE_KENCC)
+/* and none of the above matches */
+ct="kencc"
+#else
+ct="unknown"
+#endif
+;
+const char *
+#if defined(__KLIBC__) && !defined(__OS2__)
+et="klibc"
+#else
+et="unknown"
+#endif
+;
+EOF
+ct=untested
+et=untested
+vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c | \
+ sed -n '/^ *[ce]t *= */s/^ *\([ce]t\) *= */\1=/p' | tr -d \\\\015 >x"
+sed 's/^/[ /' x
+eval `cat x`
+rmf x vv.out
+cat >conftest.c <<'EOF'
+#include <unistd.h>
+int main(void) { return (isatty(0)); }
+EOF
+case $ct in
+ack)
+ # work around "the famous ACK const bug"
+ CPPFLAGS="-Dconst= $CPPFLAGS"
+ ;;
+adsp)
+ echo >&2 'Warning: Analog Devices C++ compiler for Blackfin, TigerSHARC
+ and SHARC (21000) DSPs detected. This compiler has not yet
+ been tested for compatibility with mksh. Continue at your
+ own risk, please report success/failure to the developers.'
+ ;;
+bcc)
+ echo >&2 "Warning: Borland C++ Builder detected. This compiler might
+ produce broken executables. Continue at your own risk,
+ please report success/failure to the developers."
+ ;;
+clang)
+ # does not work with current "ccc" compiler driver
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+ # one of these two works, for now
+ vv '|' "${CLANG-clang} -version"
+ vv '|' "${CLANG-clang} --version"
+ # ensure compiler and linker are in sync unless overridden
+ case $CCC_CC:$CCC_LD in
+ :*) ;;
+ *:) CCC_LD=$CCC_CC; export CCC_LD ;;
+ esac
+ : "${HAVE_STRING_POOLING=i1}"
+ ;;
+dec)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS"
+ ;;
+dmc)
+ echo >&2 "Warning: Digital Mars Compiler detected. When running under"
+ echo >&2 " UWIN, mksh tends to be unstable due to the limitations"
+ echo >&2 " of this platform. Continue at your own risk,"
+ echo >&2 " please report success/failure to the developers."
+ ;;
+gcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+ vv '|' 'eval echo "\`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -dumpmachine\`" \
+ "gcc\`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -dumpversion\`"'
+ : "${HAVE_STRING_POOLING=i2}"
+ ;;
+hpcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+ ;;
+iar)
+ echo >&2 'Warning: IAR Systems (http://www.iar.com) compiler for embedded
+ systems detected. This unsupported compiler has not yet
+ been tested for compatibility with mksh. Continue at your
+ own risk, please report success/failure to the developers.'
+ ;;
+icc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+ ;;
+kencc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+ ;;
+lacc)
+ # no version information
+ ;;
+lcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+ add_cppflags -D__inline__=__inline
+ ;;
+metrowerks)
+ echo >&2 'Warning: Metrowerks C compiler detected. This has not yet
+ been tested for compatibility with mksh. Continue at your
+ own risk, please report success/failure to the developers.'
+ ;;
+mipspro)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+ ;;
+msc)
+ ccpr= # errorlevels are not reliable
+ case $TARGET_OS in
+ Interix)
+ if test_z "$C89_COMPILER"; then
+ C89_COMPILER=CL.EXE
+ else
+ C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"`
+ fi
+ if test_z "$C89_LINKER"; then
+ C89_LINKER=LINK.EXE
+ else
+ C89_LINKER=`ntpath2posix -c "$C89_LINKER"`
+ fi
+ vv '|' "$C89_COMPILER /HELP >&2"
+ vv '|' "$C89_LINKER /LINK >&2"
+ ;;
+ esac
+ ;;
+neatcc)
+ add_cppflags -DMKSH_DONT_EMIT_IDSTRING
+ add_cppflags -DMKSH_NO_SIGSETJMP
+ add_cppflags -Dsig_atomic_t=int
+ vv '|' "$CC"
+ ;;
+nwcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+ ;;
+pcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v"
+ ;;
+pgi)
+ echo >&2 'Warning: PGI detected. This unknown compiler has not yet
+ been tested for compatibility with mksh. Continue at your
+ own risk, please report success/failure to the developers.'
+ ;;
+sdcc)
+ echo >&2 'Warning: sdcc (http://sdcc.sourceforge.net), the small devices
+ C compiler for embedded systems detected. This has not yet
+ been tested for compatibility with mksh. Continue at your
+ own risk, please report success/failure to the developers.'
+ ;;
+sunpro)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+ ;;
+tcc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v"
+ ;;
+tendra)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V 2>&1 | \
+ grep -F -i -e version -e release"
+ ;;
+ucode)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS"
+ ;;
+uslc)
+ case $TARGET_OS:$TARGET_OSREV in
+ SCO_SV:3.2*)
+ # SCO OpenServer 5
+ CFLAGS="$CFLAGS -g"
+ : "${HAVE_CAN_OTWO=0}${HAVE_CAN_OPTIMISE=0}"
+ ;;
+ esac
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+ ;;
+watcom)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+ ;;
+xlc)
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion"
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose"
+ vv '|' "ld -V"
+ ;;
+*)
+ test x"$ct" = x"untested" && $e "!!! detecting preprocessor failed"
+ ct=unknown
+ vv '|' "$CC --version"
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+ vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+ ;;
+esac
+case $cm in
+dragonegg|llvm)
+ vv '|' "llc -version"
+ ;;
+esac
+etd=" on $et"
+case $et in
+klibc)
+ add_cppflags -DMKSH_NO_LIMITS
+ ;;
+unknown)
+ # nothing special detected, don’t worry
+ etd=
+ ;;
+*)
+ # huh?
+ ;;
+esac
+$e "$bi==> which compiler type seems to be used...$ao $ui$ct$etd$ao"
+rmf conftest.c conftest.o conftest a.out* a.exe* conftest.exe* vv.out
+
+#
+# Compiler: works as-is, with -Wno-error and -Werror
+#
+save_NOWARN=$NOWARN
+NOWARN=
+DOWARN=
+ac_flags 0 compiler_works '' 'if the compiler works'
+test 1 = $HAVE_CAN_COMPILER_WORKS || exit 1
+HAVE_COMPILER_KNOWN=0
+test $ct = unknown || HAVE_COMPILER_KNOWN=1
+if ac_ifcpp 'if 0' compiler_fails '' \
+ 'if the compiler does not fail correctly'; then
+ save_CFLAGS=$CFLAGS
+ : "${HAVE_CAN_DELEXE=x}"
+ case $ct in
+ dec)
+ CFLAGS="$CFLAGS ${ccpl}-non_shared"
+ ac_testn can_delexe compiler_fails 0 'for the -non_shared linker option' <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return (isatty(0)); }
+ EOF
+ ;;
+ dmc)
+ CFLAGS="$CFLAGS ${ccpl}/DELEXECUTABLE"
+ ac_testn can_delexe compiler_fails 0 'for the /DELEXECUTABLE linker option' <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return (isatty(0)); }
+ EOF
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+ test 1 = $HAVE_CAN_DELEXE || CFLAGS=$save_CFLAGS
+ ac_ifcpp 'if 0' compiler_still_fails \
+ 'if the compiler still does not fail correctly' && exit 1
+fi
+if ac_ifcpp 'ifdef __TINYC__' couldbe_tcc '!' compiler_known 0 \
+ 'if this could be tcc'; then
+ ct=tcc
+ CPP='cpp -D__TINYC__'
+ HAVE_COMPILER_KNOWN=1
+fi
+
+case $ct in
+bcc)
+ save_NOWARN="${ccpc}-w"
+ DOWARN="${ccpc}-w!"
+ ;;
+dec)
+ # -msg_* flags not used yet, or is -w2 correct?
+ ;;
+dmc)
+ save_NOWARN="${ccpc}-w"
+ DOWARN="${ccpc}-wx"
+ ;;
+hpcc)
+ save_NOWARN=
+ DOWARN=+We
+ ;;
+kencc)
+ save_NOWARN=
+ DOWARN=
+ ;;
+mipspro)
+ save_NOWARN=
+ DOWARN="-diag_error 1-10000"
+ ;;
+msc)
+ save_NOWARN="${ccpc}/w"
+ DOWARN="${ccpc}/WX"
+ ;;
+sunpro)
+ test x"$save_NOWARN" = x"" && save_NOWARN='-errwarn=%none'
+ ac_flags 0 errwarnnone "$save_NOWARN"
+ test 1 = $HAVE_CAN_ERRWARNNONE || save_NOWARN=
+ ac_flags 0 errwarnall "-errwarn=%all"
+ test 1 = $HAVE_CAN_ERRWARNALL && DOWARN="-errwarn=%all"
+ ;;
+tendra)
+ save_NOWARN=-w
+ ;;
+ucode)
+ save_NOWARN=
+ DOWARN=-w2
+ ;;
+watcom)
+ save_NOWARN=
+ DOWARN=-Wc,-we
+ ;;
+xlc)
+ case $TARGET_OS in
+ OS/390)
+ save_NOWARN=-qflag=e
+ DOWARN=-qflag=i
+ ;;
+ *)
+ save_NOWARN=-qflag=i:e
+ DOWARN=-qflag=i:i
+ ;;
+ esac
+ ;;
+*)
+ test x"$save_NOWARN" = x"" && save_NOWARN=-Wno-error
+ ac_flags 0 wnoerror "$save_NOWARN"
+ test 1 = $HAVE_CAN_WNOERROR || save_NOWARN=
+ ac_flags 0 werror -Werror
+ test 1 = $HAVE_CAN_WERROR && DOWARN=-Werror
+ test $ct = icc && DOWARN="$DOWARN -wd1419"
+ ;;
+esac
+NOWARN=$save_NOWARN
+
+#
+# Compiler: extra flags (-O2 -f* -W* etc.)
+#
+i=`echo :"$orig_CFLAGS" | sed 's/^://' | tr -c -d $alll$allu$alln`
+# optimisation: only if orig_CFLAGS is empty
+test_n "$i" || case $ct in
+hpcc)
+ phase=u
+ ac_flags 1 otwo +O2
+ phase=x
+ ;;
+kencc|tcc|tendra)
+ # no special optimisation
+ ;;
+sunpro)
+ cat >x <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return (isatty(0)); }
+ #define __IDSTRING_CONCAT(l,p) __LINTED__ ## l ## _ ## p
+ #define __IDSTRING_EXPAND(l,p) __IDSTRING_CONCAT(l,p)
+ #define pad void __IDSTRING_EXPAND(__LINE__,x)(void) { }
+ EOF
+ yes pad | head -n 256 >>x
+ ac_flags - 1 otwo -xO2 <x
+ rmf x
+ ;;
+xlc)
+ ac_flags 1 othree "-O3 -qstrict"
+ test 1 = $HAVE_CAN_OTHREE || ac_flags 1 otwo -O2
+ ;;
+*)
+ ac_flags 1 otwo -O2
+ test 1 = $HAVE_CAN_OTWO || ac_flags 1 optimise -O
+ ;;
+esac
+# other flags: just add them if they are supported
+i=0
+case $ct in
+bcc)
+ ac_flags 1 strpool "${ccpc}-d" 'if string pooling can be enabled'
+ ;;
+clang)
+ i=1
+ ;;
+dec)
+ ac_flags 0 verb -verbose
+ ac_flags 1 rodata -readonly_strings
+ ;;
+dmc)
+ ac_flags 1 decl "${ccpc}-r" 'for strict prototype checks'
+ ac_flags 1 schk "${ccpc}-s" 'for stack overflow checking'
+ ;;
+gcc)
+ ac_flags 1 fnolto -fno-lto 'whether we can explicitly disable buggy GCC LTO' -fno-lto
+ # The following tests run with -Werror (gcc only) if possible
+ NOWARN=$DOWARN; phase=u
+ ac_flags 1 wnodeprecateddecls -Wno-deprecated-declarations
+ # mksh is not written in CFrustFrust!
+ ac_flags 1 no_eh_frame -fno-asynchronous-unwind-tables
+ ac_flags 1 fnostrictaliasing -fno-strict-aliasing
+ ac_flags 1 fstackprotectorstrong -fstack-protector-strong
+ test 1 = $HAVE_CAN_FSTACKPROTECTORSTRONG || \
+ ac_flags 1 fstackprotectorall -fstack-protector-all
+ test $cm = dragonegg && case " $CC $CFLAGS $LDFLAGS " in
+ *\ -fplugin=*dragonegg*) ;;
+ *) ac_flags 1 fplugin_dragonegg -fplugin=dragonegg ;;
+ esac
+ case $cm in
+ combine)
+ fv=0
+ checks='7 8'
+ ;;
+ lto)
+ fv=0
+ checks='1 2 3 4 5 6 7 8'
+ ;;
+ *)
+ fv=1
+ ;;
+ esac
+ 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
+ ac_flags 1 data_abi_align -malign-data=abi
+ i=1
+ ;;
+hpcc)
+ phase=u
+ # probably not needed
+ #ac_flags 1 agcc -Agcc 'for support of GCC extensions'
+ phase=x
+ ;;
+icc)
+ ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode
+ ac_flags 1 fnostrictaliasing -fno-strict-aliasing
+ ac_flags 1 fstacksecuritycheck -fstack-security-check
+ i=1
+ ;;
+mipspro)
+ ac_flags 1 fullwarn -fullwarn 'for remark output support'
+ ;;
+msc)
+ ac_flags 1 strpool "${ccpc}/GF" 'if string pooling can be enabled'
+ echo 'int main(void) { char test[64] = ""; return (*test); }' >x
+ ac_flags - 1 stackon "${ccpc}/GZ" 'if stack checks can be enabled' <x
+ ac_flags - 1 stckall "${ccpc}/Ge" 'stack checks for all functions' <x
+ ac_flags - 1 secuchk "${ccpc}/GS" 'for compiler security checks' <x
+ rmf x
+ ac_flags 1 wall "${ccpc}/Wall" 'to enable all warnings'
+ ac_flags 1 wp64 "${ccpc}/Wp64" 'to enable 64-bit warnings'
+ ;;
+nwcc)
+ #broken# ac_flags 1 ssp -stackprotect
+ i=1
+ ;;
+pcc)
+ ac_flags 1 fstackprotectorall -fstack-protector-all
+ i=1
+ ;;
+sunpro)
+ phase=u
+ ac_flags 1 v -v
+ ac_flags 1 ipo -xipo 'for cross-module optimisation'
+ phase=x
+ ;;
+tcc)
+ : #broken# ac_flags 1 boundschk -b
+ ;;
+tendra)
+ ac_flags 0 ysystem -Ysystem
+ test 1 = $HAVE_CAN_YSYSTEM && CPPFLAGS="-Ysystem $CPPFLAGS"
+ ac_flags 1 extansi -Xa
+ ;;
+xlc)
+ case $TARGET_OS in
+ OS/390)
+ # On IBM z/OS, the following are warnings by default:
+ # CCN3296: #include file <foo.h> not found.
+ # CCN3944: Attribute "__foo__" is not supported and is ignored.
+ # CCN3963: The attribute "foo" is not a valid variable attribute and is ignored.
+ ac_flags 1 halton '-qhaltonmsg=CCN3296 -qhaltonmsg=CCN3944 -qhaltonmsg=CCN3963'
+ # CCN3290: Unknown macro name FOO on #undef directive.
+ # CCN4108: The use of keyword '__attribute__' is non-portable.
+ ac_flags 1 supprss '-qsuppress=CCN3290 -qsuppress=CCN4108'
+ ;;
+ *)
+ ac_flags 1 rodata '-qro -qroconst -qroptr'
+ ac_flags 1 rtcheck -qcheck=all
+ #ac_flags 1 rtchkc -qextchk # reported broken
+ ac_flags 1 wformat '-qformat=all -qformat=nozln'
+ ;;
+ esac
+ #ac_flags 1 wp64 -qwarn64 # too verbose for now
+ ;;
+esac
+# flags common to a subset of compilers (run with -Werror on gcc)
+if test 1 = $i; then
+ ac_flags 1 wall -Wall
+ ac_flags 1 fwrapv -fwrapv
+fi
+
+# “on demand” means: GCC version >= 4
+fd='if to rely on compiler for string pooling'
+ac_cache string_pooling || case $HAVE_STRING_POOLING in
+2) fx=' (on demand, cached)' ;;
+i1) fv=1 ;;
+i2) fv=2; fx=' (on demand)' ;;
+esac
+ac_testdone
+test x"$HAVE_STRING_POOLING" = x"0" || ac_cppflags
+
+phase=x
+# The following tests run with -Werror or similar (all compilers) if possible
+NOWARN=$DOWARN
+test $ct = pcc && phase=u
+
+#
+# Compiler: check for stuff that only generates warnings
+#
+ac_test attribute_bounded '' 'for __attribute__((__bounded__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* 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)));
+ int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); }
+ int xcopy(const void *s, void *d, size_t n) {
+ /*
+ * if memmove does not exist, we are not on a system
+ * with GCC with __bounded__ attribute either so poo
+ */
+ memmove(d, s, n); return ((int)n);
+ }
+ #endif
+EOF
+ac_test attribute_format '' 'for __attribute__((__format__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* 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 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_noreturn '' 'for __attribute__((__noreturn__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* 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__));
+ int main(void) { fnord(); }
+ void fnord(void) { exit(0); }
+ #endif
+EOF
+ac_test attribute_pure '' 'for __attribute__((__pure__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* force a failure: TenDRA and gcc 1.42 have false positive here */
+ int main(void) { return (thiswillneverbedefinedIhope()); }
+ #else
+ #include <unistd.h>
+ #undef __attribute__
+ int foo(const char *) __attribute__((__pure__));
+ int main(int ac, char *av[]) { return (foo(av[ac - 1]) + isatty(0)); }
+ int foo(const char *s) { return ((int)s[0]); }
+ #endif
+EOF
+ac_test attribute_unused '' 'for __attribute__((__unused__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* force a failure: TenDRA and gcc 1.42 have false positive here */
+ int main(void) { return (thiswillneverbedefinedIhope()); }
+ #else
+ #include <unistd.h>
+ #undef __attribute__
+ int main(int ac __attribute__((__unused__)), char *av[]
+ __attribute__((__unused__))) { return (isatty(0)); }
+ #endif
+EOF
+ac_test attribute_used '' 'for __attribute__((__used__))' <<-'EOF'
+ #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+ extern int thiswillneverbedefinedIhope(void);
+ /* force a failure: TenDRA and gcc 1.42 have false positive here */
+ int main(void) { return (thiswillneverbedefinedIhope()); }
+ #else
+ #include <unistd.h>
+ #undef __attribute__
+ static const char fnord[] __attribute__((__used__)) = "42";
+ int main(void) { return (isatty(0)); }
+ #endif
+EOF
+
+# End of tests run with -Werror
+NOWARN=$save_NOWARN
+phase=x
+
+#
+# mksh: flavours (full/small mksh, omit certain stuff)
+#
+if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \
+ "if a reduced-feature mksh is requested"; then
+ : "${HAVE_NICE=0}"
+ : "${HAVE_PERSISTENT_HISTORY=0}"
+ check_categories="$check_categories smksh"
+fi
+ac_ifcpp 'if defined(MKSH_BINSHPOSIX) || defined(MKSH_BINSHREDUCED)' \
+ isset_MKSH_BINSH '' 'if invoking as sh should be handled specially' && \
+ 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"
+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 'if !MKSH_ASSUME_UTF8' isoff_MKSH_ASSUME_UTF8 \
+ isset_MKSH_ASSUME_UTF8 0 \
+ 'if the default UTF-8 mode is disabled' && \
+ check_categories="$check_categories noutf8"
+#ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \
+# "if deprecated features are to be omitted" && \
+# check_categories="$check_categories nodeprecated"
+#ac_ifcpp 'ifdef MKSH_DISABLE_EXPERIMENTAL' isset_MKSH_DISABLE_EXPERIMENTAL '' \
+# "if experimental features are to be omitted" && \
+# check_categories="$check_categories noexperimental"
+ac_ifcpp 'ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT' isset_MKSH_MIDNIGHTBSD01ASH_COMPAT '' \
+ 'if the MidnightBSD 0.1 ash compatibility mode is requested' && \
+ check_categories="$check_categories mnbsdash"
+
+#
+# Environment: headers
+#
+ac_header sys/time.h sys/types.h
+ac_header time.h sys/types.h
+test "11" = "$HAVE_SYS_TIME_H$HAVE_TIME_H" || HAVE_BOTH_TIME_H=0
+ac_test both_time_h '' 'whether <sys/time.h> and <time.h> can both be included' <<-'EOF'
+ #include <sys/types.h>
+ #include <sys/time.h>
+ #include <time.h>
+ #include <unistd.h>
+ int main(void) { struct tm tm; return ((int)sizeof(tm) + isatty(0)); }
+EOF
+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/resource.h sys/types.h _time
+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 io.h
+ac_header libgen.h
+ac_header libutil.h sys/types.h
+ac_header paths.h
+ac_header stdint.h stdarg.h
+# include strings.h only if compatible with string.h
+ac_header strings.h sys/types.h string.h
+ac_header termios.h
+ac_header ulimit.h sys/types.h
+ac_header values.h
+
+#
+# Environment: definitions
+#
+echo '#include <sys/types.h>
+#include <unistd.h>
+/* check that off_t can represent 2^63-1 correctly, thx FSF */
+#define LARGE_OFF_T ((((off_t)1 << 31) << 31) - 1 + (((off_t)1 << 31) << 31))
+int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 &&
+ LARGE_OFF_T % 2147483647 == 1) ? 1 : -1];
+int main(void) { return (isatty(0)); }' >lft.c
+ac_testn can_lfs '' "for large file support" <lft.c
+save_CPPFLAGS=$CPPFLAGS
+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
+ 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
+rm -f lft.c
+rmf lft* # end of large file support test
+
+#
+# Environment: types
+#
+ac_test can_inttypes '!' stdint_h 1 "for standard 32-bit integer types" <<-'EOF'
+ #include <sys/types.h>
+ #include <stddef.h>
+ int main(int ac, char *av[]) { return ((uint32_t)(size_t)*av + (int32_t)ac); }
+EOF
+ac_test can_ucbints '!' can_inttypes 1 "for UCB 32-bit integer types" <<-'EOF'
+ #include <sys/types.h>
+ #include <stddef.h>
+ int main(int ac, char *av[]) { return ((u_int32_t)(size_t)*av + (int32_t)ac); }
+EOF
+ac_test can_int8type '!' stdint_h 1 "for standard 8-bit integer type" <<-'EOF'
+ #include <sys/types.h>
+ #include <stddef.h>
+ int main(int ac, char *av[]) { return ((uint8_t)(size_t)av[ac]); }
+EOF
+ac_test can_ucbint8 '!' can_int8type 1 "for UCB 8-bit integer type" <<-'EOF'
+ #include <sys/types.h>
+ #include <stddef.h>
+ int main(int ac, char *av[]) { return ((u_int8_t)(size_t)av[ac]); }
+EOF
+
+ac_test rlim_t <<-'EOF'
+ #include <sys/types.h>
+ #if HAVE_BOTH_TIME_H
+ #include <sys/time.h>
+ #include <time.h>
+ #elif HAVE_SYS_TIME_H
+ #include <sys/time.h>
+ #elif HAVE_TIME_H
+ #include <time.h>
+ #endif
+ #if HAVE_SYS_RESOURCE_H
+ #include <sys/resource.h>
+ #endif
+ #include <unistd.h>
+ int main(void) { return (((int)(rlim_t)0) + isatty(0)); }
+EOF
+
+# only testn: added later below
+ac_testn sig_t <<-'EOF'
+ #include <sys/types.h>
+ #include <signal.h>
+ #include <stddef.h>
+ volatile sig_t foo = (sig_t)0;
+ int main(void) { return (foo == (sig_t)0); }
+EOF
+
+ac_testn sighandler_t '!' sig_t 0 <<-'EOF'
+ #include <sys/types.h>
+ #include <signal.h>
+ #include <stddef.h>
+ volatile sighandler_t foo = (sighandler_t)0;
+ int main(void) { return (foo == (sighandler_t)0); }
+EOF
+if test 1 = $HAVE_SIGHANDLER_T; then
+ add_cppflags -Dsig_t=sighandler_t
+ HAVE_SIG_T=1
+fi
+
+ac_testn __sighandler_t '!' sig_t 0 <<-'EOF'
+ #include <sys/types.h>
+ #include <signal.h>
+ #include <stddef.h>
+ volatile __sighandler_t foo = (__sighandler_t)0;
+ int main(void) { return (foo == (__sighandler_t)0); }
+EOF
+if test 1 = $HAVE___SIGHANDLER_T; then
+ add_cppflags -Dsig_t=__sighandler_t
+ HAVE_SIG_T=1
+fi
+
+test 1 = $HAVE_SIG_T || add_cppflags -Dsig_t=nosig_t
+ac_cppflags SIG_T
+
+#
+# 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("$srcversion");
+ int main(void) {
+ struct timeval tv;
+ printf("Hello, World!\\n");
+ return (time(&tv.tv_sec));
+ }
+EOF
+ case $cm in
+ llvm)
+ v "$CC $CFLAGS $CPPFLAGS $NOWARN -emit-llvm -c conftest.c" || fv=0
+ rmf $tfn.s
+ test $fv = 0 || v "llvm-link -o - conftest.o | opt $optflags | llc -o $tfn.s" || fv=0
+ test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn $tfn.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 $tfn.s
+ test $fv = 0 || v "llvm-link -o - conftest.bc | opt $optflags | llc -o $tfn.s" || fv=0
+ test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn $tfn.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: errors and signals
+#
+test x"NetBSD" = x"$TARGET_OS" && $e Ignore the compatibility warning.
+
+ac_testn sys_errlist '' "the sys_errlist[] array and sys_nerr" <<-'EOF'
+ extern const int sys_nerr;
+ extern const char * const sys_errlist[];
+ extern int isatty(int);
+ int main(void) { return (*sys_errlist[sys_nerr - 1] + isatty(0)); }
+EOF
+ac_testn _sys_errlist '!' sys_errlist 0 "the _sys_errlist[] array and _sys_nerr" <<-'EOF'
+ extern const int _sys_nerr;
+ extern const char * const _sys_errlist[];
+ extern int isatty(int);
+ int main(void) { return (*_sys_errlist[_sys_nerr - 1] + isatty(0)); }
+EOF
+if test 1 = "$HAVE__SYS_ERRLIST"; then
+ add_cppflags -Dsys_nerr=_sys_nerr
+ add_cppflags -Dsys_errlist=_sys_errlist
+ HAVE_SYS_ERRLIST=1
+fi
+ac_cppflags SYS_ERRLIST
+
+for what in name list; do
+ uwhat=`upper $what`
+ ac_testn sys_sig$what '' "the sys_sig$what[] array" <<-EOF
+ extern const char * const sys_sig$what[];
+ extern int isatty(int);
+ int main(void) { return (sys_sig$what[0][0] + isatty(0)); }
+ EOF
+ ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig$what[] array" <<-EOF
+ extern const char * const _sys_sig$what[];
+ extern int isatty(int);
+ int main(void) { return (_sys_sig$what[0][0] + isatty(0)); }
+ EOF
+ eval uwhat_v=\$HAVE__SYS_SIG$uwhat
+ if test 1 = "$uwhat_v"; then
+ add_cppflags -Dsys_sig$what=_sys_sig$what
+ eval HAVE_SYS_SIG$uwhat=1
+ fi
+ ac_cppflags SYS_SIG$uwhat
+done
+
+#
+# Environment: library functions
+#
+ac_test flock <<-'EOF'
+ #include <sys/types.h>
+ #include <fcntl.h>
+ #undef flock
+ #if HAVE_SYS_FILE_H
+ #include <sys/file.h>
+ #endif
+ int main(void) { return (flock(0, LOCK_EX | LOCK_UN)); }
+EOF
+
+ac_test lock_fcntl '!' flock 1 'whether we can lock files with fcntl' <<-'EOF'
+ #include <fcntl.h>
+ #undef flock
+ int main(void) {
+ struct flock lks;
+ lks.l_type = F_WRLCK | F_UNLCK;
+ return (fcntl(0, F_SETLKW, &lks));
+ }
+EOF
+
+ac_test getrusage <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) {
+ struct rusage ru;
+ return (getrusage(RUSAGE_SELF, &ru) +
+ getrusage(RUSAGE_CHILDREN, &ru));
+ }
+EOF
+
+ac_test getsid <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return ((int)getsid(0)); }
+EOF
+
+ac_test gettimeofday <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { struct timeval tv; return (gettimeofday(&tv, NULL)); }
+EOF
+
+ac_test killpg <<-'EOF'
+ #include <signal.h>
+ int main(int ac, char *av[]) { return (av[0][killpg(123, ac)]); }
+EOF
+
+ac_test memmove <<-'EOF'
+ #include <sys/types.h>
+ #include <stddef.h>
+ #include <string.h>
+ #if HAVE_STRINGS_H
+ #include <strings.h>
+ #endif
+ int main(int ac, char *av[]) {
+ return (*(int *)(void *)memmove(av[0], av[1], (size_t)ac));
+ }
+EOF
+
+ac_test mknod '' 'if to use mknod(), makedev() and friends' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(int ac, char *av[]) {
+ dev_t dv;
+ dv = makedev((unsigned int)ac, (unsigned int)av[0][0]);
+ return (mknod(av[0], (mode_t)0, dv) ? (int)major(dv) :
+ (int)minor(dv));
+ }
+EOF
+
+ac_test mmap lock_fcntl 0 'for mmap and munmap' <<-'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 <stddef.h>
+ #include <stdlib.h>
+ int main(void) { return ((void *)mmap(NULL, (size_t)0,
+ PROT_READ, MAP_PRIVATE, 0, (off_t)0) == (void *)NULL ? 1 :
+ munmap(NULL, 0)); }
+EOF
+
+ac_test ftruncate mmap 0 'for ftruncate' <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return (ftruncate(0, 0)); }
+EOF
+
+ac_test nice <<-'EOF'
+ #include <unistd.h>
+ int main(void) { return (nice(4)); }
+EOF
+
+ac_test revoke <<-'EOF'
+ #include <sys/types.h>
+ #if HAVE_LIBUTIL_H
+ #include <libutil.h>
+ #endif
+ #include <unistd.h>
+ int main(int ac, char *av[]) { return (ac + revoke(av[0])); }
+EOF
+
+ac_test setlocale_ctype '' 'setlocale(LC_CTYPE, "")' <<-'EOF'
+ #include <locale.h>
+ #include <stddef.h>
+ int main(void) { return ((int)(size_t)(void *)setlocale(LC_CTYPE, "")); }
+EOF
+
+ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF'
+ #include <langinfo.h>
+ #include <stddef.h>
+ int main(void) { return ((int)(size_t)(void *)nl_langinfo(CODESET)); }
+EOF
+
+ac_test select <<-'EOF'
+ #include <sys/types.h>
+ #if HAVE_BOTH_TIME_H
+ #include <sys/time.h>
+ #include <time.h>
+ #elif HAVE_SYS_TIME_H
+ #include <sys/time.h>
+ #elif HAVE_TIME_H
+ #include <time.h>
+ #endif
+ #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'
+ #include <sys/types.h>
+ #include <unistd.h>
+ int main(void) { return (setresuid(0,0,0) + setresgid(0,0,0)); }
+EOF
+
+ac_test setgroups setresugid 0 <<-'EOF'
+ #include <sys/types.h>
+ #if HAVE_GRP_H
+ #include <grp.h>
+ #endif
+ #include <unistd.h>
+ int main(void) { gid_t gid = 0; return (setgroups(0, &gid)); }
+EOF
+
+if test x"$et" = x"klibc"; then
+
+ ac_testn __rt_sigsuspend '' 'whether klibc uses RT signals' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ extern int __rt_sigsuspend(const sigset_t *, size_t);
+ int main(void) { return (__rt_sigsuspend(NULL, 0)); }
+EOF
+
+ # no? damn! legacy crap ahead!
+
+ ac_testn __sigsuspend_s '!' __rt_sigsuspend 1 \
+ 'whether sigsuspend is usable (1/2)' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ extern int __sigsuspend_s(sigset_t);
+ int main(void) { return (__sigsuspend_s(0)); }
+EOF
+ ac_testn __sigsuspend_xxs '!' __sigsuspend_s 1 \
+ 'whether sigsuspend is usable (2/2)' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ extern int __sigsuspend_xxs(int, int, sigset_t);
+ int main(void) { return (__sigsuspend_xxs(0, 0, 0)); }
+EOF
+
+ if test "000" = "$HAVE___RT_SIGSUSPEND$HAVE___SIGSUSPEND_S$HAVE___SIGSUSPEND_XXS"; then
+ # no usable sigsuspend(), use pause() *ugh*
+ add_cppflags -DMKSH_NO_SIGSUSPEND
+ fi
+fi
+
+ac_test strerror '!' sys_errlist 0 <<-'EOF'
+ extern char *strerror(int);
+ int main(int ac, char *av[]) { return (*strerror(*av[ac])); }
+EOF
+
+ac_test strsignal '!' sys_siglist 0 <<-'EOF'
+ #include <string.h>
+ #include <signal.h>
+ int main(void) { return (strsignal(1)[0]); }
+EOF
+
+ac_test strlcpy <<-'EOF'
+ #include <string.h>
+ int main(int ac, char *av[]) { return (strlcpy(*av, av[1],
+ (size_t)ac)); }
+EOF
+
+#
+# check headers for declarations
+#
+ac_test flock_decl flock 1 'for declaration of flock()' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ #if HAVE_SYS_FILE_H
+ #include <sys/file.h>
+ #endif
+ int main(void) { return ((flock)(0, 0)); }
+EOF
+ac_test revoke_decl revoke 1 'for declaration of revoke()' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { return ((revoke)("")); }
+EOF
+ac_test sys_errlist_decl sys_errlist 0 "for declaration of sys_errlist[] and sys_nerr" <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { return (*sys_errlist[sys_nerr - 1] + isatty(0)); }
+EOF
+ac_test sys_siglist_decl sys_siglist 0 'for declaration of sys_siglist[]' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { return (sys_siglist[0][0] + isatty(0)); }
+EOF
+
+ac_test st_mtim '' 'for struct stat.st_mtim.tv_nsec' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { struct stat sb; return (sizeof(sb.st_mtim.tv_nsec)); }
+EOF
+ac_test st_mtimensec '!' st_mtim 0 'for struct stat.st_mtimensec' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ int main(void) { struct stat sb; return (sizeof(sb.st_mtimensec)); }
+EOF
+
+#
+# other checks
+#
+fd='if to use persistent history'
+ac_cache PERSISTENT_HISTORY || case $HAVE_FTRUNCATE$HAVE_MMAP$HAVE_FLOCK$HAVE_LOCK_FCNTL in
+111*|1101) fv=1 ;;
+esac
+test 1 = $fv || check_categories="$check_categories no-histfile"
+ac_testdone
+ac_cppflags
+
+#
+# extra checks for legacy mksh
+#
+if test $legacy = 1; then
+ ac_test long_32bit '' 'whether long is 32 bit wide' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ #ifndef CHAR_BIT
+ #define CHAR_BIT 0
+ #endif
+ struct ctasserts {
+ #define cta(name,assertion) char name[(assertion) ? 1 : -1]
+ cta(char_is_8_bits, (CHAR_BIT) == 8);
+ cta(long_is_32_bits, sizeof(long) == 4);
+ };
+ int main(void) { return (sizeof(struct ctasserts)); }
+EOF
+
+ ac_test long_64bit '!' long_32bit 0 'whether long is 64 bit wide' <<-'EOF'
+ #define MKSH_INCLUDES_ONLY
+ #include "sh.h"
+ #ifndef CHAR_BIT
+ #define CHAR_BIT 0
+ #endif
+ struct ctasserts {
+ #define cta(name,assertion) char name[(assertion) ? 1 : -1]
+ cta(char_is_8_bits, (CHAR_BIT) == 8);
+ cta(long_is_64_bits, sizeof(long) == 8);
+ };
+ int main(void) { return (sizeof(struct ctasserts)); }
+EOF
+
+ case $HAVE_LONG_32BIT$HAVE_LONG_64BIT in
+ 10) check_categories="$check_categories int:32" ;;
+ 01) check_categories="$check_categories int:64" ;;
+ *) check_categories="$check_categories int:u" ;;
+ esac
+fi
+
+#
+# Compiler: Praeprocessor (only if needed)
+#
+test 0 = $HAVE_SYS_SIGNAME && if ac_testinit cpp_dd '' \
+ 'checking if the C Preprocessor supports -dD'; then
+ echo '#define foo bar' >conftest.c
+ vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c >x"
+ grep '#define foo bar' x >/dev/null 2>&1 && fv=1
+ rmf conftest.c x vv.out
+ ac_testdone
+fi
+
+#
+# End of mirtoconf checks
+#
+$e ... done.
+
+# Some operating systems have ancient versions of ed(1) writing
+# 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"
+rmf x vv.out
+
+if test 0 = $HAVE_SYS_SIGNAME; then
+ if test 1 = $HAVE_CPP_DD; then
+ $e Generating list of signal names...
+ else
+ $e No list of signal names available via cpp. Falling back...
+ fi
+ sigseenone=:
+ sigseentwo=:
+ echo '#include <signal.h>
+#if defined(NSIG_MAX)
+#define cfg_NSIG NSIG_MAX
+#elif defined(NSIG)
+#define cfg_NSIG NSIG
+#elif defined(_NSIG)
+#define cfg_NSIG _NSIG
+#elif defined(SIGMAX)
+#define cfg_NSIG (SIGMAX + 1)
+#elif defined(_SIGMAX)
+#define cfg_NSIG (_SIGMAX + 1)
+#else
+/*XXX better error out, see sh.h */
+#define cfg_NSIG 64
+#endif
+int
+mksh_cfg= cfg_NSIG
+;' >conftest.c
+ # GNU sed 2.03 segfaults when optimising this to sed -n
+ NSIG=`vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
+ grep -v '^#' | \
+ sed '/mksh_cfg.*= *$/{
+ N
+ s/\n/ /
+ }' | \
+ grep '^ *mksh_cfg *=' | \
+ sed 's/^ *mksh_cfg *=[ ]*\([()0-9x+-][()0-9x+ -]*\).*$/\1/'`
+ case $NSIG in
+ *mksh_cfg*) $e "Error: NSIG='$NSIG'"; NSIG=0 ;;
+ *[\ \(\)+-]*) NSIG=`"$AWK" "BEGIN { print $NSIG }" </dev/null` ;;
+ esac
+ printf=printf
+ (printf hallo) >/dev/null 2>&1 || printf=echo
+ test $printf = echo || test "`printf %d 42`" = 42 || printf=echo
+ test $printf = echo || NSIG=`printf %d "$NSIG" 2>/dev/null`
+ $printf "NSIG=$NSIG ... "
+ sigs="ABRT FPE ILL INT SEGV TERM ALRM BUS CHLD CONT HUP KILL PIPE QUIT"
+ sigs="$sigs STOP TSTP TTIN TTOU USR1 USR2 POLL PROF SYS TRAP URG VTALRM"
+ sigs="$sigs XCPU XFSZ INFO WINCH EMT IO DIL LOST PWR SAK CLD IOT STKFLT"
+ sigs="$sigs ABND DCE DUMP IOERR TRACE DANGER THCONT THSTOP RESV UNUSED"
+ test 1 = $HAVE_CPP_DD && test $NSIG -gt 1 && sigs="$sigs "`vq \
+ "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c" | \
+ grep '[ ]SIG[A-Z0-9][A-Z0-9]*[ ]' | \
+ sed 's/^.*[ ]SIG\([A-Z0-9][A-Z0-9]*\)[ ].*$/\1/' | sort`
+ test $NSIG -gt 1 || sigs=
+ for name in $sigs; do
+ case $sigseenone in
+ *:$name:*) continue ;;
+ esac
+ sigseenone=$sigseenone$name:
+ echo '#include <signal.h>' >conftest.c
+ echo int >>conftest.c
+ echo mksh_cfg= SIG$name >>conftest.c
+ echo ';' >>conftest.c
+ # GNU sed 2.03 croaks on optimising this, too
+ vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
+ grep -v '^#' | \
+ sed '/mksh_cfg.*= *$/{
+ N
+ s/\n/ /
+ }' | \
+ grep '^ *mksh_cfg *=' | \
+ sed 's/^ *mksh_cfg *=[ ]*\([0-9][0-9x]*\).*$/:\1 '$name/
+ done | sed -n '/^:[^ ]/s/^://p' | while read nr name; do
+ test $printf = echo || nr=`printf %d "$nr" 2>/dev/null`
+ test $nr -gt 0 && test $nr -lt $NSIG || continue
+ case $sigseentwo in
+ *:$nr:*) ;;
+ *) echo " { \"$name\", $nr },"
+ sigseentwo=$sigseentwo$nr:
+ $printf "$name=$nr " >&2
+ ;;
+ esac
+ done 2>&1 >signames.inc
+ rmf conftest.c
+ $e done.
+fi
+
+addsrcs '!' HAVE_STRLCPY strlcpy.c
+addsrcs USE_PRINTF_BUILTIN printf.c
+test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
+test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
+add_cppflags -DMKSH_BUILD_R=592
+
+$e $bi$me: Finished configuration testing, now producing output.$ao
+
+files=
+objs=
+sp=
+case $tcfn in
+a.exe|conftest.exe)
+ mkshexe=$tfn.exe
+ add_cppflags -DMKSH_EXE_EXT
+ ;;
+*)
+ mkshexe=$tfn
+ ;;
+esac
+case $curdir in
+*\ *) mkshshebang="#!./$mkshexe" ;;
+*) mkshshebang="#!$curdir/$mkshexe" ;;
+esac
+cat >test.sh <<-EOF
+ $mkshshebang
+ LC_ALL=C PATH='$PATH'; export LC_ALL PATH
+ case \$KSH_VERSION in
+ *MIRBSD*|*LEGACY*) ;;
+ *) exit 1 ;;
+ esac
+ set -A check_categories -- $check_categories
+ pflag='$curdir/$mkshexe'
+ sflag='$srcdir/check.t'
+ usee=0 useU=0 Pflag=0 Sflag=0 uset=0 vflag=1 xflag=0
+ while getopts "C:e:fPp:QSs:t:U:v" ch; do case \$ch {
+ (C) check_categories[\${#check_categories[*]}]=\$OPTARG ;;
+ (e) usee=1; eflag=\$OPTARG ;;
+ (f) check_categories[\${#check_categories[*]}]=fastbox ;;
+ (P) Pflag=1 ;;
+ (+P) Pflag=0 ;;
+ (p) pflag=\$OPTARG ;;
+ (Q) vflag=0 ;;
+ (+Q) vflag=1 ;;
+ (S) Sflag=1 ;;
+ (+S) Sflag=0 ;;
+ (s) sflag=\$OPTARG ;;
+ (t) uset=1; tflag=\$OPTARG ;;
+ (U) useU=1; Uflag=\$OPTARG ;;
+ (v) vflag=1 ;;
+ (+v) vflag=0 ;;
+ (*) xflag=1 ;;
+ }
+ done
+ shift \$((OPTIND - 1))
+ set -A args -- '$srcdir/check.pl' -p "\$pflag"
+ if $ebcdic; then
+ args[\${#args[*]}]=-E
+ fi
+ 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
+ if (( useU )); then
+ args[\${#args[*]}]=-U
+ args[\${#args[*]}]=\$Uflag
+ fi
+ (( vflag )) && args[\${#args[*]}]=-v
+ (( xflag )) && args[\${#args[*]}]=-x # force usage by synerr
+ if [[ -n \$TMPDIR && -d \$TMPDIR/. ]]; then
+ args[\${#args[*]}]=-T
+ args[\${#args[*]}]=\$TMPDIR
+ fi
+ print Testing mksh for conformance:
+ grep -F -e Mir''OS: -e MIRBSD "\$sflag"
+ print "This shell is actually:\\n\\t\$KSH_VERSION"
+ print 'test.sh built for mksh $dstversion'
+ cstr='\$os = defined \$^O ? \$^O : "unknown";'
+ cstr="\$cstr"'print \$os . ", Perl version " . \$];'
+ for perli in \$PERL perl5 perl no; do
+ if [[ \$perli = no ]]; then
+ print Cannot find a working Perl interpreter, aborting.
+ exit 1
+ fi
+ print "Trying Perl interpreter '\$perli'..."
+ perlos=\$(\$perli -e "\$cstr")
+ rv=\$?
+ print "Errorlevel \$rv, running on '\$perlos'"
+ if (( rv )); then
+ print "=> not using"
+ continue
+ fi
+ if [[ -n \$perlos ]]; then
+ print "=> using it"
+ break
+ fi
+ done
+ (( Sflag )) || echo + \$perli "\${args[@]}" -s "\$sflag" "\$@"
+ (( Sflag )) || exec \$perli "\${args[@]}" -s "\$sflag" "\$@"$tsts
+ # use of the -S option for check.t split into multiple chunks
+ rv=0
+ for s in "\$sflag".*; do
+ echo + \$perli "\${args[@]}" -s "\$s" "\$@"
+ \$perli "\${args[@]}" -s "\$s" "\$@"$tsts
+ rc=\$?
+ (( rv = rv ? rv : rc ))
+ done
+ exit \$rv
+EOF
+chmod 755 test.sh
+case $cm in
+dragonegg)
+ emitbc="-S -flto"
+ ;;
+llvm)
+ emitbc="-emit-llvm -c"
+ ;;
+*)
+ emitbc=-c
+ ;;
+esac
+echo ": # work around NeXTstep bug" >Rebuild.sh
+cd "$srcdir"
+optfiles=`echo *.opt`
+cd "$curdir"
+for file in $optfiles; do
+ echo "echo + Running genopt on '$file'..."
+ echo "(srcfile='$srcdir/$file'; BUILDSH_RUN_GENOPT=1; . '$srcdir/Build.sh')"
+done >>Rebuild.sh
+echo set -x >>Rebuild.sh
+for file in $SRCS; do
+ op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'`
+ test -f $file || file=$srcdir/$file
+ files="$files$sp$file"
+ sp=' '
+ echo "$CC $CFLAGS $CPPFLAGS $emitbc $file || exit 1" >>Rebuild.sh
+ if test $cm = dragonegg; then
+ echo "mv ${op}s ${op}ll" >>Rebuild.sh
+ echo "llvm-as ${op}ll || exit 1" >>Rebuild.sh
+ objs="$objs$sp${op}bc"
+ else
+ objs="$objs$sp${op}o"
+ fi
+done
+case $cm in
+dragonegg|llvm)
+ echo "rm -f $tfn.s" >>Rebuild.sh
+ echo "llvm-link -o - $objs | opt $optflags | llc -o $tfn.s" >>Rebuild.sh
+ lobjs=$tfn.s
+ ;;
+*)
+ lobjs=$objs
+ ;;
+esac
+echo tcfn=$mkshexe >>Rebuild.sh
+echo "$CC $CFLAGS $LDFLAGS -o \$tcfn $lobjs $LIBS $ccpr" >>Rebuild.sh
+echo "test -f \$tcfn || exit 1; $SIZE \$tcfn" >>Rebuild.sh
+if test $cm = makefile; then
+ extras='emacsfn.h exprtok.h rlimits.opt sh.h sh_flags.opt var_spec.h'
+ test 0 = $HAVE_SYS_SIGNAME && extras="$extras signames.inc"
+ gens= genq=
+ for file in $optfiles; do
+ genf=`basename "$file" | sed 's/.opt$/.gen/'`
+ gens="$gens $genf"
+ genq="$genq$nl$genf: $srcdir/Build.sh $srcdir/$file
+ srcfile=$srcdir/$file; BUILDSH_RUN_GENOPT=1; . $srcdir/Build.sh"
+ done
+ cat >Makefrag.inc <<EOF
+# Makefile fragment for building mksh $dstversion
+
+PROG= $mkshexe
+MAN= mksh.1
+SRCS= $SRCS
+SRCS_FP= $files
+OBJS_BP= $objs
+INDSRCS= $extras
+NONSRCS_INST= dot.mkshrc \$(MAN)
+NONSRCS_NOINST= Build.sh Makefile Rebuild.sh check.pl check.t test.sh
+CC= $CC
+CPPFLAGS= $CPPFLAGS
+CFLAGS= $CFLAGS
+LDFLAGS= $LDFLAGS
+LIBS= $LIBS
+
+.depend \$(OBJS_BP):$gens$genq
+
+# not BSD make only:
+#VPATH= $srcdir
+#all: \$(PROG)
+#\$(PROG): \$(OBJS_BP)
+# \$(CC) \$(CFLAGS) \$(LDFLAGS) -o \$@ \$(OBJS_BP) \$(LIBS)
+#\$(OBJS_BP): \$(SRCS_FP) \$(NONSRCS)
+#.c.o:
+# \$(CC) \$(CFLAGS) \$(CPPFLAGS) -c \$<
+
+# for all make variants:
+#REGRESS_FLAGS= -f
+#regress:
+# ./test.sh \$(REGRESS_FLAGS)
+check_categories=$check_categories
+
+# for BSD make only:
+#.PATH: $srcdir
+#.include <bsd.prog.mk>
+EOF
+ $e
+ $e Generated Makefrag.inc successfully.
+ exit 0
+fi
+for file in $optfiles; do
+ $e "+ Running genopt on '$file'..."
+ do_genopt "$srcdir/$file" || exit 1
+done
+if test $cm = combine; then
+ objs="-o $mkshexe"
+ for file in $SRCS; do
+ test -f $file || file=$srcdir/$file
+ objs="$objs $file"
+ done
+ emitbc="-fwhole-program --combine"
+ v "$CC $CFLAGS $CPPFLAGS $LDFLAGS $emitbc $objs $LIBS $ccpr"
+elif test 1 = $pm; then
+ for file in $SRCS; do
+ test -f $file || file=$srcdir/$file
+ v "$CC $CFLAGS $CPPFLAGS $emitbc $file" &
+ done
+ wait
+else
+ for file in $SRCS; do
+ test $cm = dragonegg && \
+ op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'`
+ test -f $file || file=$srcdir/$file
+ v "$CC $CFLAGS $CPPFLAGS $emitbc $file" || exit 1
+ if test $cm = dragonegg; then
+ v "mv ${op}s ${op}ll"
+ v "llvm-as ${op}ll" || exit 1
+ fi
+ done
+fi
+case $cm in
+dragonegg|llvm)
+ rmf $tfn.s
+ v "llvm-link -o - $objs | opt $optflags | llc -o $tfn.s"
+ ;;
+esac
+tcfn=$mkshexe
+test $cm = combine || v "$CC $CFLAGS $LDFLAGS -o $tcfn $lobjs $LIBS $ccpr"
+test -f $tcfn || exit 1
+test 1 = $r || v "$NROFF -mdoc <'$srcdir/lksh.1' >lksh.cat1" || rmf lksh.cat1
+test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || rmf mksh.cat1
+test 1 = $r || v "(set -- ''; . '$srcdir/FAQ2HTML.sh')" || rmf FAQ.htm
+test 0 = $eq && v $SIZE $tcfn
+i=install
+test -f /usr/ucb/$i && i=/usr/ucb/$i
+test 1 = $eq && e=:
+$e
+$e Installing the shell:
+$e "# $i -c -s -o root -g bin -m 555 $tfn /bin/$tfn"
+if test $legacy = 0; then
+ $e "# grep -x /bin/$tfn /etc/shells >/dev/null || echo /bin/$tfn >>/etc/shells"
+ $e "# $i -c -o root -g bin -m 444 dot.mkshrc /usr/share/doc/mksh/examples/"
+fi
+$e
+$e Installing the manual:
+if test -f FAQ.htm; then
+ $e "# $i -c -o root -g bin -m 444 FAQ.htm /usr/share/doc/mksh/"
+fi
+if test -f mksh.cat1; then
+ if test -f FAQ.htm; then
+ $e plus either
+ fi
+ $e "# $i -c -o root -g bin -m 444 lksh.cat1" \
+ "/usr/share/man/cat1/lksh.0"
+ $e "# $i -c -o root -g bin -m 444 mksh.cat1" \
+ "/usr/share/man/cat1/mksh.0"
+ $e or
+fi
+$e "# $i -c -o root -g bin -m 444 lksh.1 mksh.1 /usr/share/man/man1/"
+$e
+$e Run the regression test suite: ./test.sh
+$e Please also read the sample file dot.mkshrc and the fine manual.
+test -f FAQ.htm || \
+ $e Run FAQ2HTML.sh and place FAQ.htm into a suitable location as well.
+exit 0
+
+: <<'EOD'
+
+=== Environment used ===
+
+==== build environment ====
+AWK default: awk
+CC default: cc
+CFLAGS if empty, defaults to -xO2 or +O2
+ or -O3 -qstrict or -O2, per compiler
+CPPFLAGS default empty
+LDFLAGS default empty; added before sources
+LDSTATIC set this to '-static'; default unset
+LIBS default empty; added after sources
+ [Interix] default: -lcrypt (XXX still needed?)
+NOWARN -Wno-error or similar
+NROFF default: nroff
+TARGET_OS default: $(uname -s || uname)
+TARGET_OSREV [QNX] default: $(uname -r)
+
+==== feature selectors ====
+USE_PRINTF_BUILTIN 1 to include (unsupported) printf(1) as builtin
+===== general format =====
+HAVE_STRLEN ac_test
+HAVE_STRING_H ac_header
+HAVE_CAN_FSTACKPROTECTORALL ac_flags
+
+==== cpp definitions ====
+DEBUG dont use in production, wants gcc, implies:
+DEBUG_LEAKS enable freeing resources before exiting
+KSH_VERSIONNAME_VENDOR_EXT when patching; space+plus+word (e.g. " +SuSE")
+MKSHRC_PATH "~/.mkshrc" (do not change)
+MKSH_A4PB force use of arc4random_pushb
+MKSH_ASSUME_UTF8 (0=disabled, 1=enabled; default: unset)
+MKSH_BINSHPOSIX if */sh or */-sh, enable set -o posix
+MKSH_BINSHREDUCED if */sh or */-sh, enable set -o sh
+MKSH_CLS_STRING KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J"
+MKSH_DEFAULT_EXECSHELL "/bin/sh" (do not change)
+MKSH_DEFAULT_PROFILEDIR "/etc" (do not change)
+MKSH_DEFAULT_TMPDIR "/tmp" (do not change)
+MKSH_DISABLE_DEPRECATED disable code paths scheduled for later removal
+MKSH_DISABLE_EXPERIMENTAL disable code not yet comfy for (LTS) snapshots
+MKSH_DISABLE_TTY_WARNING shut up warning about ctty if OS cant be fixed
+MKSH_DONT_EMIT_IDSTRING omit RCS IDs from binary
+MKSH_EARLY_LOCALE_TRACKING track utf8-mode from POSIX locale, for SuSE
+MKSH_MIDNIGHTBSD01ASH_COMPAT set -o sh: additional compatibility quirk
+MKSH_NOPROSPECTOFWORK disable jobs, co-processes, etc. (do not use)
+MKSH_NOPWNAM skip PAM calls, for -static on glibc or Solaris
+MKSH_NO_CMDLINE_EDITING disable command line editing code entirely
+MKSH_NO_DEPRECATED_WARNING omit warning when deprecated stuff is run
+MKSH_NO_LIMITS omit ulimit code
+MKSH_NO_SIGSETJMP define if sigsetjmp is broken or not available
+MKSH_NO_SIGSUSPEND use sigprocmask+pause instead of sigsuspend
+MKSH_SMALL omit some code, optimise hard for size (slower)
+MKSH_SMALL_BUT_FAST disable some hard-for-size optim. (modern sys.)
+MKSH_S_NOVI=1 disable Vi editing mode (default if MKSH_SMALL)
+MKSH_TYPEDEF_SIG_ATOMIC_T define to e.g. 'int' if sig_atomic_t is missing
+MKSH_TYPEDEF_SSIZE_T define to e.g. 'long' if your OS has no ssize_t
+MKSH_UNEMPLOYED disable job control (but not jobs/co-processes)
+USE_REALLOC_MALLOC define as 0 to not use realloc as malloc
+
+=== generic installation instructions ===
+
+Set CC and possibly CFLAGS, CPPFLAGS, LDFLAGS, LIBS. If cross-compiling,
+also set TARGET_OS. To disable tests, set e.g. HAVE_STRLCPY=0; to enable
+them, set to a value other than 0 or 1. Ensure /bin/ed is installed. For
+MKSH_SMALL but with Vi mode, add -DMKSH_S_NOVI=0 to CPPFLAGS as well.
+
+Normally, the following command is what you want to run, then:
+$ (sh Build.sh -r && ./test.sh -f) 2>&1 | tee log
+
+Copy dot.mkshrc to /etc/skel/.mkshrc; install mksh into $prefix/bin; or
+/bin; install the manpage, if omitting the -r flag a catmanpage is made
+using $NROFF. Consider using a forward script as /etc/skel/.mkshrc like
+https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=alioth/mksh.git;a=blob;f=debian/.mkshrc
+and put dot.mkshrc as /etc/mkshrc so users need not keep up their HOME.
+
+You may also want to install the lksh binary (also as /bin/sh) built by:
+$ CPPFLAGS="$CPPFLAGS -DMKSH_BINSHPOSIX" sh Build.sh -L -r
+
+EOD
diff --git a/shells/mksh/files/FAQ2HTML.sh b/shells/mksh/files/FAQ2HTML.sh
new file mode 100644
index 00000000000..180ed98a6f5
--- /dev/null
+++ b/shells/mksh/files/FAQ2HTML.sh
@@ -0,0 +1,136 @@
+#!/bin/mksh
+rcsid='$MirOS: src/bin/mksh/FAQ2HTML.sh,v 1.1 2020/02/03 22:23:33 tg Exp $'
+#-
+# Copyright © 2020
+# mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including unâ€
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+
+set -e
+LC_ALL=C; LANGUAGE=C
+export LC_ALL; unset LANGUAGE
+nl='
+'
+srcdir=$(dirname "$0")
+
+p=--posix
+sed $p -e q </dev/null >/dev/null 2>&1 || p=
+
+v=$1
+if test -z "$v"; then
+ v=$(sed $p -n '/^#define MKSH_VERSION "\(.*\)"$/s//\1/p' "$srcdir"/sh.h)
+fi
+src_id=$(sed $p -n '/^RCSID: /s///p' "$srcdir"/mksh.faq)
+# sanity check
+case $src_id in
+(*"$nl"*)
+ echo >&2 "E: more than one RCSID in mksh.faq?"
+ exit 1 ;;
+esac
+
+sed $p \
+ -e '/^RCSID: \$/s/^.*$/----/' \
+ -e 's!@@RELPATH@@!http://www.mirbsd.org/!g' \
+ -e 's^ <span style="display:none;"> </span>' \
+ "$srcdir"/mksh.faq | tr '\n' '' | sed $p \
+ -e 'sg' \
+ -e 's----g' \
+ -e 's\([^]*\)\1g' \
+ -e 's\([^]*\)\1g' \
+ -e 's\([^]*\)*ToC: \([^]*\)Title: \([^]*\)\([^]*\)\{0,1\}</div><h2 id="\2"><a href="#\2">\3</a></h2><div>g' \
+ -e 's[^]*</div><div>g' \
+ -e 's^</div>*' \
+ -e 's$</div>' \
+ -e 's<><error><>g' \
+ -e 'sg' | tr '' '\n' >FAQ.tmp
+
+exec >FAQ.htm~
+cat <<EOF
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>mksh $v FAQ (local copy)</title>
+ <meta name="source" content="$src_id" />
+ <meta name="generator" content="$rcsid" />
+ <style type="text/css"><!--/*--><![CDATA[/*><!--*/
+ .boxhead {
+ margin-bottom:0px;
+ }
+
+ .boxtext {
+ border:4px ridge green;
+ margin:0px 24px 0px 18px;
+ padding:2px 3px 2px 3px;
+ }
+
+ .boxfoot {
+ margin-top:0px;
+ }
+
+ h2:before {
+ content:"đź”— ";
+ }
+
+ a[href^="ftp://"]:after,
+ a[href^="http://"]:after,
+ a[href^="https://"]:after,
+ a[href^="irc://"]:after,
+ a[href^="mailto:"]:after,
+ a[href^="news:"]:after,
+ a[href^="nntp://"]:after {
+ content:"⏍";
+ color:#FF0000;
+ vertical-align:super;
+ margin:0 0 0 1px;
+ }
+
+ pre {
+ /* ↑ → ↓ ↠*/
+ margin:0px 9px 0px 15px;
+ }
+
+ tt {
+ white-space:nowrap;
+ }
+ /*]]>*/--></style>
+</head><body>
+<p>Note: Links marked like <a href="irc://chat.freenode.net/!/bin/mksh">this
+ one to the mksh IRC channel</a> connect to external resources.</p>
+<p>âš  <b>Notice:</b> the website will have <a
+ href="http://www.mirbsd.org/mksh-faq.htm">the latest version of the
+ mksh FAQ</a> online.</p>
+<h1>Table of Contents</h1>
+<ul>
+EOF
+sed $p -n \
+ '/^<h2 id="\([^"]*"\)><a[^>]*\(>.*<\/a><\/\)h2>$/s//<li><a href="#\1\2li>/p' \
+ <FAQ.tmp
+cat <<EOF
+</ul>
+
+<h1>Frequently Asked Questions</h1>
+EOF
+cat FAQ.tmp - <<EOF
+<h1>Imprint</h1>
+<p>This offline HTML page for mksh $v was automatically generated
+ from the sources.</p>
+</body></html>
+EOF
+exec >/dev/null
+rm FAQ.tmp
+mv FAQ.htm~ FAQ.htm
diff --git a/shells/mksh/files/check.pl b/shells/mksh/files/check.pl
new file mode 100644
index 00000000000..c08e287e354
--- /dev/null
+++ b/shells/mksh/files/check.pl
@@ -0,0 +1,1363 @@
+# $MirOS: src/bin/mksh/check.pl,v 1.50 2019/08/01 20:05:55 tg Exp $
+# $OpenBSD: th,v 1.1 2013/12/02 20:39:44 millert Exp $
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
+# 2012, 2013, 2014, 2015, 2017
+# mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# Example test:
+# name: a-test
+# description:
+# a test to show how tests are done
+# arguments: !-x!-f!
+# stdin:
+# echo -n *
+# false
+# expected-stdout: !
+# *
+# expected-stderr:
+# + echo -n *
+# + false
+# expected-exit: 1
+# ---
+# This runs the test-program (eg, mksh) with the arguments -x and -f,
+# standard input is a file containing "echo hi*\nfalse\n". The program
+# is expected to produce "hi*" (no trailing newline) on standard output,
+# "+ echo hi*\n+false\n" on standard error, and an exit code of 1.
+#
+#
+# Format of test files:
+# - blank lines and lines starting with # are ignored
+# - a test file contains a series of tests
+# - a test is a series of tag:value pairs ended with a "---" line
+# (leading/trailing spaces are stripped from the first line of value)
+# - test tags are:
+# Tag Flag Description
+# ----- ---- -----------
+# name r The name of the test; should be unique
+# description m What test does
+# arguments M Arguments to pass to the program;
+# default is no arguments.
+# script m Value is written to a file which
+# is passed as an argument to the program
+# (after the arguments arguments)
+# stdin m Value is written to a file which is
+# used as standard-input for the program;
+# default is to use /dev/null.
+# perl-setup m Value is a perl script which is executed
+# just before the test is run. Try to
+# avoid using this...
+# perl-cleanup m Value is a perl script which is executed
+# just after the test is run. Try to
+# avoid using this...
+# env-setup M Value is a list of NAME=VALUE elements
+# which are put in the environment before
+# the test is run. If the =VALUE is
+# missing, NAME is removed from the
+# environment. Programs are run with
+# the following minimal environment:
+# HOME, LD_LIBRARY_PATH, LOCPATH,
+# LOGNAME, PATH, SHELL, UNIXMODE,
+# UNIXROOT, USER
+# (values taken from the environment of
+# the test harness).
+# CYGWIN is set to nodosfilewarning.
+# ENV is set to /nonexistant.
+# __progname is set to the -p argument.
+# __perlname is set to $^X (perlexe).
+# @utflocale@ is substituted from -U.
+# file-setup mps Used to create files, directories
+# and symlinks. First word is either
+# file, dir or symlink; second word is
+# permissions; this is followed by a
+# quoted word that is the name of the
+# file; the end-quote should be followed
+# by a newline, then the file data
+# (if any). The first word may be
+# preceded by a ! to strip the trailing
+# newline in a symlink.
+# file-result mps Used to verify a file, symlink or
+# directory is created correctly.
+# The first word is either
+# file, dir or symlink; second word is
+# expected permissions; third word
+# is user-id; fourth is group-id;
+# fifth is "exact" or "pattern"
+# indicating whether the file contents
+# which follow is to be matched exactly
+# or if it is a regular expression.
+# The fifth argument is the quoted name
+# of the file that should be created.
+# The end-quote should be followed
+# by a newline, then the file data
+# (if any). The first word may be
+# preceded by a ! to strip the trailing
+# newline in the file contents.
+# The permissions, user and group fields
+# may be * meaning accept any value.
+# time-limit Time limit - the program is sent a
+# SIGKILL N seconds. Default is no
+# limit.
+# expected-fail 'yes' if the test is expected to fail.
+# expected-exit expected exit code. Can be a number,
+# or a C expression using the variables
+# e, s and w (exit code, termination
+# signal, and status code).
+# expected-stdout m What the test should generate on stdout;
+# default is to expect no output.
+# expected-stdout-pattern m A perl pattern which matches the
+# expected output.
+# expected-stderr m What the test should generate on stderr;
+# default is to expect no output.
+# expected-stderr-pattern m A perl pattern which matches the
+# expected standard error.
+# category m Specify a comma separated list of
+# 'categories' of program that the test
+# is to be run for. A category can be
+# negated by prefixing the name with a !.
+# The idea is that some tests in a
+# test suite may apply to a particular
+# program version and shouldn't be run
+# on other versions. The category(s) of
+# the program being tested can be
+# specified on the command line.
+# 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
+# a tab. If the value part of the initial tag:value line is
+# - empty: the initial blank line is stripped.
+# - a lone !: the last newline in the value is stripped;
+# M value can be multiple lines (prefixed by a tab) and consists
+# of multiple fields, delimited by a field separator character.
+# The value must start and end with the f-s-c.
+# p tag takes parameters (used with m).
+# s tag can be used several times.
+
+# require Config only if it exists
+# pull EINTR from POSIX.pm or Errno.pm if they exist
+# otherwise just skip it
+BEGIN {
+ eval {
+ require Config;
+ import Config;
+ 1;
+ };
+ $EINTR = 0;
+ eval {
+ require POSIX;
+ $EINTR = POSIX::EINTR();
+ };
+ if ($@) {
+ eval {
+ require Errno;
+ $EINTR = Errno::EINTR();
+ } or do {
+ $EINTR = 0;
+ };
+ }
+};
+
+use Getopt::Std;
+
+$os = defined $^O ? $^O : 'unknown';
+
+($prog = $0) =~ s#.*/##;
+
+$Usage = <<EOF ;
+Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-T dir] \
+ [-t tmo] [-U lcl] 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 dir Use dir instead of /tmp to hold temporary files
+ -t t Use t as default time limit for tests (default is unlimited)
+ -U lcl Use lcl as UTF-8 locale (e.g. C.UTF-8) instead of the default
+ -v Verbose mode: print reason test failed.
+ name specifies the name of the test(s) to run; if none are
+ specified, all tests are run.
+EOF
+
+# See comment above for flag meanings
+%test_fields = (
+ 'name', 'r',
+ 'description', 'm',
+ 'arguments', 'M',
+ 'script', 'm',
+ 'stdin', 'm',
+ 'perl-setup', 'm',
+ 'perl-cleanup', 'm',
+ 'env-setup', 'M',
+ 'file-setup', 'mps',
+ 'file-result', 'mps',
+ 'time-limit', '',
+ 'expected-fail', '',
+ 'expected-exit', '',
+ 'expected-stdout', 'm',
+ 'expected-stdout-pattern', 'm',
+ 'expected-stderr', 'm',
+ 'expected-stderr-pattern', 'm',
+ 'category', 'm',
+ 'need-ctty', '',
+ 'need-pass', '',
+ );
+# Filled in by read_test()
+%internal_test_fields = (
+ ':full-name', 1, # file:name
+ ':long-name', 1, # dir/file:lineno:name
+ );
+
+# Categories of the program under test. Provide the current
+# os by default.
+%categories = (
+ "os:$os", '1'
+ );
+
+$nfailed = 0;
+$nifailed = 0;
+$nxfailed = 0;
+$npassed = 0;
+$nxpassed = 0;
+
+%known_tests = ();
+
+if (!getopts('C:Ee:Pp:s:T:t:U:v')) {
+ print STDERR $Usage;
+ exit 1;
+}
+
+die "$prog: no program specified (use -p)\n" if !defined $opt_p;
+die "$prog: no test set specified (use -s)\n" if !defined $opt_s;
+$test_prog = $opt_p;
+$verbose = defined $opt_v && $opt_v;
+$is_ebcdic = defined $opt_E && $opt_E;
+$test_set = $opt_s;
+$temp_base = $opt_T || "/tmp";
+$utflocale = $opt_U || (($os eq "hpux") ? "en_US.utf8" : "en_US.UTF-8");
+if (defined $opt_t) {
+ die "$prog: bad -t argument (should be number > 0): $opt_t\n"
+ if $opt_t !~ /^\d+$/ || $opt_t <= 0;
+ $default_time_limit = $opt_t;
+}
+$program_kludge = defined $opt_P ? $opt_P : 0;
+
+if ($is_ebcdic) {
+ $categories{'shell:ebcdic-yes'} = 1;
+ $categories{'shell:ascii-no'} = 1;
+} else {
+ $categories{'shell:ebcdic-no'} = 1;
+ $categories{'shell:ascii-yes'} = 1;
+}
+
+if (defined $opt_C) {
+ foreach $c (split(',', $opt_C)) {
+ $c =~ s/\s+//;
+ die "$prog: categories can't be negated on the command line\n"
+ if ($c =~ /^!/);
+ $categories{$c} = 1;
+ }
+}
+
+# Note which tests are to be run.
+%do_test = ();
+grep($do_test{$_} = 1, @ARGV);
+$all_tests = @ARGV == 0;
+
+# Set up a very minimal environment
+%new_env = ();
+foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
+ 'PATH', 'PERLIO', 'SHELL', 'UNIXMODE', 'UNIXROOT', 'USER')) {
+ $new_env{$env} = $ENV{$env} if defined $ENV{$env};
+}
+$new_env{'CYGWIN'} = 'nodosfilewarning';
+$new_env{'ENV'} = '/nonexistant';
+
+if (($os eq 'VMS') || ($Config{perlpath} =~ m/$Config{_exe}$/i)) {
+ $new_env{'__perlname'} = $Config{perlpath};
+} else {
+ $new_env{'__perlname'} = $Config{perlpath} . $Config{_exe};
+}
+$new_env{'__perlname'} = $^X if ($new_env{'__perlname'} eq '') and -f $^X and -x $^X;
+if ($new_env{'__perlname'} eq '') {
+ foreach $pathelt (split /:/,$ENV{'PATH'}) {
+ chomp($pathelt = `pwd`) if $pathelt eq '';
+ my $x = $pathelt . '/' . $^X;
+ next unless -f $x and -x $x;
+ $new_env{'__perlname'} = $x;
+ last;
+ }
+}
+$new_env{'__perlname'} = $^X if ($new_env{'__perlname'} eq '');
+
+if (defined $opt_e) {
+ # XXX need a way to allow many -e arguments...
+ if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) {
+ $new_env{$1} = $2 eq '' ? $ENV{$1} : $3;
+ } else {
+ die "$0: bad -e argument: $opt_e\n";
+ }
+}
+%old_env = %ENV;
+
+chop($pwd = `pwd 2>/dev/null`);
+die "$prog: couldn't get current working directory\n" if $pwd eq '';
+die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd);
+
+die "$prog: couldn't cd to $temp_base - $!\n" if !chdir($temp_base);
+die "$prog: couldn't get temporary directory base\n" unless -d '.';
+$temps = sprintf("chk%d-%d.", $$, time());
+$tempi = 0;
+until (mkdir(($tempdir = sprintf("%s%03d", $temps, $tempi)), 0700)) {
+ die "$prog: couldn't get temporary directory\n" if $tempi++ >= 999;
+}
+die "$prog: couldn't cd to $tempdir - $!\n" if !chdir($tempdir);
+chop($temp_dir = `pwd 2>/dev/null`);
+die "$prog: couldn't get temporary directory\n" if $temp_dir eq '';
+die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd);
+
+if (!$program_kludge) {
+ $test_prog = "$pwd/$test_prog" if (substr($test_prog, 0, 1) ne '/') &&
+ ($os ne 'os2' || substr($test_prog, 1, 1) ne ':');
+ die "$prog: $test_prog is not executable - bye\n"
+ if (! -x $test_prog && $os ne 'os2');
+}
+
+@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP');
+@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs;
+$child_kill_ok = 0;
+$SIG{'ALRM'} = 'catch_sigalrm';
+
+$| = 1;
+
+# Create temp files
+$temps = "${temp_dir}/rts";
+$tempi = "${temp_dir}/rti";
+$tempo = "${temp_dir}/rto";
+$tempe = "${temp_dir}/rte";
+$tempdir = "${temp_dir}/rtd";
+mkdir($tempdir, 0700) or die "$prog: couldn't mkdir $tempdir - $!\n";
+
+if (-d $test_set) {
+ $file_prefix_skip = length($test_set) + 1;
+ $ret = &process_test_dir($test_set);
+} else {
+ $file_prefix_skip = 0;
+ $ret = &process_test_file($test_set);
+}
+&cleanup_exit() if !defined $ret;
+
+$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 && !$nifailed;
+ print " ($nfailed expected)" if $nfailed && ($nxfailed || $nifailed);
+ print "\nTotal passed: $tot_passed";
+ print " ($nxpassed unexpected)" if $nxpassed;
+ print "\n";
+}
+
+&cleanup_exit('ok');
+
+sub
+cleanup_exit
+{
+ local($sig, $exitcode) = ('', 1);
+
+ if ($_[0] eq 'ok') {
+ unless ($nxfailed) {
+ $exitcode = 0;
+ } else {
+ $exitcode = 1;
+ }
+ } elsif ($_[0] ne '') {
+ $sig = $_[0];
+ }
+
+ unlink($tempi, $tempo, $tempe, $temps);
+ &scrub_dir($tempdir) if defined $tempdir;
+ rmdir($tempdir) if defined $tempdir;
+ rmdir($temp_dir) if defined $temp_dir;
+
+ if ($sig) {
+ $SIG{$sig} = 'DEFAULT';
+ kill $sig, $$;
+ return;
+ }
+ exit $exitcode;
+}
+
+sub
+catch_sigalrm
+{
+ $SIG{'ALRM'} = 'catch_sigalrm';
+ kill(9, $child_pid) if $child_kill_ok;
+ $child_killed = 1;
+}
+
+sub
+process_test_dir
+{
+ local($dir) = @_;
+ local($ret, $file);
+ local(@todo) = ();
+
+ if (!opendir(DIR, $dir)) {
+ print STDERR "$prog: can't open directory $dir - $!\n";
+ return undef;
+ }
+ while (defined ($file = readdir(DIR))) {
+ push(@todo, $file) if $file =~ /^[^.].*\.t$/;
+ }
+ closedir(DIR);
+
+ foreach $file (@todo) {
+ $file = "$dir/$file";
+ if (-d $file) {
+ $ret = &process_test_dir($file);
+ } elsif (-f _) {
+ $ret = &process_test_file($file);
+ }
+ last if !defined $ret;
+ }
+
+ return $ret;
+}
+
+sub
+process_test_file
+{
+ local($file) = @_;
+ local($ret);
+
+ if (!open(IN, $file)) {
+ print STDERR "$prog: can't open $file - $!\n";
+ return undef;
+ }
+ binmode(IN);
+ while (1) {
+ $ret = &read_test($file, IN, *test);
+ last if !defined $ret || !$ret;
+ next if !$all_tests && !$do_test{$test{'name'}};
+ next if !&category_check(*test);
+ $ret = &run_test(*test);
+ last if !defined $ret;
+ }
+ close(IN);
+
+ return $ret;
+}
+
+sub
+run_test
+{
+ local(*test) = @_;
+ local($name) = $test{':full-name'};
+
+ return undef if !&scrub_dir($tempdir);
+
+ if (defined $test{'stdin'}) {
+ return undef if !&write_file($tempi, $test{'stdin'});
+ $ifile = $tempi;
+ } else {
+ $ifile = '/dev/null';
+ }
+
+ if (defined $test{'script'}) {
+ return undef if !&write_file($temps, $test{'script'});
+ }
+
+ if (!chdir($tempdir)) {
+ print STDERR "$prog: couldn't cd to $tempdir - $!\n";
+ return undef;
+ }
+
+ if (defined $test{'file-setup'}) {
+ local($i);
+ local($type, $perm, $rest, $c, $len, $name);
+
+ for ($i = 0; $i < $test{'file-setup'}; $i++) {
+ $val = $test{"file-setup:$i"};
+
+ # format is: type perm "name"
+ ($type, $perm, $rest) =
+ split(' ', $val, 3);
+ $c = substr($rest, 0, 1);
+ $len = index($rest, $c, 1) - 1;
+ $name = substr($rest, 1, $len);
+ $rest = substr($rest, 2 + $len);
+ $perm = oct($perm) if $perm =~ /^\d+$/;
+ if ($type eq 'file') {
+ return undef if !&write_file($name, $rest);
+ if (!chmod($perm, $name)) {
+ print STDERR
+ "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n";
+ return undef;
+ }
+ } elsif ($type eq 'dir') {
+ if (!mkdir($name, $perm)) {
+ print STDERR
+ "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n";
+ return undef;
+ }
+ } elsif ($type eq 'symlink') {
+ local($oumask) = umask($perm);
+ local($ret) = symlink($rest, $name);
+ umask($oumask);
+ if (!$ret) {
+ print STDERR
+ "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n";
+ return undef;
+ }
+ }
+ }
+ }
+
+ if (defined $test{'perl-setup'}) {
+ eval $test{'perl-setup'};
+ if ($@ ne '') {
+ print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n";
+ return undef;
+ }
+ }
+
+ $pid = fork;
+ if (!defined $pid) {
+ print STDERR "$prog: can't fork - $!\n";
+ return undef;
+ }
+ if (!$pid) {
+ @SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs;
+ $SIG{'ALRM'} = 'DEFAULT';
+ if (defined $test{'env-setup'}) {
+ local($var, $val, $i);
+
+ foreach $var (split(substr($test{'env-setup'}, 0, 1),
+ $test{'env-setup'}))
+ {
+ $i = index($var, '=');
+ next if $i == 0 || $var eq '';
+ if ($i < 0) {
+ delete $new_env{$var};
+ } else {
+ $new_env{substr($var, 0, $i)} = substr($var, $i + 1);
+ }
+ }
+ }
+ if (!open(STDIN, "< $ifile")) {
+ print STDERR "$prog: couldn't open $ifile in child - $!\n";
+ kill('TERM', $$);
+ }
+ binmode(STDIN);
+ if (!open(STDOUT, "> $tempo")) {
+ print STDERR "$prog: couldn't open $tempo in child - $!\n";
+ kill('TERM', $$);
+ }
+ binmode(STDOUT);
+ if (!open(STDERR, "> $tempe")) {
+ print STDOUT "$prog: couldn't open $tempe in child - $!\n";
+ kill('TERM', $$);
+ }
+ binmode(STDERR);
+ if ($program_kludge) {
+ @argv = split(' ', $test_prog);
+ } else {
+ @argv = ($test_prog);
+ }
+ if (defined $test{'arguments'}) {
+ push(@argv,
+ split(substr($test{'arguments'}, 0, 1),
+ substr($test{'arguments'}, 1)));
+ }
+ push(@argv, $temps) if defined $test{'script'};
+
+ #XXX realpathise, use command -v/whence -p/which, or sth. like that
+ #XXX if !$program_kludge, we get by with not doing it for now tho
+ $new_env{'__progname'} = $argv[0];
+
+ # The following doesn't work with perl5... Need to do it explicitly - yuck.
+ #%ENV = %new_env;
+ foreach $k (keys(%ENV)) {
+ delete $ENV{$k};
+ }
+ $ENV{$k} = $v while ($k,$v) = each %new_env;
+
+ exec { $argv[0] } @argv;
+ print STDERR "$prog: couldn't execute $test_prog - $!\n";
+ kill('TERM', $$);
+ exit(95);
+ }
+ $child_pid = $pid;
+ $child_killed = 0;
+ $child_kill_ok = 1;
+ alarm($test{'time-limit'}) if defined $test{'time-limit'};
+ while (1) {
+ $xpid = waitpid($pid, 0);
+ $child_kill_ok = 0;
+ if ($xpid < 0) {
+ if ($EINTR) {
+ next if $! == $EINTR;
+ }
+ print STDERR "$prog: error waiting for child - $!\n";
+ return undef;
+ }
+ last;
+ }
+ $status = $?;
+ alarm(0) if defined $test{'time-limit'};
+
+ $failed = 0;
+ $why = '';
+
+ if ($child_killed) {
+ $failed = 1;
+ $why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n";
+ }
+
+ $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'});
+ return undef if !defined $ret;
+ if (!$ret) {
+ local($expl);
+
+ $failed = 1;
+ if (($status & 0xff) == 0x7f) {
+ $expl = "stopped";
+ } elsif (($status & 0xff)) {
+ $expl = "signal " . ($status & 0x7f);
+ } else {
+ $expl = "exit-code " . (($status >> 8) & 0xff);
+ }
+ $why .=
+ "\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n";
+ }
+
+ $tmp = &check_output($test{'long-name'}, $tempo, 'stdout',
+ $test{'expected-stdout'}, $test{'expected-stdout-pattern'});
+ return undef if !defined $tmp;
+ if ($tmp ne '') {
+ $failed = 1;
+ $why .= $tmp;
+ }
+
+ $tmp = &check_output($test{'long-name'}, $tempe, 'stderr',
+ $test{'expected-stderr'}, $test{'expected-stderr-pattern'});
+ return undef if !defined $tmp;
+ if ($tmp ne '') {
+ $failed = 1;
+ $why .= $tmp;
+ }
+
+ $tmp = &check_file_result(*test);
+ return undef if !defined $tmp;
+ if ($tmp ne '') {
+ $failed = 1;
+ $why .= $tmp;
+ }
+
+ if (defined $test{'perl-cleanup'}) {
+ eval $test{'perl-cleanup'};
+ if ($@ ne '') {
+ print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n";
+ return undef;
+ }
+ }
+
+ if (!chdir($pwd)) {
+ print STDERR "$prog: couldn't cd to $pwd - $!\n";
+ return undef;
+ }
+
+ if ($failed) {
+ if (!$test{'expected-fail'}) {
+ if ($test{'need-pass'}) {
+ print "FAIL $name\n";
+ $nxfailed++;
+ } else {
+ print "FAIL $name (ignored)\n";
+ $nifailed++;
+ }
+ } else {
+ print "fail $name (as expected)\n";
+ $nfailed++;
+ }
+ $why = "\tDescription"
+ . &wrap_lines($test{'description'}, " (missing)\n")
+ . $why;
+ } elsif ($test{'expected-fail'}) {
+ print "PASS $name (unexpectedly)\n";
+ $nxpassed++;
+ } else {
+ print "pass $name\n";
+ $npassed++;
+ }
+ print $why if $verbose;
+ return 0;
+}
+
+sub
+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'})) {
+ $c =~ s/\s+//;
+ if ($c =~ /^!/) {
+ $c = $';
+ return 0 if (defined $categories{$c});
+ $ok = 1;
+ } else {
+ $ok = 1 if (defined $categories{$c});
+ }
+ }
+ return $ok;
+}
+
+sub
+scrub_dir
+{
+ local($dir) = @_;
+ local(@todo) = ();
+ local($file);
+
+ if (!opendir(DIR, $dir)) {
+ print STDERR "$prog: couldn't open directory $dir - $!\n";
+ return undef;
+ }
+ while (defined ($file = readdir(DIR))) {
+ push(@todo, $file) if $file ne '.' && $file ne '..';
+ }
+ closedir(DIR);
+ foreach $file (@todo) {
+ $file = "$dir/$file";
+ if (-d $file) {
+ return undef if !&scrub_dir($file);
+ if (!rmdir($file)) {
+ print STDERR "$prog: couldn't rmdir $file - $!\n";
+ return undef;
+ }
+ } else {
+ if (!unlink($file)) {
+ print STDERR "$prog: couldn't unlink $file - $!\n";
+ return undef;
+ }
+ }
+ }
+ return 1;
+}
+
+sub
+write_file
+{
+ local($file, $str) = @_;
+
+ if (!open(TEMP, "> $file")) {
+ print STDERR "$prog: can't open $file - $!\n";
+ return undef;
+ }
+ binmode(TEMP);
+ print TEMP $str;
+ if (!close(TEMP)) {
+ print STDERR "$prog: error writing $file - $!\n";
+ return undef;
+ }
+ return 1;
+}
+
+sub
+check_output
+{
+ local($name, $file, $what, $expect, $expect_pat) = @_;
+ local($got) = '';
+ local($why) = '';
+ local($ret);
+
+ if (!open(TEMP, "< $file")) {
+ print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n";
+ return undef;
+ }
+ binmode(TEMP);
+ while (<TEMP>) {
+ $got .= $_;
+ }
+ close(TEMP);
+ return compare_output($name, $what, $expect, $expect_pat, $got);
+}
+
+sub
+compare_output
+{
+ local($name, $what, $expect, $expect_pat, $got) = @_;
+ local($why) = '';
+
+ if (defined $expect_pat) {
+ $_ = $got;
+ $ret = eval "$expect_pat";
+ if ($@ ne '') {
+ print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n";
+ return undef;
+ }
+ if (!$ret) {
+ $why = "\tunexpected $what - wanted pattern";
+ $why .= &wrap_lines($expect_pat);
+ $why .= "\tgot";
+ $why .= &wrap_lines($got);
+ }
+ } else {
+ $expect = '' if !defined $expect;
+ if ($got ne $expect) {
+ $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n";
+ $why .= "\twanted";
+ $why .= &wrap_lines($expect);
+ $why .= "\tgot";
+ $why .= &wrap_lines($got);
+ }
+ }
+ return $why;
+}
+
+sub
+wrap_lines
+{
+ local($str, $empty) = @_;
+ local($nonl) = substr($str, -1, 1) ne "\n";
+
+ return (defined $empty ? $empty : " nothing\n") if $str eq '';
+ substr($str, 0, 0) = ":\n";
+ $str =~ s/\n/\n\t\t/g;
+ if ($nonl) {
+ $str .= "\n\t[incomplete last line]\n";
+ } else {
+ chop($str);
+ chop($str);
+ }
+ return $str;
+}
+
+sub
+first_diff
+{
+ local($exp, $got) = @_;
+ local($lineno, $char) = (1, 1);
+ local($i, $exp_len, $got_len);
+ local($ce, $cg);
+
+ $exp_len = length($exp);
+ $got_len = length($got);
+ if ($exp_len != $got_len) {
+ if ($exp_len < $got_len) {
+ if (substr($got, 0, $exp_len) eq $exp) {
+ return "got too much output";
+ }
+ } elsif (substr($exp, 0, $got_len) eq $got) {
+ return "got too little output";
+ }
+ }
+ for ($i = 0; $i < $exp_len; $i++) {
+ $ce = substr($exp, $i, 1);
+ $cg = substr($got, $i, 1);
+ last if $ce ne $cg;
+ $char++;
+ if ($ce eq "\n") {
+ $lineno++;
+ $char = 1;
+ }
+ }
+ return "first difference: line $lineno, char $char (wanted " .
+ &format_char($ce) . ", got " . &format_char($cg);
+}
+
+sub
+format_char
+{
+ local($ch, $s, $q);
+
+ $ch = ord($_[0]);
+ $q = "'";
+
+ if ($is_ebcdic) {
+ if ($ch == 0x15) {
+ return $q . '\n' . $q;
+ } elsif ($ch == 0x16) {
+ return $q . '\b' . $q;
+ } elsif ($ch == 0x05) {
+ return $q . '\t' . $q;
+ } elsif ($ch < 64 || $ch == 255) {
+ return sprintf("X'%02X'", $ch);
+ }
+ return sprintf("'%c' (X'%02X')", $ch, $ch);
+ }
+
+ $s = sprintf("0x%02X (", $ch);
+ if ($ch == 10) {
+ return $s . $q . '\n' . $q . ')';
+ } elsif ($ch == 13) {
+ return $s . $q . '\r' . $q . ')';
+ } elsif ($ch == 8) {
+ return $s . $q . '\b' . $q . ')';
+ } elsif ($ch == 9) {
+ return $s . $q . '\t' . $q . ')';
+ } elsif ($ch > 127) {
+ $ch -= 128;
+ $s .= "M-";
+ }
+ if ($ch < 32) {
+ return sprintf("%s^%c)", $s, $ch + ord('@'));
+ } elsif ($ch == 127) {
+ return $s . "^?)";
+ }
+ return sprintf("%s'%c')", $s, $ch);
+}
+
+sub
+eval_exit
+{
+ local($name, $status, $expect) = @_;
+ local($expr);
+ local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f);
+
+ $e = -1000 if $status & 0xff;
+ $s = -1000 if $s == 0x7f;
+ if (!defined $expect) {
+ $expr = '$w == 0';
+ } elsif ($expect =~ /^(|-)\d+$/) {
+ $expr = "\$e == $expect";
+ } else {
+ $expr = $expect;
+ $expr =~ s/\b([wse])\b/\$$1/g;
+ $expr =~ s/\b(SIG[A-Z][A-Z0-9]*)\b/&$1/g;
+ }
+ $w = eval $expr;
+ if ($@ ne '') {
+ print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n";
+ return undef;
+ }
+ return $w;
+}
+
+sub
+read_test
+{
+ local($file, $in, *test) = @_;
+ local($field, $val, $flags, $do_chop, $need_redo, $start_lineno);
+ local(%cnt, $sfield);
+
+ %test = ();
+ %cnt = ();
+ while (<$in>) {
+ chop;
+ next if /^\s*$/;
+ next if /^ *#/;
+ last if /^\s*---\s*$/;
+ $start_lineno = $. if !defined $start_lineno;
+ if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) {
+ print STDERR "$prog:$file:$.: unrecognised line \"$_\"\n";
+ return undef;
+ }
+ ($field, $val) = ($1, $2);
+ $sfield = $field;
+ $flags = $test_fields{$field};
+ if (!defined $flags) {
+ print STDERR "$prog:$file:$.: unrecognised field \"$field\"\n";
+ return undef;
+ }
+ if ($flags =~ /s/) {
+ local($cnt) = $cnt{$field}++;
+ $test{$field} = $cnt{$field};
+ $cnt = 0 if $cnt eq '';
+ $sfield .= ":$cnt";
+ } elsif (defined $test{$field}) {
+ print STDERR "$prog:$file:$.: multiple \"$field\" fields\n";
+ return undef;
+ }
+ $do_chop = $flags !~ /m/;
+ $need_redo = 0;
+ if ($val eq '' || $val eq '!' || $flags =~ /p/) {
+ if ($flags =~ /[Mm]/) {
+ if ($flags =~ /p/) {
+ if ($val =~ /^!/) {
+ $do_chop = 1;
+ $val = $';
+ } else {
+ $do_chop = 0;
+ }
+ if ($val eq '') {
+ print STDERR
+ "$prog:$file:$.: no parameters given for field \"$field\"\n";
+ return undef;
+ }
+ } else {
+ if ($val eq '!') {
+ $do_chop = 1;
+ }
+ $val = '';
+ }
+ while (<$in>) {
+ last if !/^\t/;
+ $val .= $';
+ }
+ chop $val if $do_chop;
+ $do_chop = 1;
+ $need_redo = 1;
+
+ # Syntax check on fields that can several instances
+ # (can give useful line numbers this way)
+
+ if ($field eq 'file-setup') {
+ local($type, $perm, $rest, $c, $len, $name);
+
+ # format is: type perm "name"
+ if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) {
+ print STDERR
+ "$prog:$file:$.: bad parameter line for file-setup field\n";
+ return undef;
+ }
+ ($type, $perm, $rest) = ($1, $2, $3);
+ if ($type !~ /^(file|dir|symlink)$/) {
+ print STDERR
+ "$prog:$file:$.: bad file type for file-setup: $type\n";
+ return undef;
+ }
+ if ($perm !~ /^\d+$/) {
+ print STDERR
+ "$prog:$file:$.: bad permissions for file-setup: $type\n";
+ return undef;
+ }
+ $c = substr($rest, 0, 1);
+ if (($len = index($rest, $c, 1) - 1) <= 0) {
+ print STDERR
+ "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n";
+ return undef;
+ }
+ $name = substr($rest, 1, $len);
+ if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) {
+ # Note: this is not a security thing - just a sanity
+ # check - a test can still use symlinks to get at files
+ # outside the test directory.
+ print STDERR
+"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n";
+ return undef;
+ }
+ }
+ if ($field eq 'file-result') {
+ local($type, $perm, $uid, $gid, $matchType,
+ $rest, $c, $len, $name);
+
+ # format is: type perm uid gid matchType "name"
+ if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) {
+ print STDERR
+ "$prog:$file:$.: bad parameter line for file-result field\n";
+ return undef;
+ }
+ ($type, $perm, $uid, $gid, $matchType, $rest)
+ = ($1, $2, $3, $4, $5, $6);
+ if ($type !~ /^(file|dir|symlink)$/) {
+ print STDERR
+ "$prog:$file:$.: bad file type for file-result: $type\n";
+ return undef;
+ }
+ if ($perm !~ /^\d+$/ && $perm ne '*') {
+ print STDERR
+ "$prog:$file:$.: bad permissions for file-result: $perm\n";
+ return undef;
+ }
+ if ($uid !~ /^\d+$/ && $uid ne '*') {
+ print STDERR
+ "$prog:$file:$.: bad user-id for file-result: $uid\n";
+ return undef;
+ }
+ if ($gid !~ /^\d+$/ && $gid ne '*') {
+ print STDERR
+ "$prog:$file:$.: bad group-id for file-result: $gid\n";
+ return undef;
+ }
+ if ($matchType !~ /^(exact|pattern)$/) {
+ print STDERR
+ "$prog:$file:$.: bad match type for file-result: $matchType\n";
+ return undef;
+ }
+ $c = substr($rest, 0, 1);
+ if (($len = index($rest, $c, 1) - 1) <= 0) {
+ print STDERR
+ "$prog:$file:$.: missing end quote for file name in file-result: $rest\n";
+ return undef;
+ }
+ $name = substr($rest, 1, $len);
+ if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) {
+ # Note: this is not a security thing - just a sanity
+ # check - a test can still use symlinks to get at files
+ # outside the test directory.
+ print STDERR
+"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n";
+ return undef;
+ }
+ }
+ } elsif ($val eq '') {
+ print STDERR
+ "$prog:$file:$.: no value given for field \"$field\"\n";
+ return undef;
+ }
+ }
+ $val .= "\n" if !$do_chop;
+ $test{$sfield} = $val;
+ redo if $need_redo;
+ }
+ if ($_ eq '') {
+ if (%test) {
+ print STDERR
+ "$prog:$file:$start_lineno: end-of-file while reading test\n";
+ return undef;
+ }
+ return 0;
+ }
+
+ while (($field, $val) = each %test_fields) {
+ if ($val =~ /r/ && !defined $test{$field}) {
+ print STDERR
+ "$prog:$file:$start_lineno: required field \"$field\" missing\n";
+ return undef;
+ }
+ }
+
+ $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}";
+ $test{':long-name'} = "$file:$start_lineno:$test{'name'}";
+
+ # Syntax check on specific fields
+ if (defined $test{'expected-fail'}) {
+ if ($test{'expected-fail'} !~ /^(yes|no)$/) {
+ print STDERR
+ "$prog:$test{':long-name'}: bad value for expected-fail field\n";
+ return undef;
+ }
+ $test{'expected-fail'} = $1 eq 'yes';
+ } 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);
+
+ if (substr($test{'arguments'}, -1, 1) ne $firstc) {
+ print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n";
+ return undef;
+ }
+ }
+ if (defined $test{'env-setup'}) {
+ local($firstc) = substr($test{'env-setup'}, 0, 1);
+
+ if (substr($test{'env-setup'}, -1, 1) ne $firstc) {
+ print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n";
+ return undef;
+ }
+
+ $test{'env-setup'} =~ s/\@utflocale\@/$utflocale/g;
+ }
+ if (defined $test{'expected-exit'}) {
+ local($val) = $test{'expected-exit'};
+
+ if ($val =~ /^(|-)\d+$/) {
+ if ($val < 0 || $val > 255) {
+ print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n";
+ return undef;
+ }
+ } elsif ($val !~ /^([\s\d<>+=*%\/&|!()-]|\b[wse]\b|\bSIG[A-Z][A-Z0-9]*\b)+$/) {
+ print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n";
+ return undef;
+ }
+ } else {
+ $test{'expected-exit'} = 0;
+ }
+ if (defined $test{'expected-stdout'}
+ && defined $test{'expected-stdout-pattern'})
+ {
+ print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n";
+ return undef;
+ }
+ if (defined $test{'expected-stderr'}
+ && defined $test{'expected-stderr-pattern'})
+ {
+ print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n";
+ return undef;
+ }
+ if (defined $test{'time-limit'}) {
+ if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) {
+ print STDERR
+ "$prog:$test{':long-name'}: bad value for time-limit field\n";
+ return undef;
+ }
+ } elsif (defined $default_time_limit) {
+ $test{'time-limit'} = $default_time_limit;
+ }
+
+ if (defined $known_tests{$test{'name'}}) {
+ print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n";
+ }
+ $known_tests{$test{'name'}} = 1;
+
+ return 1;
+}
+
+sub
+tty_msg
+{
+ local($msg) = @_;
+
+ open(TTY, "> /dev/tty") || return 0;
+ print TTY $msg;
+ close(TTY);
+ return 1;
+}
+
+sub
+never_called_funcs
+{
+ return 0;
+ &tty_msg("hi\n");
+ &never_called_funcs();
+ &catch_sigalrm();
+ $old_env{'foo'} = 'bar';
+ $internal_test_fields{'foo'} = 'bar';
+}
+
+sub
+check_file_result
+{
+ local(*test) = @_;
+
+ return '' if (!defined $test{'file-result'});
+
+ local($why) = '';
+ local($i);
+ local($type, $perm, $uid, $gid, $rest, $c, $len, $name);
+ local(@stbuf);
+
+ for ($i = 0; $i < $test{'file-result'}; $i++) {
+ $val = $test{"file-result:$i"};
+
+ # format is: type perm "name"
+ ($type, $perm, $uid, $gid, $matchType, $rest) =
+ split(' ', $val, 6);
+ $c = substr($rest, 0, 1);
+ $len = index($rest, $c, 1) - 1;
+ $name = substr($rest, 1, $len);
+ $rest = substr($rest, 2 + $len);
+ $perm = oct($perm) if $perm =~ /^\d+$/;
+
+ @stbuf = lstat($name);
+ if (!@stbuf) {
+ $why .= "\texpected $type \"$name\" not created\n";
+ next;
+ }
+ if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) {
+ $why .= "\t$type \"$name\" has unexpected permissions\n";
+ $why .= sprintf("\t\texpected 0%o, found 0%o\n",
+ $perm, $stbuf[2] & 07777);
+ }
+ if ($uid ne '*' && $stbuf[4] != $uid) {
+ $why .= "\t$type \"$name\" has unexpected user-id\n";
+ $why .= sprintf("\t\texpected %d, found %d\n",
+ $uid, $stbuf[4]);
+ }
+ if ($gid ne '*' && $stbuf[5] != $gid) {
+ $why .= "\t$type \"$name\" has unexpected group-id\n";
+ $why .= sprintf("\t\texpected %d, found %d\n",
+ $gid, $stbuf[5]);
+ }
+
+ if ($type eq 'file') {
+ if (-l _ || ! -f _) {
+ $why .= "\t$type \"$name\" is not a regular file\n";
+ } else {
+ local $tmp = &check_output($test{'long-name'}, $name,
+ "$type contents in \"$name\"",
+ $matchType eq 'exact' ? $rest : undef
+ $matchType eq 'pattern' ? $rest : undef);
+ return undef if (!defined $tmp);
+ $why .= $tmp;
+ }
+ } elsif ($type eq 'dir') {
+ if ($rest !~ /^\s*$/) {
+ print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n";
+ return undef;
+ }
+ if (-l _ || ! -d _) {
+ $why .= "\t$type \"$name\" is not a directory\n";
+ }
+ } elsif ($type eq 'symlink') {
+ if (!-l _) {
+ $why .= "\t$type \"$name\" is not a symlink\n";
+ } else {
+ local $content = readlink($name);
+ if (!defined $content) {
+ print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n";
+ return undef;
+ }
+ local $tmp = &compare_output($test{'long-name'},
+ "$type contents in \"$name\"",
+ $matchType eq 'exact' ? $rest : undef
+ $matchType eq 'pattern' ? $rest : undef);
+ return undef if (!defined $tmp);
+ $why .= $tmp;
+ }
+ }
+ }
+
+ return $why;
+}
+
+sub
+HELP_MESSAGE
+{
+ print STDERR $Usage;
+ exit 0;
+}
diff --git a/shells/mksh/files/check.t b/shells/mksh/files/check.t
new file mode 100644
index 00000000000..8818822bde8
--- /dev/null
+++ b/shells/mksh/files/check.t
@@ -0,0 +1,13910 @@
+# $MirOS: src/bin/mksh/check.t,v 1.845 2020/05/16 22:19:15 tg Exp $
+# -*- mode: sh -*-
+#-
+# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+# 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+# 2019, 2020
+# mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including unâ€
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# You may also want to test IFS with the script at
+# http://www.research.att.com/~gsf/public/ifs.sh
+#
+# More testsuites at:
+# http://svnweb.freebsd.org/base/head/bin/test/tests/legacy_test.sh?view=co&content-type=text%2Fplain
+#
+# Integrated testsuites from:
+# (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
+
+expected-stdout:
+ KSH R59 2020/05/16
+description:
+ Check base version of full shell
+stdin:
+ vsn=${KSH_VERSION%%' +'*}
+ echo "${vsn#* }"
+name: KSH_VERSION
+---
+expected-stdout:
+ @(#)MIRBSD
+description:
+ Check this identifies as legacy shell
+stdin:
+ echo "${KSH_VERSION%% *}"
+name: KSH_VERSION-modern
+category: !shell:legacy-yes
+---
+expected-stdout:
+ @(#)LEGACY
+description:
+ Check this identifies as legacy shell
+stdin:
+ echo "${KSH_VERSION%% *}"
+name: KSH_VERSION-legacy
+category: !shell:legacy-no
+---
+name: KSH_VERSION-ascii
+description:
+ Check that the shell version tag does not include EBCDIC
+category: !shell:ebcdic-yes
+stdin:
+ set -o noglob
+ for x in $KSH_VERSION; do
+ [[ $x = '+EBCDIC' ]] && exit 1
+ done
+ exit 0
+---
+name: KSH_VERSION-ebcdic
+description:
+ Check that the shell version tag includes EBCDIC
+category: !shell:ebcdic-no
+stdin:
+ set -o noglob
+ for x in $KSH_VERSION; do
+ [[ $x = '+EBCDIC' ]] && exit 0
+ done
+ exit 1
+---
+name: KSH_VERSION-binmode
+description:
+ Check that the shell version tag does not include TEXTMODE
+category: !shell:textmode-yes
+stdin:
+ set -o noglob
+ for x in $KSH_VERSION; do
+ [[ $x = '+TEXTMODE' ]] && exit 1
+ done
+ exit 0
+---
+name: KSH_VERSION-textmode
+description:
+ Check that the shell version tag includes TEXTMODE
+category: !shell:textmode-no
+stdin:
+ set -o noglob
+ for x in $KSH_VERSION; do
+ [[ $x = '+TEXTMODE' ]] && exit 0
+ done
+ exit 1
+---
+name: selftest-1
+description:
+ Regression test self-testing
+stdin:
+ echo ${foo:-baz}
+expected-stdout:
+ baz
+---
+name: selftest-2
+description:
+ Regression test self-testing
+env-setup: !foo=bar!
+stdin:
+ echo ${foo:-baz}
+expected-stdout:
+ bar
+---
+name: selftest-3
+description:
+ Regression test self-testing
+env-setup: !ENV=fnord!
+stdin:
+ echo "<$ENV>"
+expected-stdout:
+ <fnord>
+---
+name: selftest-exec
+description:
+ Ensure that the test run directory (default /tmp but can be changed
+ with check.pl flag -T or test.sh $TMPDIR) is not mounted noexec, as
+ we execute scripts from the scratch directory during several tests.
+stdin:
+ print '#!'"$__progname"'\necho tf' >lq
+ chmod +x lq
+ ./lq
+expected-stdout:
+ tf
+---
+name: selftest-env
+description:
+ Just output the environment variables set (always fails)
+category: disabled
+stdin:
+ set
+---
+name: selftest-direct-builtin-call
+description:
+ Check that direct builtin calls work
+stdin:
+ ln -s "$__progname" cat || cp "$__progname" cat
+ ln -s "$__progname" echo || cp "$__progname" echo
+ ./echo -c 'echo foo' | ./cat -u
+expected-stdout:
+ -c echo foo
+---
+name: selftest-pathsep-unix
+description:
+ Check that $PATHSEP is set correctly.
+category: !os:os2
+stdin:
+ PATHSEP=.; export PATHSEP
+ "$__progname" -c 'print -r -- $PATHSEP'
+expected-stdout:
+ :
+---
+name: selftest-pathsep-dospath
+description:
+ Check that $PATHSEP is set correctly.
+category: os:os2
+stdin:
+ PATHSEP=.; export PATHSEP
+ "$__progname" -c 'print -r -- $PATHSEP'
+expected-stdout:
+ ;
+---
+name: selftest-tty-absent
+description:
+ Check that a controlling tty is not present as regress:no-ctty was used
+ (if this test fails for you DO NOT PASS regress:no-ctty and fix every
+ other test that fails: why u use it if u haz ctty?)
+category: regress:no-ctty
+env-setup: !ENV=./envf!
+file-setup: file 644 "envf"
+ PS1=X
+arguments: !-i!
+stdin:
+ echo ok
+expected-stdout:
+ ok
+expected-stderr-pattern:
+ /ksh: warning: won't have full job control\nXX/
+---
+name: selftest-tty-present
+description:
+ Check that a controlling tty is present as regress:no-ctty was not used
+need-ctty: yes
+env-setup: !ENV=./envf!
+file-setup: file 644 "envf"
+ PS1=X
+arguments: !-i!
+stdin:
+ echo ok
+expected-stdout:
+ ok
+expected-stderr: !
+ XX
+---
+name: alias-1
+description:
+ Check that recursion is detected/avoided in aliases.
+stdin:
+ alias fooBar=fooBar
+ fooBar
+ exit 0
+expected-stderr-pattern:
+ /fooBar.*not found.*/
+---
+name: alias-2
+description:
+ Check that recursion is detected/avoided in aliases.
+stdin:
+ alias fooBar=barFoo
+ alias barFoo=fooBar
+ fooBar
+ barFoo
+ exit 0
+expected-stderr-pattern:
+ /fooBar.*not found.*\n.*barFoo.*not found/
+---
+name: alias-3
+description:
+ Check that recursion is detected/avoided in aliases.
+stdin:
+ alias Echo='echo '
+ alias fooBar=barFoo
+ alias barFoo=fooBar
+ Echo fooBar
+ unalias barFoo
+ Echo fooBar
+expected-stdout:
+ fooBar
+ barFoo
+---
+name: alias-4
+description:
+ Check that alias expansion isn't done on keywords (in keyword
+ postitions).
+stdin:
+ alias Echo='echo '
+ alias while=While
+ while false; do echo hi ; done
+ Echo while
+expected-stdout:
+ While
+---
+name: alias-5
+description:
+ Check that alias expansion done after alias with trailing space.
+stdin:
+ alias Echo='echo '
+ alias foo='bar stuff '
+ alias bar='Bar1 Bar2 '
+ alias stuff='Stuff'
+ alias blah='Blah'
+ Echo foo blah
+expected-stdout:
+ Bar1 Bar2 Stuff Blah
+---
+name: alias-6
+description:
+ Check that alias expansion done after alias with trailing space.
+stdin:
+ alias Echo='echo '
+ alias foo='bar bar'
+ alias bar='Bar '
+ alias blah=Blah
+ Echo foo blah
+expected-stdout:
+ Bar Bar Blah
+---
+name: alias-7
+description:
+ Check that alias expansion done after alias with trailing space
+ after a keyword.
+stdin:
+ alias X='case '
+ alias Y=Z
+ X Y in 'Y') echo is y ;; Z) echo is z ;; esac
+expected-stdout:
+ is z
+---
+name: alias-8
+description:
+ Check that newlines in an alias don't cause the command to be lost.
+stdin:
+ alias foo='
+
+
+ echo hi
+
+
+
+ echo there
+
+
+ '
+ foo
+expected-stdout:
+ hi
+ there
+---
+name: alias-9
+description:
+ Check that recursion is detected/avoided in aliases.
+ This check fails for slow machines or Cygwin, raise
+ the time-limit clause (e.g. to 7) if this occurs.
+time-limit: 3
+stdin:
+ print '#!'"$__progname"'\necho tf' >lq
+ chmod +x lq
+ PATH=$PWD$PATHSEP$PATH
+ alias lq=lq
+ lq
+ echo = now
+ i=`lq`
+ print -r -- $i
+ echo = out
+ exit 0
+expected-stdout:
+ tf
+ = now
+ tf
+ = out
+---
+name: alias-10
+description:
+ Check that recursion is detected/avoided in aliases.
+ Regression, introduced during an old bugfix.
+stdin:
+ alias foo='print hello '
+ alias bar='foo world'
+ echo $(bar)
+expected-stdout:
+ hello world
+---
+name: alias-11
+description:
+ Check that special argument handling still applies with escaped aliases
+stdin:
+ alias local1='\typeset'
+ alias local2='\\builtin typeset'
+ function fooa {
+ local1 x=$1 y=z
+ print -r -- "$x,$y"
+ }
+ function foob {
+ local2 x=$1 y=z
+ print -r -- "$x,$y"
+ }
+ x=1 y=2; fooa 'bar - baz'
+ x=1 y=2; foob 'bar - baz'
+expected-stdout:
+ bar - baz,z
+ bar - baz,z
+---
+name: alias-12
+description:
+ Something weird from Martijn Dekker
+stdin:
+ alias echo=print
+ x() { echo a; (echo b); x=$(echo c); }
+ typeset -f x
+ alias OPEN='{' CLOSE='};'
+ { OPEN echo hi1; CLOSE }
+ var=`{ OPEN echo hi2; CLOSE }` && echo "$var"
+ var=$({ OPEN echo hi3; CLOSE }) && echo "$var"
+expected-stdout:
+ x() {
+ \print a
+ ( \print b )
+ x=$(\print c )
+ }
+ hi1
+ hi2
+ hi3
+---
+name: arith-compound
+description:
+ Check that arithmetic expressions are compound constructs
+stdin:
+ { ! (( 0$(cat >&2) )) <<<1; } <<<2
+expected-stderr:
+ 1
+---
+name: arith-lazy-1
+description:
+ Check that only one side of ternary operator is evaluated
+stdin:
+ x=i+=2
+ y=j+=2
+ typeset -i i=1 j=1
+ echo $((1 ? 20 : (x+=2)))
+ echo $i,$x
+ echo $((0 ? (y+=2) : 30))
+ echo $j,$y
+expected-stdout:
+ 20
+ 1,i+=2
+ 30
+ 1,j+=2
+---
+name: arith-lazy-2
+description:
+ Check that assignments not done on non-evaluated side of ternary
+ operator
+stdin:
+ x=i+=2
+ y=j+=2
+ typeset -i i=1 j=1
+ echo $((1 ? 20 : (x+=2)))
+ echo $i,$x
+ echo $((0 ? (y+=2) : 30))
+ echo $i,$y
+expected-stdout:
+ 20
+ 1,i+=2
+ 30
+ 1,j+=2
+---
+name: arith-lazy-3
+description:
+ Check that assignments not done on non-evaluated side of ternary
+ operator and this construct is parsed correctly (Debian #445651)
+stdin:
+ x=4
+ y=$((0 ? x=1 : 2))
+ echo = $x $y =
+expected-stdout:
+ = 4 2 =
+---
+name: arith-lazy-4
+description:
+ Check that preun/postun not done on non-evaluated side of ternary
+ operator
+stdin:
+ (( m = n = 0, 1 ? n++ : m++ ? 2 : 3 ))
+ echo "($n, $m)"
+ m=0; echo $(( 0 ? ++m : 2 )); echo $m
+ m=0; echo $(( 0 ? m++ : 2 )); echo $m
+expected-stdout:
+ (1, 0)
+ 2
+ 0
+ 2
+ 0
+---
+name: arith-lazy-5-arr-n
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((0&&b[a++],a))"
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-arr-p
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((0&&(b[a++]),a))"
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-str-n
+description: Check lazy evaluation with side effects
+stdin:
+ a=0 b=a++; ((0&&b)); echo $a
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-str-p
+description: Check lazy evaluation with side effects
+stdin:
+ a=0 b=a++; ((0&&(b))); echo $a
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-tern-l-n
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((0?b[a++]:999,a))"
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-tern-l-p
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((0?(b[a++]):999,a))"
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-tern-r-n
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((1?999:b[a++],a))"
+expected-stdout:
+ 0
+---
+name: arith-lazy-5-tern-r-p
+description: Check lazy evaluation with side effects
+stdin:
+ a=0; echo "$((1?999:(b[a++]),a))"
+expected-stdout:
+ 0
+---
+name: arith-ternary-prec-1
+description:
+ Check precedence of ternary operator vs assignment
+stdin:
+ typeset -i x=2
+ y=$((1 ? 20 : x+=2))
+expected-exit: e != 0
+expected-stderr-pattern:
+ /.*:.*1 \? 20 : x\+=2.*lvalue.*\n$/
+---
+name: arith-ternary-prec-2
+description:
+ Check precedence of ternary operator vs assignment
+stdin:
+ typeset -i x=2
+ echo $((0 ? x+=2 : 20))
+expected-stdout:
+ 20
+---
+name: arith-prec-1
+description:
+ Prove arithmetic expressions with embedded parameter
+ substitutions cannot be parsed ahead of time
+stdin:
+ a='3 + 4'
+ print 1 $((2 * a)) .
+ print 2 $((2 * $a)) .
+expected-stdout:
+ 1 14 .
+ 2 10 .
+---
+name: arith-div-assoc-1
+description:
+ Check associativity of division operator
+stdin:
+ echo $((20 / 2 / 2))
+expected-stdout:
+ 5
+---
+name: arith-div-byzero
+description:
+ Check division by zero errors out
+stdin:
+ x=$(echo $((1 / 0)))
+ echo =$?:$x.
+expected-stdout:
+ =1:.
+expected-stderr-pattern:
+ /.*divisor/
+---
+name: arith-div-intmin-by-minusone
+description:
+ Check division overflow wraps around silently
+category: int:32
+stdin:
+ echo signed:$((-2147483648 / -1))r$((-2147483648 % -1)).
+ echo unsigned:$((# -2147483648 / -1))r$((# -2147483648 % -1)).
+expected-stdout:
+ signed:-2147483648r0.
+ unsigned:0r2147483648.
+---
+name: arith-div-intmin-by-minusone-64
+description:
+ Check division overflow wraps around silently
+category: int:64
+stdin:
+ echo signed:$((-9223372036854775808 / -1))r$((-9223372036854775808 % -1)).
+ echo unsigned:$((# -9223372036854775808 / -1))r$((# -9223372036854775808 % -1)).
+expected-stdout:
+ signed:-9223372036854775808r0.
+ unsigned:0r9223372036854775808.
+---
+name: arith-assop-assoc-1
+description:
+ Check associativity of assignment-operator operator
+stdin:
+ typeset -i i=1 j=2 k=3
+ echo $((i += j += k))
+ echo $i,$j,$k
+expected-stdout:
+ 6
+ 6,5,3
+---
+name: arith-mandatory
+description:
+ Passing of this test is *mandatory* for a valid mksh executable!
+category: shell:legacy-no
+stdin:
+ typeset -i sari=0
+ typeset -Ui uari=0
+ typeset -i x=0
+ print -r -- $((x++)):$sari=$uari. #0
+ let --sari --uari
+ print -r -- $((x++)):$sari=$uari. #1
+ sari=2147483647 uari=2147483647
+ print -r -- $((x++)):$sari=$uari. #2
+ let ++sari ++uari
+ print -r -- $((x++)):$sari=$uari. #3
+ let --sari --uari
+ let 'sari *= 2' 'uari *= 2'
+ let ++sari ++uari
+ print -r -- $((x++)):$sari=$uari. #4
+ let ++sari ++uari
+ print -r -- $((x++)):$sari=$uari. #5
+ sari=-2147483648 uari=-2147483648
+ print -r -- $((x++)):$sari=$uari. #6
+ let --sari --uari
+ print -r -- $((x++)):$sari=$uari. #7
+ (( sari = -5 >> 1 ))
+ ((# uari = -5 >> 1 ))
+ print -r -- $((x++)):$sari=$uari. #8
+ (( sari = -2 ))
+ ((# uari = sari ))
+ print -r -- $((x++)):$sari=$uari. #9
+expected-stdout:
+ 0:0=0.
+ 1:-1=4294967295.
+ 2:2147483647=2147483647.
+ 3:-2147483648=2147483648.
+ 4:-1=4294967295.
+ 5:0=0.
+ 6:-2147483648=2147483648.
+ 7:2147483647=2147483647.
+ 8:-3=2147483645.
+ 9:-2=4294967294.
+---
+name: arith-unsigned-1
+description:
+ Check if unsigned arithmetics work
+category: int:32
+stdin:
+ # signed vs unsigned
+ echo x1 $((-1)) $((#-1))
+ # calculating
+ typeset -i vs
+ typeset -Ui vu
+ vs=4123456789; vu=4123456789
+ echo x2 $vs $vu
+ (( vs %= 2147483647 ))
+ (( vu %= 2147483647 ))
+ echo x3 $vs $vu
+ vs=4123456789; vu=4123456789
+ (( # vs %= 2147483647 ))
+ (( # vu %= 2147483647 ))
+ echo x4 $vs $vu
+ # make sure the calculation does not change unsigned flag
+ vs=4123456789; vu=4123456789
+ echo x5 $vs $vu
+ # short form
+ echo x6 $((# vs % 2147483647)) $((# vu % 2147483647))
+ # array refs
+ set -A va
+ va[1975973142]=right
+ va[4123456789]=wrong
+ echo x7 ${va[#4123456789%2147483647]}
+ # make sure multiple calculations don't interfere with each other
+ let '# mca = -4 % -2' ' mcb = -4 % -2'
+ echo x8 $mca $mcb
+expected-stdout:
+ x1 -1 4294967295
+ x2 -171510507 4123456789
+ x3 -171510507 4123456789
+ x4 1975973142 1975973142
+ x5 -171510507 4123456789
+ x6 1975973142 1975973142
+ x7 right
+ x8 -4 0
+---
+name: arith-limit32-1
+description:
+ Check if arithmetics are 32 bit
+category: int:32
+stdin:
+ # signed vs unsigned
+ echo x1 $((-1)) $((#-1))
+ # calculating
+ typeset -i vs
+ typeset -Ui vu
+ vs=2147483647; vu=2147483647
+ echo x2 $vs $vu
+ let vs++ vu++
+ echo x3 $vs $vu
+ vs=4294967295; vu=4294967295
+ echo x4 $vs $vu
+ let vs++ vu++
+ echo x5 $vs $vu
+ let vs++ vu++
+ echo x6 $vs $vu
+expected-stdout:
+ x1 -1 4294967295
+ x2 2147483647 2147483647
+ x3 -2147483648 2147483648
+ x4 -1 4294967295
+ x5 0 0
+ x6 1 1
+---
+name: arith-limit64-1
+description:
+ Check if arithmetics are 64 bit
+category: int:64
+stdin:
+ # signed vs unsigned
+ echo x1 $((-1)) $((#-1))
+ # calculating
+ typeset -i vs
+ typeset -Ui vu
+ vs=9223372036854775807; vu=9223372036854775807
+ echo x2 $vs $vu
+ let vs++ vu++
+ echo x3 $vs $vu
+ vs=18446744073709551615; vu=18446744073709551615
+ echo x4 $vs $vu
+ let vs++ vu++
+ echo x5 $vs $vu
+ let vs++ vu++
+ echo x6 $vs $vu
+expected-stdout:
+ x1 -1 18446744073709551615
+ x2 9223372036854775807 9223372036854775807
+ x3 -9223372036854775808 9223372036854775808
+ x4 -1 18446744073709551615
+ x5 0 0
+ x6 1 1
+---
+name: bksl-nl-ign-1
+description:
+ Check that \newline is not collapsed after #
+stdin:
+ echo hi #there \
+ echo folks
+expected-stdout:
+ hi
+ folks
+---
+name: bksl-nl-ign-2
+description:
+ Check that \newline is not collapsed inside single quotes
+stdin:
+ echo 'hi \
+ there'
+ echo folks
+expected-stdout:
+ hi \
+ there
+ folks
+---
+name: bksl-nl-ign-3
+description:
+ Check that \newline is not collapsed inside single quotes
+stdin:
+ cat << \EOF
+ hi \
+ there
+ EOF
+expected-stdout:
+ hi \
+ there
+---
+name: bksl-nl-ign-4
+description:
+ Check interaction of aliases, single quotes and here-documents
+ with backslash-newline
+ (don't know what POSIX has to say about this)
+stdin:
+ a=2
+ alias x='echo hi
+ cat << "EOF"
+ foo\
+ bar
+ some'
+ x
+ more\
+ stuff$a
+ EOF
+expected-stdout:
+ hi
+ foo\
+ bar
+ some
+ more\
+ stuff$a
+---
+name: bksl-nl-ign-5
+description:
+ Check what happens with backslash at end of input
+ (the old Bourne shell trashes them; so do we)
+stdin: !
+ echo `echo foo\\`bar
+ echo hi\
+expected-stdout:
+ foobar
+ hi
+---
+#
+# Places \newline should be collapsed
+#
+name: bksl-nl-1
+description:
+ Check that \newline is collapsed before, in the middle of, and
+ after words
+stdin:
+ \
+ echo hi\
+ There, \
+ folks
+expected-stdout:
+ hiThere, folks
+---
+name: bksl-nl-2
+description:
+ Check that \newline is collapsed in $ sequences
+ (ksh93 fails this)
+stdin:
+ a=12
+ ab=19
+ echo $\
+ a
+ echo $a\
+ b
+ echo $\
+ {a}
+ echo ${a\
+ b}
+ echo ${ab\
+ }
+expected-stdout:
+ 12
+ 19
+ 12
+ 19
+ 19
+---
+name: bksl-nl-3
+description:
+ Check that \newline is collapsed in $(..) and `...` sequences
+ (ksh93 fails this)
+stdin:
+ echo $\
+ (echo foobar1)
+ echo $(\
+ echo foobar2)
+ echo $(echo foo\
+ bar3)
+ echo $(echo foobar4\
+ )
+ echo `
+ echo stuff1`
+ echo `echo st\
+ uff2`
+expected-stdout:
+ foobar1
+ foobar2
+ foobar3
+ foobar4
+ stuff1
+ stuff2
+---
+name: bksl-nl-4
+description:
+ Check that \newline is collapsed in $((..)) sequences
+ (ksh93 fails this)
+stdin:
+ echo $\
+ ((1+2))
+ echo $(\
+ (1+2+3))
+ echo $((\
+ 1+2+3+4))
+ echo $((1+\
+ 2+3+4+5))
+ echo $((1+2+3+4+5+6)\
+ )
+expected-stdout:
+ 3
+ 6
+ 10
+ 15
+ 21
+---
+name: bksl-nl-5
+description:
+ Check that \newline is collapsed in double quoted strings
+stdin:
+ echo "\
+ hi"
+ echo "foo\
+ bar"
+ echo "folks\
+ "
+expected-stdout:
+ hi
+ foobar
+ folks
+---
+name: bksl-nl-6
+description:
+ Check that \newline is collapsed in here document delimiters
+ (ksh93 fails second part of this)
+stdin:
+ a=12
+ cat << EO\
+ F
+ a=$a
+ foo\
+ bar
+ EOF
+ cat << E_O_F
+ foo
+ E_O_\
+ F
+ echo done
+expected-stdout:
+ a=12
+ foobar
+ foo
+ done
+---
+name: bksl-nl-7
+description:
+ Check that \newline is collapsed in double-quoted here-document
+ delimiter.
+stdin:
+ a=12
+ cat << "EO\
+ F"
+ a=$a
+ foo\
+ bar
+ EOF
+ echo done
+expected-stdout:
+ a=$a
+ foo\
+ bar
+ done
+---
+name: bksl-nl-8
+description:
+ Check that \newline is collapsed in various 2+ character tokens
+ delimiter.
+ (ksh93 fails this)
+stdin:
+ echo hi &\
+ & echo there
+ echo foo |\
+ | echo bar
+ cat <\
+ < EOF
+ stuff
+ EOF
+ cat <\
+ <\
+ - EOF
+ more stuff
+ EOF
+ cat <<\
+ EOF
+ abcdef
+ EOF
+ echo hi >\
+ > /dev/null
+ echo $?
+ i=1
+ case $i in
+ (\
+ x|\
+ 1\
+ ) echo hi;\
+ ;
+ (*) echo oops
+ esac
+expected-stdout:
+ hi
+ there
+ foo
+ stuff
+ more stuff
+ abcdef
+ 0
+ hi
+---
+name: bksl-nl-9
+description:
+ Check that \ at the end of an alias is collapsed when followed
+ by a newline
+ (don't know what POSIX has to say about this)
+stdin:
+ alias x='echo hi\'
+ x
+ echo there
+expected-stdout:
+ hiecho there
+---
+name: bksl-nl-10
+description:
+ Check that \newline in a keyword is collapsed
+stdin:
+ i\
+ f true; then\
+ echo pass; el\
+ se echo fail; fi
+expected-stdout:
+ pass
+---
+#
+# Places \newline should be collapsed (ksh extensions)
+#
+name: bksl-nl-ksh-1
+description:
+ Check that \newline is collapsed in extended globbing
+ (ksh93 fails this)
+stdin:
+ xxx=foo
+ case $xxx in
+ (f*\
+ (\
+ o\
+ )\
+ ) echo ok ;;
+ *) echo bad
+ esac
+expected-stdout:
+ ok
+---
+name: bksl-nl-ksh-2
+description:
+ Check that \newline is collapsed in ((...)) expressions
+ (ksh93 fails this)
+stdin:
+ i=1
+ (\
+ (\
+ i=i+2\
+ )\
+ )
+ echo $i
+expected-stdout:
+ 3
+---
+name: break-1
+description:
+ See if break breaks out of loops
+stdin:
+ for i in a b c; do echo $i; break; echo bad-$i; done
+ echo end-1
+ for i in a b c; do echo $i; break 1; echo bad-$i; done
+ echo end-2
+ for i in a b c; do
+ for j in x y z; do
+ echo $i:$j
+ break
+ echo bad-$i
+ done
+ echo end-$i
+ done
+ echo end-3
+ for i in a b c; do echo $i; eval break; echo bad-$i; done
+ echo end-4
+expected-stdout:
+ a
+ end-1
+ a
+ end-2
+ a:x
+ end-a
+ b:x
+ end-b
+ c:x
+ end-c
+ end-3
+ a
+ end-4
+---
+name: break-2
+description:
+ See if break breaks out of nested loops
+stdin:
+ for i in a b c; do
+ for j in x y z; do
+ echo $i:$j
+ break 2
+ echo bad-$i
+ done
+ echo end-$i
+ done
+ echo end
+expected-stdout:
+ a:x
+ end
+---
+name: break-3
+description:
+ What if break used outside of any loops
+ (ksh88,ksh93 don't print error messages here)
+stdin:
+ break
+expected-stderr-pattern:
+ /.*break.*/
+---
+name: break-4
+description:
+ What if break N used when only N-1 loops
+ (ksh88,ksh93 don't print error messages here)
+stdin:
+ for i in a b c; do echo $i; break 2; echo bad-$i; done
+ echo end
+expected-stdout:
+ a
+ end
+expected-stderr-pattern:
+ /.*break.*/
+---
+name: break-5
+description:
+ Error if break argument isn't a number
+stdin:
+ for i in a b c; do echo $i; break abc; echo more-$i; done
+ echo end
+expected-stdout:
+ a
+expected-exit: e != 0
+expected-stderr-pattern:
+ /.*break.*/
+---
+name: continue-1
+description:
+ See if continue continues loops
+stdin:
+ for i in a b c; do echo $i; continue; echo bad-$i ; done
+ echo end-1
+ for i in a b c; do echo $i; continue 1; echo bad-$i; done
+ echo end-2
+ for i in a b c; do
+ for j in x y z; do
+ echo $i:$j
+ continue
+ echo bad-$i-$j
+ done
+ echo end-$i
+ done
+ echo end-3
+ for i in a b c; do echo $i; eval continue; echo bad-$i ; done
+ echo end-4
+expected-stdout:
+ a
+ b
+ c
+ end-1
+ a
+ b
+ c
+ end-2
+ a:x
+ a:y
+ a:z
+ end-a
+ b:x
+ b:y
+ b:z
+ end-b
+ c:x
+ c:y
+ c:z
+ end-c
+ end-3
+ a
+ b
+ c
+ end-4
+---
+name: continue-2
+description:
+ See if continue breaks out of nested loops
+stdin:
+ for i in a b c; do
+ for j in x y z; do
+ echo $i:$j
+ continue 2
+ echo bad-$i-$j
+ done
+ echo end-$i
+ done
+ echo end
+expected-stdout:
+ a:x
+ b:x
+ c:x
+ end
+---
+name: continue-3
+description:
+ What if continue used outside of any loops
+ (ksh88,ksh93 don't print error messages here)
+stdin:
+ continue
+expected-stderr-pattern:
+ /.*continue.*/
+---
+name: continue-4
+description:
+ What if continue N used when only N-1 loops
+ (ksh88,ksh93 don't print error messages here)
+stdin:
+ for i in a b c; do echo $i; continue 2; echo bad-$i; done
+ echo end
+expected-stdout:
+ a
+ b
+ c
+ end
+expected-stderr-pattern:
+ /.*continue.*/
+---
+name: continue-5
+description:
+ Error if continue argument isn't a number
+stdin:
+ for i in a b c; do echo $i; continue abc; echo more-$i; done
+ echo end
+expected-stdout:
+ a
+expected-exit: e != 0
+expected-stderr-pattern:
+ /.*continue.*/
+---
+name: cd-history
+description:
+ Test someone's CD history package (uses arrays)
+stdin:
+ # go to known place before doing anything
+ cd /
+
+ alias cd=_cd
+ function _cd
+ {
+ typeset -i cdlen i
+ typeset t
+
+ if [ $# -eq 0 ]
+ then
+ set -- $HOME
+ fi
+
+ if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists
+ then
+ typeset CDHIST
+ i=-1
+ while read -r t # read directory history file
+ do
+ CDHIST[i=i+1]=$t
+ done <$CDHISTFILE
+ fi
+
+ if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ]
+ then
+ _cdins # insert $PWD into cd history
+ fi
+
+ cdlen=${#CDHIST[*]} # number of elements in history
+
+ case "$@" in
+ -) # cd to new dir
+ if [ "$OLDPWD" = "" ] && ((cdlen>1))
+ then
+ 'print' ${CDHIST[1]}
+ 'cd' ${CDHIST[1]}
+ _pwd
+ else
+ 'cd' $@
+ _pwd
+ fi
+ ;;
+ -l) # print directory list
+ typeset -R3 num
+ ((i=cdlen))
+ while (((i=i-1)>=0))
+ do
+ num=$i
+ 'print' "$num ${CDHIST[i]}"
+ done
+ return
+ ;;
+ -[0-9]|-[0-9][0-9]) # cd to dir in list
+ if (((i=${1#-})<cdlen))
+ then
+ 'print' ${CDHIST[i]}
+ 'cd' ${CDHIST[i]}
+ _pwd
+ else
+ 'cd' $@
+ _pwd
+ fi
+ ;;
+ -*) # cd to matched dir in list
+ t=${1#-}
+ i=1
+ while ((i<cdlen))
+ do
+ case ${CDHIST[i]} in
+ *$t*)
+ 'print' ${CDHIST[i]}
+ 'cd' ${CDHIST[i]}
+ _pwd
+ break
+ ;;
+ esac
+ ((i=i+1))
+ done
+ if ((i>=cdlen))
+ then
+ 'cd' $@
+ _pwd
+ fi
+ ;;
+ *) # cd to new dir
+ 'cd' $@
+ _pwd
+ ;;
+ esac
+
+ _cdins # insert $PWD into cd history
+
+ if [ "$CDHISTFILE" ]
+ then
+ cdlen=${#CDHIST[*]} # number of elements in history
+
+ i=0
+ while ((i<cdlen))
+ do
+ 'print' -r ${CDHIST[i]} # update directory history
+ ((i=i+1))
+ done >$CDHISTFILE
+ fi
+ }
+
+ function _cdins # insert $PWD into cd history
+ { # meant to be called only by _cd
+ typeset -i i
+
+ ((i=0))
+ while ((i<${#CDHIST[*]})) # see if dir is already in list
+ do
+ if [ "${CDHIST[$i]}" = "$PWD" ]
+ then
+ break
+ fi
+ ((i=i+1))
+ done
+
+ if ((i>22)) # limit max size of list
+ then
+ i=22
+ fi
+
+ while (((i=i-1)>=0)) # bump old dirs in list
+ do
+ CDHIST[i+1]=${CDHIST[i]}
+ done
+
+ CDHIST[0]=$PWD # insert new directory in list
+ }
+
+
+ function _pwd
+ {
+ if [ -n "$ECD" ]
+ then
+ pwd 1>&6
+ fi
+ }
+ # Start of test
+ cd /tmp
+ cd /bin
+ cd /etc
+ cd -
+ cd -2
+ cd -l
+expected-stdout:
+ /bin
+ /tmp
+ 3 /
+ 2 /etc
+ 1 /bin
+ 0 /tmp
+---
+name: cd-pe
+description:
+ Check package for cd -Pe
+need-pass: no
+# the mv command fails on Cygwin and z/OS
+# Hurd aborts the testsuite (permission denied)
+# QNX does not find subdir to cd into
+category: !os:cygwin,!os:gnu,!os:midipix,!os:msys,!os:nto,!os:os390,!nosymlink
+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 noread renamed 2>/dev/null
+ 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
+env-setup: !ENV=./foo!
+file-setup: file 644 "foo"
+ XXX=_
+ PS1=X
+ false && echo hmmm
+need-ctty: yes
+arguments: !-i!
+stdin:
+ echo hi${XXX}there
+expected-stdout:
+ hi_there
+expected-stderr: !
+ XX
+---
+name: expand-ugly
+description:
+ Check that weird ${foo+bar} constructs are parsed correctly
+stdin:
+ print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn
+ print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "<$x> "; done' >pfs
+ chmod +x pfn pfs
+ (echo 1 ${IFS+'}'z}) 2>/dev/null || echo failed in 1
+ (echo 2 "${IFS+'}'z}") 2>/dev/null || echo failed in 2
+ (echo 3 "foo ${IFS+'bar} baz") 2>/dev/null || echo failed in 3
+ (echo -n '4 '; ./pfn "foo ${IFS+"b c"} baz") 2>/dev/null || echo failed in 4
+ (echo -n '5 '; ./pfn "foo ${IFS+b c} baz") 2>/dev/null || echo failed in 5
+ (echo 6 ${IFS+"}"z}) 2>/dev/null || echo failed in 6
+ (echo 7 "${IFS+"}"z}") 2>/dev/null || echo failed in 7
+ (echo 8 "${IFS+\"}\"z}") 2>/dev/null || echo failed in 8
+ (echo 9 "${IFS+\"\}\"z}") 2>/dev/null || echo failed in 9
+ (echo 10 foo ${IFS+'bar} baz'}) 2>/dev/null || echo failed in 10
+ (echo 11 "$(echo "${IFS+'}'z}")") 2>/dev/null || echo failed in 11
+ (echo 12 "$(echo ${IFS+'}'z})") 2>/dev/null || echo failed in 12
+ (echo 13 ${IFS+\}z}) 2>/dev/null || echo failed in 13
+ (echo 14 "${IFS+\}z}") 2>/dev/null || echo failed in 14
+ u=x; (echo -n '15 '; ./pfs "foo ${IFS+a"b$u{ {"{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz; echo .) 2>/dev/null || echo failed in 15
+ l=t; (echo 16 ${IFS+h`echo -n i ${IFS+$l}h`ere}) 2>/dev/null || echo failed in 16
+ l=t; (echo 17 ${IFS+h$(echo -n i ${IFS+$l}h)ere}) 2>/dev/null || echo failed in 17
+ l=t; (echo 18 "${IFS+h`echo -n i ${IFS+$l}h`ere}") 2>/dev/null || echo failed in 18
+ l=t; (echo 19 "${IFS+h$(echo -n i ${IFS+$l}h)ere}") 2>/dev/null || echo failed in 19
+ l=t; (echo 20 ${IFS+h`echo -n i "${IFS+$l}"h`ere}) 2>/dev/null || echo failed in 20
+ l=t; (echo 21 ${IFS+h$(echo -n i "${IFS+$l}"h)ere}) 2>/dev/null || echo failed in 21
+ l=t; (echo 22 "${IFS+h`echo -n i "${IFS+$l}"h`ere}") 2>/dev/null || echo failed in 22
+ l=t; (echo 23 "${IFS+h$(echo -n i "${IFS+$l}"h)ere}") 2>/dev/null || echo failed in 23
+ key=value; (echo -n '24 '; ./pfn "${IFS+'$key'}") 2>/dev/null || echo failed in 24
+ key=value; (echo -n '25 '; ./pfn "${IFS+"'$key'"}") 2>/dev/null || echo failed in 25 # ksh93: “'$key'”
+ key=value; (echo -n '26 '; ./pfn ${IFS+'$key'}) 2>/dev/null || echo failed in 26
+ key=value; (echo -n '27 '; ./pfn ${IFS+"'$key'"}) 2>/dev/null || echo failed in 27
+ (echo -n '28 '; ./pfn "${IFS+"'"x ~ x'}'x"'}"x}" #') 2>/dev/null || echo failed in 28
+ u=x; (echo -n '29 '; ./pfs foo ${IFS+a"b$u{ {"{ {\}b} c ${IFS+d{}} bar ${IFS-e{}} baz; echo .) 2>/dev/null || echo failed in 29
+ (echo -n '30 '; ./pfs ${IFS+foo 'b\
+ ar' baz}; echo .) 2>/dev/null || (echo failed in 30; echo failed in 31)
+ (echo -n '32 '; ./pfs ${IFS+foo "b\
+ ar" baz}; echo .) 2>/dev/null || echo failed in 32
+ (echo -n '33 '; ./pfs "${IFS+foo 'b\
+ ar' baz}"; echo .) 2>/dev/null || echo failed in 33
+ (echo -n '34 '; ./pfs "${IFS+foo "b\
+ ar" baz}"; echo .) 2>/dev/null || echo failed in 34
+ (echo -n '35 '; ./pfs ${v=a\ b} x ${v=c\ d}; echo .) 2>/dev/null || echo failed in 35
+ (echo -n '36 '; ./pfs "${v=a\ b}" x "${v=c\ d}"; echo .) 2>/dev/null || echo failed in 36
+ (echo -n '37 '; ./pfs ${v-a\ b} x ${v-c\ d}; echo .) 2>/dev/null || echo failed in 37
+ (echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>/dev/null || echo failed in 38
+ foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>/dev/null || echo failed in 39
+ foo="a b c"; (echo -n '40 '; ./pfs "${foo#a}"; echo .) 2>/dev/null || echo failed in 40
+ (foo() { return 100; }; foo; echo 41 ${#+${#:+${#?}}\ \}\}\}}) 2>/dev/null || echo failed in 41
+expected-stdout:
+ 1 }z
+ 2 ''z}
+ 3 foo 'bar baz
+ 4 foo b c baz
+ 5 foo b c baz
+ 6 }z
+ 7 }z
+ 8 ""z}
+ 9 "}"z
+ 10 foo bar} baz
+ 11 ''z}
+ 12 }z
+ 13 }z
+ 14 }z
+ 15 <foo abx{ {{{}b c d{} bar> <}> <baz> .
+ 16 hi there
+ 17 hi there
+ 18 hi there
+ 19 hi there
+ 20 hi there
+ 21 hi there
+ 22 hi there
+ 23 hi there
+ 24 'value'
+ 25 'value'
+ 26 $key
+ 27 'value'
+ 28 'x ~ x''x}"x}" #
+ 29 <foo> <abx{ {{> <{}b> <c> <d{}> <bar> <}> <baz> .
+ 30 <foo> <b\
+ ar> <baz> .
+ 32 <foo> <bar> <baz> .
+ 33 <foo 'bar' baz> .
+ 34 <foo bar baz> .
+ 35 <a> <b> <x> <a> <b> .
+ 36 <a\ b> <x> <a\ b> .
+ 37 <a b> <x> <c d> .
+ 38 xay / x'a'y .
+ 39 x' / x' .
+ 40 < b c> .
+ 41 3 }}}
+---
+name: expand-unglob-dblq
+description:
+ Check that regular "${foo+bar}" constructs are parsed correctly
+stdin:
+ u=x
+ tl_norm() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo "$1 plus norm foo ${v+'bar'} baz")
+ (echo "$1 dash norm foo ${v-'bar'} baz")
+ (echo "$1 eqal norm foo ${v='bar'} baz")
+ (echo "$1 qstn norm foo ${v?'bar'} baz") 2>/dev/null || \
+ echo "$1 qstn norm -> error"
+ (echo "$1 PLUS norm foo ${v:+'bar'} baz")
+ (echo "$1 DASH norm foo ${v:-'bar'} baz")
+ (echo "$1 EQAL norm foo ${v:='bar'} baz")
+ (echo "$1 QSTN norm foo ${v:?'bar'} baz") 2>/dev/null || \
+ echo "$1 QSTN norm -> error"
+ }
+ tl_paren() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo "$1 plus parn foo ${v+(bar)} baz")
+ (echo "$1 dash parn foo ${v-(bar)} baz")
+ (echo "$1 eqal parn foo ${v=(bar)} baz")
+ (echo "$1 qstn parn foo ${v?(bar)} baz") 2>/dev/null || \
+ echo "$1 qstn parn -> error"
+ (echo "$1 PLUS parn foo ${v:+(bar)} baz")
+ (echo "$1 DASH parn foo ${v:-(bar)} baz")
+ (echo "$1 EQAL parn foo ${v:=(bar)} baz")
+ (echo "$1 QSTN parn foo ${v:?(bar)} baz") 2>/dev/null || \
+ echo "$1 QSTN parn -> error"
+ }
+ tl_brace() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo "$1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz")
+ (echo "$1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz")
+ (echo "$1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz")
+ (echo "$1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz") 2>/dev/null || \
+ echo "$1 qstn brac -> error"
+ (echo "$1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz")
+ (echo "$1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz")
+ (echo "$1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz")
+ (echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>/dev/null || \
+ echo "$1 QSTN brac -> error"
+ }
+ : '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}'
+ tl_norm 1 -
+ tl_norm 2 ''
+ tl_norm 3 x
+ tl_paren 4 -
+ tl_paren 5 ''
+ tl_paren 6 x
+ tl_brace 7 -
+ tl_brace 8 ''
+ tl_brace 9 x
+expected-stdout:
+ 1 plus norm foo baz
+ 1 dash norm foo 'bar' baz
+ 1 eqal norm foo 'bar' baz
+ 1 qstn norm -> error
+ 1 PLUS norm foo baz
+ 1 DASH norm foo 'bar' baz
+ 1 EQAL norm foo 'bar' baz
+ 1 QSTN norm -> error
+ 2 plus norm foo 'bar' baz
+ 2 dash norm foo baz
+ 2 eqal norm foo baz
+ 2 qstn norm foo baz
+ 2 PLUS norm foo baz
+ 2 DASH norm foo 'bar' baz
+ 2 EQAL norm foo 'bar' baz
+ 2 QSTN norm -> error
+ 3 plus norm foo 'bar' baz
+ 3 dash norm foo x baz
+ 3 eqal norm foo x baz
+ 3 qstn norm foo x baz
+ 3 PLUS norm foo 'bar' baz
+ 3 DASH norm foo x baz
+ 3 EQAL norm foo x baz
+ 3 QSTN norm foo x baz
+ 4 plus parn foo baz
+ 4 dash parn foo (bar) baz
+ 4 eqal parn foo (bar) baz
+ 4 qstn parn -> error
+ 4 PLUS parn foo baz
+ 4 DASH parn foo (bar) baz
+ 4 EQAL parn foo (bar) baz
+ 4 QSTN parn -> error
+ 5 plus parn foo (bar) baz
+ 5 dash parn foo baz
+ 5 eqal parn foo baz
+ 5 qstn parn foo baz
+ 5 PLUS parn foo baz
+ 5 DASH parn foo (bar) baz
+ 5 EQAL parn foo (bar) baz
+ 5 QSTN parn -> error
+ 6 plus parn foo (bar) baz
+ 6 dash parn foo x baz
+ 6 eqal parn foo x baz
+ 6 qstn parn foo x baz
+ 6 PLUS parn foo (bar) baz
+ 6 DASH parn foo x baz
+ 6 EQAL parn foo x baz
+ 6 QSTN parn foo x baz
+ 7 plus brac foo c } baz
+ 7 dash brac foo ax{{{}b c d{} baz
+ 7 eqal brac foo ax{{{}b c ax{{{}b} baz
+ 7 qstn brac -> error
+ 7 PLUS brac foo c } baz
+ 7 DASH brac foo ax{{{}b c d{} baz
+ 7 EQAL brac foo ax{{{}b c ax{{{}b} baz
+ 7 QSTN brac -> error
+ 8 plus brac foo ax{{{}b c d{} baz
+ 8 dash brac foo c } baz
+ 8 eqal brac foo c } baz
+ 8 qstn brac foo c } baz
+ 8 PLUS brac foo c } baz
+ 8 DASH brac foo ax{{{}b c d{} baz
+ 8 EQAL brac foo ax{{{}b c ax{{{}b} baz
+ 8 QSTN brac -> error
+ 9 plus brac foo ax{{{}b c d{} baz
+ 9 dash brac foo x c x} baz
+ 9 eqal brac foo x c x} baz
+ 9 qstn brac foo x c x} baz
+ 9 PLUS brac foo ax{{{}b c d{} baz
+ 9 DASH brac foo x c x} baz
+ 9 EQAL brac foo x c x} baz
+ 9 QSTN brac foo x c x} baz
+---
+name: expand-unglob-unq
+description:
+ Check that regular ${foo+bar} constructs are parsed correctly
+stdin:
+ u=x
+ tl_norm() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo $1 plus norm foo ${v+'bar'} baz)
+ (echo $1 dash norm foo ${v-'bar'} baz)
+ (echo $1 eqal norm foo ${v='bar'} baz)
+ (echo $1 qstn norm foo ${v?'bar'} baz) 2>/dev/null || \
+ echo "$1 qstn norm -> error"
+ (echo $1 PLUS norm foo ${v:+'bar'} baz)
+ (echo $1 DASH norm foo ${v:-'bar'} baz)
+ (echo $1 EQAL norm foo ${v:='bar'} baz)
+ (echo $1 QSTN norm foo ${v:?'bar'} baz) 2>/dev/null || \
+ echo "$1 QSTN norm -> error"
+ }
+ tl_paren() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo $1 plus parn foo ${v+\(bar')'} baz)
+ (echo $1 dash parn foo ${v-\(bar')'} baz)
+ (echo $1 eqal parn foo ${v=\(bar')'} baz)
+ (echo $1 qstn parn foo ${v?\(bar')'} baz) 2>/dev/null || \
+ echo "$1 qstn parn -> error"
+ (echo $1 PLUS parn foo ${v:+\(bar')'} baz)
+ (echo $1 DASH parn foo ${v:-\(bar')'} baz)
+ (echo $1 EQAL parn foo ${v:=\(bar')'} baz)
+ (echo $1 QSTN parn foo ${v:?\(bar')'} baz) 2>/dev/null || \
+ echo "$1 QSTN parn -> error"
+ }
+ tl_brace() {
+ v=$2
+ test x"$v" = x"-" && unset v
+ (echo $1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz)
+ (echo $1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz)
+ (echo $1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz)
+ (echo $1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz) 2>/dev/null || \
+ echo "$1 qstn brac -> error"
+ (echo $1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz)
+ (echo $1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz)
+ (echo $1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz)
+ (echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>/dev/null || \
+ echo "$1 QSTN brac -> error"
+ }
+ : '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}'
+ tl_norm 1 -
+ tl_norm 2 ''
+ tl_norm 3 x
+ tl_paren 4 -
+ tl_paren 5 ''
+ tl_paren 6 x
+ tl_brace 7 -
+ tl_brace 8 ''
+ tl_brace 9 x
+expected-stdout:
+ 1 plus norm foo baz
+ 1 dash norm foo bar baz
+ 1 eqal norm foo bar baz
+ 1 qstn norm -> error
+ 1 PLUS norm foo baz
+ 1 DASH norm foo bar baz
+ 1 EQAL norm foo bar baz
+ 1 QSTN norm -> error
+ 2 plus norm foo bar baz
+ 2 dash norm foo baz
+ 2 eqal norm foo baz
+ 2 qstn norm foo baz
+ 2 PLUS norm foo baz
+ 2 DASH norm foo bar baz
+ 2 EQAL norm foo bar baz
+ 2 QSTN norm -> error
+ 3 plus norm foo bar baz
+ 3 dash norm foo x baz
+ 3 eqal norm foo x baz
+ 3 qstn norm foo x baz
+ 3 PLUS norm foo bar baz
+ 3 DASH norm foo x baz
+ 3 EQAL norm foo x baz
+ 3 QSTN norm foo x baz
+ 4 plus parn foo baz
+ 4 dash parn foo (bar) baz
+ 4 eqal parn foo (bar) baz
+ 4 qstn parn -> error
+ 4 PLUS parn foo baz
+ 4 DASH parn foo (bar) baz
+ 4 EQAL parn foo (bar) baz
+ 4 QSTN parn -> error
+ 5 plus parn foo (bar) baz
+ 5 dash parn foo baz
+ 5 eqal parn foo baz
+ 5 qstn parn foo baz
+ 5 PLUS parn foo baz
+ 5 DASH parn foo (bar) baz
+ 5 EQAL parn foo (bar) baz
+ 5 QSTN parn -> error
+ 6 plus parn foo (bar) baz
+ 6 dash parn foo x baz
+ 6 eqal parn foo x baz
+ 6 qstn parn foo x baz
+ 6 PLUS parn foo (bar) baz
+ 6 DASH parn foo x baz
+ 6 EQAL parn foo x baz
+ 6 QSTN parn foo x baz
+ 7 plus brac foo c } baz
+ 7 dash brac foo ax{{{}b c d{} baz
+ 7 eqal brac foo ax{{{}b c ax{{{}b} baz
+ 7 qstn brac -> error
+ 7 PLUS brac foo c } baz
+ 7 DASH brac foo ax{{{}b c d{} baz
+ 7 EQAL brac foo ax{{{}b c ax{{{}b} baz
+ 7 QSTN brac -> error
+ 8 plus brac foo ax{{{}b c d{} baz
+ 8 dash brac foo c } baz
+ 8 eqal brac foo c } baz
+ 8 qstn brac foo c } baz
+ 8 PLUS brac foo c } baz
+ 8 DASH brac foo ax{{{}b c d{} baz
+ 8 EQAL brac foo ax{{{}b c ax{{{}b} baz
+ 8 QSTN brac -> error
+ 9 plus brac foo ax{{{}b c d{} baz
+ 9 dash brac foo x c x} baz
+ 9 eqal brac foo x c x} baz
+ 9 qstn brac foo x c x} baz
+ 9 PLUS brac foo ax{{{}b c d{} baz
+ 9 DASH brac foo x c x} baz
+ 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 cases of trim expansion vs. $# vs. ${#var} 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}
+ (exit 0)
+ echo $? = ${#?} .
+ (exit 111)
+ echo $? = ${#?} .
+expected-stdout:
+ 11
+ 2
+ 1
+ 2
+ 0 = 1 .
+ 111 = 3 .
+---
+name: expand-weird-2
+description:
+ Check more substitution and extension corner cases
+stdin:
+ :& set -C; pid=$$; sub=$!; flg=$-; set -- i; exec 3>x.tmp
+ #echo "D: !=$! #=$# \$=$$ -=$- ?=$?"
+ echo >&3 3 = s^${!-word} , ${#-word} , p^${$-word} , f^${--word} , ${?-word} .
+ echo >&3 4 = ${!+word} , ${#+word} , ${$+word} , ${-+word} , ${?+word} .
+ echo >&3 5 = s^${!=word} , ${#=word} , p^${$=word} , f^${-=word} , ${?=word} .
+ echo >&3 6 = s^${!?word} , ${#?word} , p^${$?word} , f^${-?word} , ${??word} .
+ echo >&3 7 = sl^${#!} , ${##} , pl^${#$} , fl^${#-} , ${#?} .
+ echo >&3 8 = sw^${%!} , ${%#} , pw^${%$} , fw^${%-} , ${%?} .
+ echo >&3 9 = ${!!} , s^${!#} , ${!$} , s^${!-} , s^${!?} .
+ echo >&3 10 = s^${!#pattern} , ${##pattern} , p^${$#pattern} , f^${-#pattern} , ${?#pattern} .
+ echo >&3 11 = s^${!%pattern} , ${#%pattern} , p^${$%pattern} , f^${-%pattern} , ${?%pattern} .
+ echo >&3 12 = $# : ${##} , ${##1} .
+ set --
+ echo >&3 14 = $# : ${##} , ${##1} .
+ set -- 1 2 3 4 5
+ echo >&3 16 = $# : ${##} , ${##1} .
+ set -- 1 2 3 4 5 6 7 8 9 a b c d e
+ echo >&3 18 = $# : ${##} , ${##1} .
+ exec 3>&-
+ <x.tmp sed \
+ -e "s/ pl^${#pid} / PID /g" -e "s/ sl^${#sub} / SUB /g" -e "s/ fl^${#flg} / FLG /g" \
+ -e "s/ pw^${%pid} / PID /g" -e "s/ sw^${%sub} / SUB /g" -e "s/ fw^${%flg} / FLG /g" \
+ -e "s/ p^$pid / PID /g" -e "s/ s^$sub / SUB /g" -e "s/ f^$flg / FLG /g"
+expected-stdout:
+ 3 = SUB , 1 , PID , FLG , 0 .
+ 4 = word , word , word , word , word .
+ 5 = SUB , 1 , PID , FLG , 0 .
+ 6 = SUB , 1 , PID , FLG , 0 .
+ 7 = SUB , 1 , PID , FLG , 1 .
+ 8 = SUB , 1 , PID , FLG , 1 .
+ 9 = ! , SUB , $ , SUB , SUB .
+ 10 = SUB , 1 , PID , FLG , 0 .
+ 11 = SUB , 1 , PID , FLG , 0 .
+ 12 = 1 : 1 , .
+ 14 = 0 : 1 , 0 .
+ 16 = 5 : 1 , 5 .
+ 18 = 14 : 2 , 4 .
+---
+name: expand-weird-3
+description:
+ Check that trimming works with positional parameters (Debian #48453)
+stdin:
+ A=9999-02
+ B=9999
+ echo 1=${A#$B?}.
+ set -- $A $B
+ echo 2=${1#$2?}.
+expected-stdout:
+ 1=02.
+ 2=02.
+---
+name: expand-weird-4
+description:
+ Check that tilde expansion is enabled in ${x#~}
+ and cases that are modelled after it (${x/~/~})
+stdin:
+ HOME=/etc
+ a="~/x"
+ echo "<${a#~}> <${a#\~}> <${b:-~}> <${b:-\~}> <${c:=~}><$c> <${a/~}> <${a/x/~}> <${a/x/\~}>"
+expected-stdout:
+ <~/x> </x> <~> <\~> <~><~> <~/x> <~//etc> <~/~>
+---
+name: expand-bang-1
+description:
+ Check corner case of ${!?} with ! being var vs. op
+stdin:
+ echo ${!?}
+expected-exit: 1
+expected-stderr-pattern: /not set/
+---
+name: expand-bang-2
+description:
+ Check corner case of ${!var} vs. ${var op} with var=!
+stdin:
+ echo 1 $! .
+ echo 2 ${!#} .
+ echo 3 ${!#[0-9]} .
+ echo 4 ${!-foo} .
+ # get an at least three-digit bg pid
+ while :; do
+ :&
+ x=$!
+ if [[ $x != +([0-9]) ]]; then
+ echo >&2 "cannot test, pid '$x' not numeric"
+ echo >&2 report this with as many details as possible
+ exit 1
+ fi
+ [[ $x = [0-9][0-9][0-9]* ]] && break
+ done
+ y=${x#?}
+ t=$!; [[ $t = $x ]]; echo 5 $? .
+ t=${!#}; [[ $t = $x ]]; echo 6 $? .
+ t=${!#[0-9]}; [[ $t = $y ]]; echo 7 $? .
+ t=${!-foo}; [[ $t = $x ]]; echo 8 $? .
+ t=${!?bar}; [[ $t = $x ]]; echo 9 $? .
+expected-stdout:
+ 1 .
+ 2 .
+ 3 .
+ 4 foo .
+ 5 0 .
+ 6 0 .
+ 7 0 .
+ 8 0 .
+ 9 0 .
+---
+name: expand-number-1
+description:
+ Check that positional arguments do not overflow
+stdin:
+ echo "1 ${12345678901234567890} ."
+expected-stdout:
+ 1 .
+---
+name: expand-slashes-1
+description:
+ Check that side effects in substring replacement are handled correctly
+stdin:
+ foo=n1n1n1n2n3
+ i=2
+ n=1
+ echo 1 ${foo//n$((n++))/[$((++i))]} .
+ echo 2 $n , $i .
+expected-stdout:
+ 1 [3][3][3]n2n3 .
+ 2 2 , 3 .
+---
+name: expand-slashes-2
+description:
+ Check that side effects in substring replacement are handled correctly
+stdin:
+ foo=n1n1n1n2n3
+ i=2
+ n=1
+ echo 1 ${foo@/n$((n++))/[$((++i))]} .
+ echo 2 $n , $i .
+expected-stdout:
+ 1 [3]n1n1[4][5] .
+ 2 5 , 5 .
+---
+name: expand-slashes-3
+description:
+ Check that we can access the replaced string
+stdin:
+ foo=n1n1n1n2n3
+ echo 1 ${foo@/n[12]/[$KSH_MATCH]} .
+expected-stdout:
+ 1 [n1][n1][n1][n2]n3 .
+---
+name: eglob-bad-1
+description:
+ Check that globbing isn't done when glob has syntax error
+category: !os:cygwin,!os:midipix,!os:msys,!os:os2
+file-setup: file 644 "@(a[b|)c]foo"
+stdin:
+ echo @(a[b|)c]*
+expected-stdout:
+ @(a[b|)c]*
+---
+name: eglob-bad-2
+description:
+ Check that globbing isn't done when glob has syntax error
+ (AT&T ksh fails this test)
+file-setup: file 644 "abcx"
+file-setup: file 644 "abcz"
+file-setup: file 644 "bbc"
+stdin:
+ echo [a*(]*)z
+expected-stdout:
+ [a*(]*)z
+---
+name: eglob-infinite-plus
+description:
+ Check that shell doesn't go into infinite loop expanding +(...)
+ expressions.
+file-setup: file 644 "abc"
+time-limit: 3
+stdin:
+ echo +()c
+ echo +()x
+ echo +(*)c
+ echo +(*)x
+expected-stdout:
+ +()c
+ +()x
+ abc
+ +(*)x
+---
+name: eglob-subst-1
+description:
+ Check that eglobbing isn't done on substitution results
+file-setup: file 644 "abc"
+stdin:
+ x='@(*)'
+ echo $x
+expected-stdout:
+ @(*)
+---
+name: eglob-nomatch-1
+description:
+ Check that the pattern doesn't match
+stdin:
+ echo 1: no-file+(a|b)stuff
+ echo 2: no-file+(a*(c)|b)stuff
+ echo 3: no-file+((((c)))|b)stuff
+expected-stdout:
+ 1: no-file+(a|b)stuff
+ 2: no-file+(a*(c)|b)stuff
+ 3: no-file+((((c)))|b)stuff
+---
+name: eglob-match-1
+description:
+ Check that the pattern matches correctly
+file-setup: file 644 "abd"
+file-setup: file 644 "acd"
+file-setup: file 644 "abac"
+stdin:
+ echo 1: a+(b|c)d
+ echo 2: a!(@(b|B))d
+ echo 3: *(a(b|c)) # (...|...) can be used within X(..)
+ echo 4: a[b*(foo|bar)]d # patterns not special inside [...]
+expected-stdout:
+ 1: abd acd
+ 2: acd
+ 3: abac
+ 4: abd
+---
+name: eglob-case-1
+description:
+ Simple negation tests
+stdin:
+ case foo in !(foo|bar)) echo yes;; *) echo no;; esac
+ case bar in !(foo|bar)) echo yes;; *) echo no;; esac
+expected-stdout:
+ no
+ no
+---
+name: eglob-case-2
+description:
+ Simple kleene tests
+stdin:
+ case foo in *(a|b[)) echo yes;; *) echo no;; esac
+ case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac
+ case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac
+ case 'aab[b[ab[a' in *(a|b[)) echo yes;; *) echo no;; esac
+expected-stdout:
+ no
+ yes
+ no
+ yes
+---
+name: eglob-trim-1
+description:
+ Eglobbing in trim expressions...
+ (AT&T ksh fails this - docs say # matches shortest string, ## matches
+ longest...)
+stdin:
+ x=abcdef
+ echo 1: ${x#a|abc}
+ echo 2: ${x##a|abc}
+ echo 3: ${x%def|f}
+ echo 4: ${x%%f|def}
+expected-stdout:
+ 1: bcdef
+ 2: def
+ 3: abcde
+ 4: abc
+---
+name: eglob-trim-2
+description:
+ Check eglobbing works in trims...
+stdin:
+ x=abcdef
+ echo 1: ${x#*(a|b)cd}
+ echo 2: "${x#*(a|b)cd}"
+ echo 3: ${x#"*(a|b)cd"}
+ echo 4: ${x#a(b|c)}
+expected-stdout:
+ 1: ef
+ 2: ef
+ 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
+stdin:
+ [[ -n $BASH_VERSION ]] && shopt -s extglob
+ x=1222321_ab/cde_b/c_1221
+ y=xyz
+ echo 1: ${x/2} . ${x/}
+ echo 2: ${x//2}
+ echo 3: ${x/+(2)}
+ echo 4: ${x//+(2)}
+ echo 5: ${x/2/4}
+ echo 6: ${x//2/4}
+ echo 7: ${x/+(2)/4}
+ echo 8: ${x//+(2)/4}
+ echo 9: ${x/b/c/e/f}
+ echo 10: ${x/b\/c/e/f}
+ echo 11: ${x/b\/c/e\/f}
+ echo 12: ${x/b\/c/e\\/f}
+ echo 13: ${x/b\\/c/e\\/f}
+ echo 14: ${x//b/c/e/f}
+ echo 15: ${x//b\/c/e/f}
+ echo 16: ${x//b\/c/e\/f}
+ echo 17: ${x//b\/c/e\\/f}
+ echo 18: ${x//b\\/c/e\\/f}
+ echo 19: ${x/b\/*\/c/x}
+ echo 20: ${x/\//.}
+ echo 21: ${x//\//.}
+ echo 22: ${x///.}
+ echo 23: ${x/#1/9}
+ echo 24: ${x//#1/9}
+ echo 25: ${x/%1/9}
+ echo 26: ${x//%1/9}
+ echo 27: ${x//\%1/9}
+ echo 28: ${x//\\%1/9}
+ echo 29: ${x//\a/9}
+ echo 30: ${x//\\a/9}
+ echo 31: ${x/2/$y}
+expected-stdout:
+ 1: 122321_ab/cde_b/c_1221 . 1222321_ab/cde_b/c_1221
+ 2: 131_ab/cde_b/c_11
+ 3: 1321_ab/cde_b/c_1221
+ 4: 131_ab/cde_b/c_11
+ 5: 1422321_ab/cde_b/c_1221
+ 6: 1444341_ab/cde_b/c_1441
+ 7: 14321_ab/cde_b/c_1221
+ 8: 14341_ab/cde_b/c_141
+ 9: 1222321_ac/e/f/cde_b/c_1221
+ 10: 1222321_ae/fde_b/c_1221
+ 11: 1222321_ae/fde_b/c_1221
+ 12: 1222321_ae\/fde_b/c_1221
+ 13: 1222321_ab/cde_b/c_1221
+ 14: 1222321_ac/e/f/cde_c/e/f/c_1221
+ 15: 1222321_ae/fde_e/f_1221
+ 16: 1222321_ae/fde_e/f_1221
+ 17: 1222321_ae\/fde_e\/f_1221
+ 18: 1222321_ab/cde_b/c_1221
+ 19: 1222321_ax_1221
+ 20: 1222321_ab.cde_b/c_1221
+ 21: 1222321_ab.cde_b.c_1221
+ 22: 1222321_ab/cde_b/c_1221
+ 23: 9222321_ab/cde_b/c_1221
+ 24: 1222321_ab/cde_b/c_1221
+ 25: 1222321_ab/cde_b/c_1229
+ 26: 1222321_ab/cde_b/c_1221
+ 27: 1222321_ab/cde_b/c_1221
+ 28: 1222321_ab/cde_b/c_1221
+ 29: 1222321_9b/cde_b/c_1221
+ 30: 1222321_ab/cde_b/c_1221
+ 31: 1xyz22321_ab/cde_b/c_1221
+---
+name: eglob-substrpl-2
+description:
+ Check anchored substring replacement works, corner cases
+stdin:
+ foo=123
+ echo 1: ${foo/#/x}
+ echo 2: ${foo/%/x}
+ echo 3: ${foo/#/}
+ echo 4: ${foo/#}
+ echo 5: ${foo/%/}
+ echo 6: ${foo/%}
+expected-stdout:
+ 1: x123
+ 2: 123x
+ 3: 123
+ 4: 123
+ 5: 123
+ 6: 123
+---
+name: eglob-substrpl-3a
+description:
+ Check substring replacement works with variables and slashes, too
+stdin:
+ HOME=/etc
+ pfx=/home/user
+ wd=/home/user/tmp
+ echo "${wd/#$pfx/~}"
+ echo "${wd/#\$pfx/~}"
+ echo "${wd/#"$pfx"/~}"
+ echo "${wd/#'$pfx'/~}"
+ echo "${wd/#"\$pfx"/~}"
+ echo "${wd/#'\$pfx'/~}"
+expected-stdout:
+ /etc/tmp
+ /home/user/tmp
+ /etc/tmp
+ /home/user/tmp
+ /home/user/tmp
+ /home/user/tmp
+---
+name: eglob-substrpl-3b
+description:
+ More of this, bash fails it (bash4 passes)
+stdin:
+ HOME=/etc
+ pfx=/home/user
+ wd=/home/user/tmp
+ echo "${wd/#$(echo /home/user)/~}"
+ echo "${wd/#"$(echo /home/user)"/~}"
+ echo "${wd/#'$(echo /home/user)'/~}"
+expected-stdout:
+ /etc/tmp
+ /etc/tmp
+ /home/user/tmp
+---
+name: eglob-substrpl-3c
+description:
+ Even more weird cases
+stdin:
+ HOME=/etc
+ pfx=/home/user
+ wd='$pfx/tmp'
+ echo 1: ${wd/#$pfx/~}
+ echo 2: ${wd/#\$pfx/~}
+ echo 3: ${wd/#"$pfx"/~}
+ echo 4: ${wd/#'$pfx'/~}
+ echo 5: ${wd/#"\$pfx"/~}
+ echo 6: ${wd/#'\$pfx'/~}
+ ts='a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)'
+ tp=a/b
+ tr=c/d
+ [[ -n $BASH_VERSION ]] && shopt -s extglob
+ echo 7: ${ts/a\/b/$tr}
+ echo 8: ${ts/a\/b/\$tr}
+ echo 9: ${ts/$tp/$tr}
+ echo 10: ${ts/\$tp/$tr}
+ echo 11: ${ts/\\$tp/$tr}
+ echo 12: ${ts/$tp/c/d}
+ echo 13: ${ts/$tp/c\/d}
+ echo 14: ${ts/$tp/c\\/d}
+ echo 15: ${ts/+(a\/b)/$tr}
+ echo 16: ${ts/+(a\/b)/\$tr}
+ echo 17: ${ts/+($tp)/$tr}
+ echo 18: ${ts/+($tp)/c/d}
+ echo 19: ${ts/+($tp)/c\/d}
+ echo 20: ${ts//a\/b/$tr}
+ echo 21: ${ts//a\/b/\$tr}
+ echo 22: ${ts//$tp/$tr}
+ echo 23: ${ts//$tp/c/d}
+ echo 24: ${ts//$tp/c\/d}
+ echo 25: ${ts//+(a\/b)/$tr}
+ echo 26: ${ts//+(a\/b)/\$tr}
+ echo 27: ${ts//+($tp)/$tr}
+ echo 28: ${ts//+($tp)/c/d}
+ echo 29: ${ts//+($tp)/c\/d}
+ tp="+($tp)"
+ echo 30: ${ts/$tp/$tr}
+ echo 31: ${ts//$tp/$tr}
+expected-stdout:
+ 1: $pfx/tmp
+ 2: /etc/tmp
+ 3: $pfx/tmp
+ 4: /etc/tmp
+ 5: /etc/tmp
+ 6: $pfx/tmp
+ 7: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 8: $tra/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 9: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 10: a/ba/bc/d$tp_a/b$tp_*(a/b)_*($tp)
+ 11: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 12: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 13: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 14: c\/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 15: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 16: $tr$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 17: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 18: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 19: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 20: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 21: $tr$tr$tp$tp_$tr$tp_*($tr)_*($tp)
+ 22: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 23: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 24: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 25: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 26: $tr$tp$tp_$tr$tp_*($tr)_*($tp)
+ 27: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 28: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 29: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+ 30: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+ 31: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+# This is what GNU bash does:
+# 30: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+# 31: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+---
+name: eglob-utf8-1
+description:
+ UTF-8 mode differences for eglobbing
+category: !shell:ebcdic-yes
+stdin:
+ s=blöd
+ set +U
+ print 1: ${s%???} .
+ print 2: ${s/b???d/x} .
+ set -U
+ print 3: ${s%???} .
+ print 4: ${s/b??d/x} .
+ x=nö
+ print 5: ${x%?} ${x%%?} .
+ x=äh
+ print 6: ${x#?} ${x##?} .
+ x=‚
+ print 7: ${x%?} ${x%%?} .
+ x=mä€
+ print 8: ${x%?} ${x%%?} .
+ x=何
+ print 9: ${x%?} ${x%%?} .
+expected-stdout:
+ 1: bl .
+ 2: x .
+ 3: b .
+ 4: x .
+ 5: n n .
+ 6: h h .
+ 7: .
+ 8: mä mä .
+ 9: .
+---
+name: glob-bad-1
+description:
+ Check that [ matches itself if it's not a valid bracket expr
+ but does not prevent globbing, while backslash-escaping does
+file-setup: dir 755 "[x"
+file-setup: file 644 "[x/foo"
+stdin:
+ echo [*
+ echo *[x
+ echo [x/*
+ :>'ab[x'
+ :>'a[a-z][x'
+ echo a[a-z][*
+ echo a[a-z]*
+ echo a[a\-z]*
+expected-stdout:
+ [x
+ [x
+ [x/foo
+ ab[x
+ ab[x
+ a[a-z]*
+---
+name: glob-bad-2
+description:
+ Check that symbolic links aren't stat()'d
+# breaks on Dell UNIX 4.0 R2.2 (SVR4) where unlink also fails
+# breaks on FreeMiNT (cannot unlink dangling symlinks)
+# breaks on MSYS, OS/2 (do not support symlinks)
+category: !os:mint,!os:msys,!os:svr4.0,!nosymlink
+file-setup: dir 755 "dir"
+file-setup: symlink 644 "dir/abc"
+ non-existent-file
+stdin:
+ echo d*/*
+ echo d*/abc
+expected-stdout:
+ dir/abc
+ dir/abc
+---
+name: glob-bad-3
+description:
+ Check that the slash is parsed before the glob
+stdin:
+ mkdir a 'a[b'
+ (cd 'a[b'; echo ok >'c]d')
+ echo nok >abd
+ echo fail >a/d
+ cat a[b/c]d
+expected-stdout:
+ ok
+---
+name: glob-range-1
+description:
+ Test range matching
+file-setup: file 644 ".bc"
+file-setup: file 644 "abc"
+file-setup: file 644 "bbc"
+file-setup: file 644 "cbc"
+file-setup: file 644 "-bc"
+file-setup: file 644 "!bc"
+file-setup: file 644 "^bc"
+file-setup: file 644 "+bc"
+file-setup: file 644 ",bc"
+file-setup: file 644 "0bc"
+file-setup: file 644 "1bc"
+stdin:
+ echo [ab-]*
+ echo [-ab]*
+ echo [!-ab]*
+ echo [!ab]*
+ echo []ab]*
+ echo [^ab]*
+ echo [+--]*
+ echo [--1]*
+
+expected-stdout:
+ -bc abc bbc
+ -bc abc bbc
+ !bc +bc ,bc 0bc 1bc ^bc cbc
+ !bc +bc ,bc -bc 0bc 1bc ^bc cbc
+ abc bbc
+ ^bc abc bbc
+ +bc ,bc -bc
+ -bc 0bc 1bc
+---
+name: glob-range-2
+description:
+ Test range matching
+ (AT&T ksh fails this; POSIX says invalid)
+file-setup: file 644 "abc"
+stdin:
+ echo [a--]*
+expected-stdout:
+ [a--]*
+---
+name: glob-range-3
+description:
+ Check that globbing matches the right things...
+# breaks on Mac OSX (HFS+ non-standard UTF-8 canonical decomposition)
+# breaks on Cygwin 1.7 (files are now UTF-16 or something)
+# breaks on QNX 6.4.1 (says RT)
+category: !os:cygwin,!os:midipix,!os:darwin,!os:msys,!os:nto,!os:os2,!os:os390
+need-pass: no
+file-setup: file 644 "aÂc"
+stdin:
+ echo a[Á-Ú]*
+expected-stdout:
+ aÂc
+---
+name: glob-range-4
+description:
+ Results unspecified according to POSIX
+file-setup: file 644 ".bc"
+stdin:
+ echo [a.]*
+expected-stdout:
+ [a.]*
+---
+name: glob-range-5
+description:
+ Results unspecified according to POSIX
+ (AT&T ksh treats this like [a-cc-e]*)
+file-setup: file 644 "abc"
+file-setup: file 644 "bbc"
+file-setup: file 644 "cbc"
+file-setup: file 644 "dbc"
+file-setup: file 644 "ebc"
+file-setup: file 644 "-bc"
+file-setup: file 644 "@bc"
+stdin:
+ echo [a-c-e]*
+ echo [a--@]*
+expected-stdout:
+ -bc abc bbc cbc ebc
+ @bc
+---
+name: glob-range-6
+description:
+ ksh93 fails this but POSIX probably demands it
+file-setup: file 644 "abc"
+file-setup: file 644 "cbc"
+stdin:
+ echo *b*
+ [ '*b*' = *b* ] && echo yep; echo $?
+expected-stdout:
+ abc cbc
+ 2
+expected-stderr-pattern: /.*/
+---
+name: glob-word-1
+description:
+ Check BSD word boundary matches
+stdin:
+ t() { [[ $1 = *[[:\<:]]bar[[:\>:]]* ]]; echo =$?; }
+ t 'foo bar baz'
+ t 'foobar baz'
+ t 'foo barbaz'
+ t 'bar'
+ t '_bar'
+ t 'bar_'
+expected-stdout:
+ =0
+ =1
+ =1
+ =0
+ =1
+ =1
+---
+name: glob-trim-1
+description:
+ Check against a regression from fixing IFS-subst-2
+stdin:
+ x='#foo'
+ print -r "before='$x'"
+ x=${x%%#*}
+ print -r "after ='$x'"
+expected-stdout:
+ before='#foo'
+ after =''
+---
+name: heredoc-1
+description:
+ Check ordering/content of redundent here documents.
+stdin:
+ cat << EOF1 << EOF2
+ hi
+ EOF1
+ there
+ EOF2
+expected-stdout:
+ there
+---
+name: heredoc-2
+description:
+ Check quoted here-doc is protected.
+stdin:
+ a=foo
+ cat << 'EOF'
+ hi\
+ there$a
+ stuff
+ EO\
+ F
+ EOF
+expected-stdout:
+ hi\
+ there$a
+ stuff
+ EO\
+ F
+---
+name: heredoc-3
+description:
+ Check that newline isn't needed after heredoc-delimiter marker.
+stdin: !
+ cat << EOF
+ hi
+ there
+ EOF
+expected-stdout:
+ hi
+ there
+---
+name: heredoc-4a
+description:
+ Check that an error occurs if the heredoc-delimiter is missing.
+stdin: !
+ cat << EOF
+ hi
+ there
+expected-exit: e > 0
+expected-stderr-pattern: /.*/
+---
+name: heredoc-4an
+description:
+ Check that an error occurs if the heredoc-delimiter is missing.
+arguments: !-n!
+stdin: !
+ cat << EOF
+ hi
+ there
+expected-exit: e > 0
+expected-stderr-pattern: /.*/
+---
+name: heredoc-4b
+description:
+ Check that an error occurs if the heredoc is missing.
+stdin: !
+ cat << EOF
+expected-exit: e > 0
+expected-stderr-pattern: /.*/
+---
+name: heredoc-4bn
+description:
+ Check that an error occurs if the heredoc is missing.
+arguments: !-n!
+stdin: !
+ cat << EOF
+expected-exit: e > 0
+expected-stderr-pattern: /.*/
+---
+name: heredoc-5
+description:
+ Check that backslash quotes a $, ` and \ and kills a \newline
+stdin:
+ a=BAD
+ b=ok
+ cat << EOF
+ h\${a}i
+ h\\${b}i
+ th\`echo not-run\`ere
+ th\\`echo is-run`ere
+ fol\\ks
+ more\\
+ last \
+ line
+ EOF
+expected-stdout:
+ h${a}i
+ h\oki
+ th`echo not-run`ere
+ th\is-runere
+ fol\ks
+ more\
+ last line
+---
+name: heredoc-6
+description:
+ Check that \newline in initial here-delim word doesn't imply
+ a quoted here-doc.
+stdin:
+ a=i
+ cat << EO\
+ F
+ h$a
+ there
+ EOF
+expected-stdout:
+ hi
+ there
+---
+name: heredoc-7
+description:
+ Check that double quoted $ expressions in here delimiters are
+ not expanded and match the delimiter.
+ POSIX says only quote removal is applied to the delimiter.
+stdin:
+ a=b
+ cat << "E$a"
+ hi
+ h$a
+ hb
+ E$a
+ echo done
+expected-stdout:
+ hi
+ h$a
+ hb
+ done
+---
+name: heredoc-8
+description:
+ Check that double quoted escaped $ expressions in here
+ delimiters are not expanded and match the delimiter.
+ POSIX says only quote removal is applied to the delimiter
+ (\ counts as a quote).
+stdin:
+ a=b
+ cat << "E\$a"
+ hi
+ h$a
+ h\$a
+ hb
+ h\b
+ E$a
+ echo done
+expected-stdout:
+ hi
+ h$a
+ h\$a
+ hb
+ h\b
+ done
+---
+name: heredoc-9
+description:
+ Check that here strings work.
+stdin:
+ bar="bar
+ baz"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo
+ "$__progname" -c "tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"$bar"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<'$bar'
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<\$bar
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<-foo
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"$(echo "foo bar")"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"A $(echo "foo bar") B"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<\$b\$b$bar
+ fnord=42
+ bar="bar
+ \$fnord baz"
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<$bar
+ tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<< bar
+ echo $(tr r z <<<'bar' 2>/dev/null)
+ cat <<< "$( : )aa"
+ IFS=$'\n'
+ x=(a "b c")
+ tr ac 12 <<< ${x[*]}
+ tr ac 34 <<< "${x[*]}"
+ tr ac 56 <<< ${x[@]}
+ tr ac 78 <<< "${x[@]}"
+expected-stdout:
+ sbb
+ sbb
+ one
+ onm
+ $one
+ $one
+ -sbb
+ sbb one
+ A sbb one B
+ $o$oone
+ onm
+ one
+ $sabeq onm
+ one
+ baz
+ aa
+ 1
+ b 2
+ 3
+ b 4
+ 5 b 6
+ 7 b 8
+---
+name: heredoc-10
+description:
+ Check direct here document assignment
+category: !shell:ebcdic-yes
+stdin:
+ x=u
+ va=<<EOF
+ =a $x \x40=
+ EOF
+ vb=<<'EOF'
+ =b $x \x40=
+ EOF
+ function foo {
+ vc=<<-EOF
+ =c $x \x40=
+ EOF
+ }
+ fnd=$(typeset -f foo)
+ print -r -- "$fnd"
+ function foo {
+ echo blub
+ }
+ foo
+ eval "$fnd"
+ 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} |"
+ # check append
+ v=<<-EOF
+ vapp1
+ EOF
+ v+=<<-EOF
+ vapp2
+ EOF
+ print -r -- "| ${v//$'\n'/^} |"
+expected-stdout:
+ function foo {
+ vc=<<-EOF
+ =c $x \x40=
+ EOF
+
+ }
+ blub
+ | va={=a u \x40=
+ } vb={=b $x \x40=
+ } vc={=c u \x40=
+ } vd={=d u \x40=
+ } ve={=e $x \x40=
+ } vf={=f $x @=
+ } |
+ | vapp1^vapp2^ |
+---
+name: heredoc-10-ebcdic
+description:
+ Check direct here document assignment
+category: !shell:ebcdic-no
+stdin:
+ x=u
+ va=<<EOF
+ =a $x \x7C=
+ EOF
+ vb=<<'EOF'
+ =b $x \x7C=
+ EOF
+ function foo {
+ vc=<<-EOF
+ =c $x \x7C=
+ EOF
+ }
+ fnd=$(typeset -f foo)
+ print -r -- "$fnd"
+ function foo {
+ echo blub
+ }
+ foo
+ eval "$fnd"
+ foo
+ # rather nonsensical, but…
+ vd=<<<"=d $x \x7C="
+ ve=<<<'=e $x \x7C='
+ vf=<<<$'=f $x \x7C='
+ # now check
+ print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |"
+ # check append
+ v=<<-EOF
+ vapp1
+ EOF
+ v+=<<-EOF
+ vapp2
+ EOF
+ print -r -- "| ${v//$'\n'/^} |"
+expected-stdout:
+ function foo {
+ vc=<<-EOF
+ =c $x \x7C=
+ EOF
+
+ }
+ blub
+ | va={=a u \x7C=
+ } vb={=b $x \x7C=
+ } vc={=c u \x7C=
+ } vd={=d u \x7C=
+ } ve={=e $x \x7C=
+ } vf={=f $x @=
+ } |
+ | vapp1^vapp2^ |
+---
+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=
+
+ }
+ fnd=$(typeset -f foo)
+ print -r -- "$fnd"
+ function foo {
+ echo blub
+ }
+ foo
+ eval "$fnd"
+ foo
+ print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |"
+ x=y
+ foo
+ typeset -f foo
+ print -r -- "| vc={$vc} vd={$vd} |"
+ # check append
+ v=<<-
+ vapp1
+ <<
+ v+=<<-''
+ vapp2
+
+ print -r -- "| ${v//$'\n'/^} |"
+expected-stdout:
+ function foo {
+ vc=<<-
+ =c $x \x40=
+ <<
+
+ vd=<<-""
+ =d $x \x40=
+
+
+ }
+ blub
+ | va={=a u \x40=
+ } vb={=b $x \x40=
+ } vc={=c u \x40=
+ } vd={=d $x \x40=
+ } |
+ function foo {
+ vc=<<-
+ =c $x \x40=
+ <<
+
+ vd=<<-""
+ =d $x \x40=
+
+
+ }
+ | vc={=c y \x40=
+ } vd={=d $x \x40=
+ } |
+ | vapp1^vapp2^ |
+---
+name: heredoc-12
+description:
+ Check here documents can use $* and $@; note shells vary:
+ • pdksh 5.2.14 acts the same
+ • dash has 1 and 2 the same but 3 lacks the space
+ • ksh93, bash4 differ in 2 by using space ipv colon
+stdin:
+ set -- a b
+ nl='
+ '
+ IFS=" $nl"; n=1
+ cat <<EOF
+ $n foo $* foo
+ $n bar "$*" bar
+ $n baz $@ baz
+ $n bla "$@" bla
+ EOF
+ IFS=":"; n=2
+ cat <<EOF
+ $n foo $* foo
+ $n bar "$*" bar
+ $n baz $@ baz
+ $n bla "$@" bla
+ EOF
+ IFS=; n=3
+ cat <<EOF
+ $n foo $* foo
+ $n bar "$*" bar
+ $n baz $@ baz
+ $n bla "$@" bla
+ EOF
+expected-stdout:
+ 1 foo a b foo
+ 1 bar "a b" bar
+ 1 baz a b baz
+ 1 bla "a b" bla
+ 2 foo a:b foo
+ 2 bar "a:b" bar
+ 2 baz a:b baz
+ 2 bla "a:b" bla
+ 3 foo a b foo
+ 3 bar "a b" bar
+ 3 baz a b baz
+ 3 bla "a b" bla
+---
+name: heredoc-14
+description:
+ Check that using multiple here documents works
+stdin:
+ foo() {
+ echo "got $(cat) on stdin"
+ echo "got $(cat <&4) on fd#4"
+ echo "got $(cat <&5) on fd#5"
+ }
+ bar() {
+ foo 4<<-a <<-b 5<<-c
+ four
+ a
+ zero
+ b
+ five
+ c
+ }
+ x=$(typeset -f bar)
+ eval "$x"
+ y=$(typeset -f bar)
+ [[ $x = "$y" ]]; echo $?
+ typeset -f bar
+ bar
+expected-stdout:
+ 0
+ bar() {
+ \foo 4<<-a <<-b 5<<-c
+ four
+ a
+ zero
+ b
+ five
+ c
+
+ }
+ got zero on stdin
+ got four on fd#4
+ got five on fd#5
+---
+name: heredoc-15
+description:
+ Check high-bit7 separators work
+stdin:
+ u=ä
+ tr a-z A-Z <<-…
+ m${u}h
+ …
+ echo ok
+expected-stdout:
+ MäH
+ ok
+---
+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-comsub-5
+description:
+ Check heredoc and COMSUB mixture in input
+stdin:
+ prefix() { sed -e "s/^/$1:/"; }
+ XXX() { echo x-en; }
+ YYY() { echo y-es; }
+
+ prefix A <<XXX && echo "$(prefix B <<XXX
+ echo line 1
+ XXX
+ echo line 2)" && prefix C <<YYY
+ echo line 3
+ XXX
+ echo line 4)"
+ echo line 5
+ YYY
+ XXX
+expected-stdout:
+ A:echo line 3
+ B:echo line 1
+ line 2
+ C:echo line 4)"
+ C:echo line 5
+ x-en
+---
+name: heredoc-comsub-6
+description:
+ Check here documents and here strings can be used
+ without a specific command, like $(<…) (extension)
+stdin:
+ foo=bar
+ x=$(<<<EO${foo}F)
+ echo "3<$x>"
+ y=$(<<-EOF
+ hi!
+
+ $foo) is not a problem
+
+
+ EOF)
+ echo "7<$y>"
+expected-stdout:
+ 3<EObarF>
+ 7<hi!
+
+ bar) is not 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-tmpfile-1
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Heredoc in simple command.
+stdin:
+ TMPDIR=$PWD
+ eval '
+ cat <<- EOF
+ hi
+ EOF
+ for i in a b ; do
+ cat <<- EOF
+ more
+ EOF
+ done
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ hi
+ more
+ more
+ Left overs: *
+---
+name: heredoc-tmpfile-2
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Heredoc in function, multiple calls to function.
+stdin:
+ TMPDIR=$PWD
+ eval '
+ foo() {
+ cat <<- EOF
+ hi
+ EOF
+ }
+ foo
+ foo
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ hi
+ hi
+ Left overs: *
+---
+name: heredoc-tmpfile-3
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Heredoc in function in loop, multiple calls to function.
+stdin:
+ TMPDIR=$PWD
+ eval '
+ foo() {
+ cat <<- EOF
+ hi
+ EOF
+ }
+ for i in a b; do
+ foo
+ foo() {
+ cat <<- EOF
+ folks $i
+ EOF
+ }
+ done
+ foo
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ hi
+ folks b
+ folks b
+ Left overs: *
+---
+name: heredoc-tmpfile-4
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Backgrounded simple command with here doc
+stdin:
+ TMPDIR=$PWD
+ eval '
+ cat <<- EOF &
+ hi
+ EOF
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ hi
+ Left overs: *
+---
+name: heredoc-tmpfile-5
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Backgrounded subshell command with here doc
+stdin:
+ TMPDIR=$PWD
+ eval '
+ (
+ sleep 1 # so parent exits
+ echo A
+ cat <<- EOF
+ hi
+ EOF
+ echo B
+ ) &
+ ' &
+ sleep 5
+ echo Left overs: *
+expected-stdout:
+ A
+ hi
+ B
+ Left overs: *
+---
+name: heredoc-tmpfile-6
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Heredoc in pipeline.
+stdin:
+ TMPDIR=$PWD
+ eval '
+ cat <<- EOF | sed "s/hi/HI/"
+ hi
+ EOF
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ HI
+ Left overs: *
+---
+name: heredoc-tmpfile-7
+description:
+ Check that heredoc temp files aren't removed too soon or too late.
+ Heredoc in backgrounded pipeline.
+stdin:
+ TMPDIR=$PWD
+ eval '
+ cat <<- EOF | sed 's/hi/HI/' &
+ hi
+ EOF
+ ' &
+ sleep 1
+ echo Left overs: *
+expected-stdout:
+ HI
+ Left overs: *
+---
+name: heredoc-tmpfile-8
+description:
+ Check that heredoc temp files aren't removed too soon or too
+ 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
+ eval '
+ foo() {
+ cat <<- EOF
+ hi
+ EOF
+ }
+ foo
+ # sleep so eval can die
+ (sleep 1; foo) &
+ (sleep 1; foo) &
+ foo
+ ' &
+ sleep 5
+ echo Left overs: *
+expected-stdout:
+ hi
+ hi
+ hi
+ hi
+ Left overs: *
+---
+name: heredoc-quoting-unsubst
+description:
+ Check for correct handling of quoted characters in
+ here documents without substitution (marker is quoted).
+stdin:
+ foo=bar
+ cat <<-'EOF'
+ x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+ EOF
+expected-stdout:
+ x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+---
+name: heredoc-quoting-subst
+description:
+ Check for correct handling of quoted characters in
+ here documents with substitution (marker is not quoted).
+stdin:
+ foo=bar
+ cat <<-EOF
+ x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+ EOF
+expected-stdout:
+ x " \" \ \ $ $ baz `echo baz` bar $foo x
+---
+name: single-quotes-in-braces
+description:
+ Check that single quotes inside unquoted {} are treated as quotes
+stdin:
+ foo=1
+ echo ${foo:+'blah $foo'}
+expected-stdout:
+ blah $foo
+---
+name: single-quotes-in-quoted-braces
+description:
+ Check that single quotes inside quoted {} are treated as
+ normal char
+stdin:
+ foo=1
+ echo "${foo:+'blah $foo'}"
+expected-stdout:
+ 'blah 1'
+---
+name: single-quotes-in-braces-nested
+description:
+ Check that single quotes inside unquoted {} are treated as quotes,
+ even if that's inside a double-quoted command expansion
+stdin:
+ foo=1
+ echo "$( echo ${foo:+'blah $foo'})"
+expected-stdout:
+ blah $foo
+---
+name: single-quotes-in-brace-pattern
+description:
+ Check that single quotes inside {} pattern are treated as quotes
+stdin:
+ foo=1234
+ echo ${foo%'2'*} "${foo%'2'*}" ${foo%2'*'} "${foo%2'*'}"
+expected-stdout:
+ 1 1 1234 1234
+---
+name: single-quotes-in-heredoc-braces
+description:
+ Check that single quotes inside {} in heredoc are treated
+ as normal char
+stdin:
+ foo=1
+ cat <<EOM
+ ${foo:+'blah $foo'}
+ EOM
+expected-stdout:
+ 'blah 1'
+---
+name: single-quotes-in-nested-braces
+description:
+ Check that single quotes inside nested unquoted {} are
+ treated as quotes
+stdin:
+ foo=1
+ echo ${foo:+${foo:+'blah $foo'}}
+expected-stdout:
+ blah $foo
+---
+name: single-quotes-in-nested-quoted-braces
+description:
+ Check that single quotes inside nested quoted {} are treated
+ as normal char
+stdin:
+ foo=1
+ echo "${foo:+${foo:+'blah $foo'}}"
+expected-stdout:
+ 'blah 1'
+---
+name: single-quotes-in-nested-braces-nested
+description:
+ Check that single quotes inside nested unquoted {} are treated
+ as quotes, even if that's inside a double-quoted command expansion
+stdin:
+ foo=1
+ echo "$( echo ${foo:+${foo:+'blah $foo'}})"
+expected-stdout:
+ blah $foo
+---
+name: single-quotes-in-nested-brace-pattern
+description:
+ Check that single quotes inside nested {} pattern are treated as quotes
+stdin:
+ foo=1234
+ echo ${foo:+${foo%'2'*}} "${foo:+${foo%'2'*}}" ${foo:+${foo%2'*'}} "${foo:+${foo%2'*'}}"
+expected-stdout:
+ 1 1 1234 1234
+---
+name: single-quotes-in-heredoc-nested-braces
+description:
+ Check that single quotes inside nested {} in heredoc are treated
+ as normal char
+stdin:
+ foo=1
+ cat <<EOM
+ ${foo:+${foo:+'blah $foo'}}
+ EOM
+expected-stdout:
+ 'blah 1'
+---
+name: single-quotes-in-heredoc-trim
+description:
+ In some cases, single quotes inside {} in heredoc are not normal
+stdin:
+ x=notOK
+ cat <<EOF
+ 1: ${x#not} ${x:+${x#not}}
+ 2: ${x#\n\o\t} ${x:+${x#\n\o\t}}
+ 3: ${x#"not"} ${x:+${x#"not"}}
+ 4: ${x#'not'} ${x:+${x#'not'}}
+ 5: ${x#$'not'} ${x:+${x#$'not'}}
+ EOF
+expected-stdout:
+ 1: OK OK
+ 2: OK OK
+ 3: OK OK
+ 4: OK OK
+ 5: OK OK
+---
+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"
+ PS1=X
+stdin:
+ echo hi
+ fc -l
+expected-stdout:
+ hi
+ 1 echo hi
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo hi
+ echo yo
+ echo hi
+ fc -l
+expected-stdout:
+ hi
+ yo
+ hi
+ 1 echo hi
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+file-setup: dir 755 "foo"
+file-setup: file 644 "foo/hist.file"
+ sometext
+time-limit: 5
+perl-setup: chmod(0555, "foo");
+stdin:
+ echo hi
+ fc -l
+ chmod 0755 foo
+expected-stdout:
+ hi
+ 1 echo hi
+expected-stderr-pattern:
+ /(.*can't unlink HISTFILE.*\n)?X*$/
+---
+name: history-multiline
+description:
+ Check correct multiline history, Debian #783978
+need-ctty: yes
+arguments: !-i!
+env-setup: !ENV=./Env!
+file-setup: file 644 "Env"
+ PS1=X
+ PS2=Y
+stdin:
+ for i in A B C
+ do
+ print $i
+ print $i
+ done
+ fc -l
+expected-stdout:
+ A
+ A
+ B
+ B
+ C
+ C
+ 1 for i in A B C
+ do
+ print $i
+ print $i
+ done
+expected-stderr-pattern:
+ /^XYYYYXX$/
+---
+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"
+ PS1=X
+stdin:
+ echo hi
+ echo there
+ fc -e -
+expected-stdout:
+ hi
+ there
+ there
+expected-stderr-pattern:
+ /^X*echo there\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ exec 2>&1
+ echo hi
+ echo there
+ fc -e -
+expected-stdout-pattern:
+ /X*hi\nX*there\nX*echo there\nthere\nX*/
+expected-stderr-pattern:
+ /^X*$/
+---
+name: history-e-minus-3
+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"
+ PS1=X
+stdin:
+ fc -e -
+ echo ok
+expected-stdout:
+ ok
+expected-stderr-pattern:
+ /^X*.*:.*history.*\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo abc
+ fc -e - | (read x; echo "A $x")
+ echo ok
+expected-stdout:
+ abc
+ A abc
+ ok
+expected-stderr-pattern:
+ /^X*echo abc\nX*/
+---
+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"
+ PS1=X
+stdin:
+ echo abc def
+ echo ghi jkl
+ :
+ fc -e - echo
+ fc -l 2 5
+expected-stdout:
+ abc def
+ ghi jkl
+ ghi jkl
+ 2 echo ghi jkl
+ 3 :
+ 4 echo ghi jkl
+ 5 fc -l 2 5
+expected-stderr-pattern:
+ /^X*echo ghi jkl\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ fc -l -- -2
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ 2 echo line 2
+ 3 echo line 3
+expected-stderr-pattern:
+ /^X*$/
+---
+name: history-list-2
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ fc -l -- -40
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ 1 echo line 1
+ 2 echo line 2
+ 3 echo line 3
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ fc -l -3 -2
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ 2 echo line 2
+ 3 echo line 3
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ fc -l -1 -1
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ 4 echo line 4
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ fc -l -1 -1
+ fc -l -2 -1
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ 4 echo line 4
+ 4 echo line 4
+ 5 fc -l -1 -1
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ echo line 5
+ fc -l
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ 4 echo line 4
+ 5 echo line 5
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ echo line 5
+ fc -l 1 30
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ 4 echo line 4
+ 5 echo line 5
+ 6 fc -l 1 30
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ echo line 5
+ fc -l -r 2 4
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ 4 echo line 4
+ 3 echo line 3
+ 2 echo line 2
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ echo line 5
+ fc -l 4 2
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ 4 echo line 4
+ 3 echo line 3
+ 2 echo line 2
+expected-stderr-pattern:
+ /^X*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2
+ echo line 3
+ echo line 4
+ echo line 5
+ fc -l -r 4 2
+expected-stdout:
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ 2 echo line 2
+ 3 echo line 3
+ 4 echo line 4
+expected-stderr-pattern:
+ /^X*$/
+---
+name: history-subst-1
+description:
+ Basic substitution
+need-ctty: yes
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+ PS1=X
+stdin:
+ echo abc def
+ echo ghi jkl
+ fc -e - abc=AB 'echo a'
+expected-stdout:
+ abc def
+ ghi jkl
+ AB def
+expected-stderr-pattern:
+ /^X*echo AB def\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo abc def
+ echo ghi jkl
+ fc -e - jkl=XYZQRT 'echo g'
+expected-stdout:
+ abc def
+ ghi jkl
+ ghi XYZQRT
+expected-stderr-pattern:
+ /^X*echo ghi XYZQRT\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo abc def
+ echo ghi jkl
+ fc -e - jkl=XYZQRT
+expected-stdout:
+ abc def
+ ghi jkl
+ ghi XYZQRT
+expected-stderr-pattern:
+ /^X*echo ghi XYZQRT\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo abc def asjj sadjhasdjh asdjhasd
+ fc -e - -g a=FooBAR
+expected-stdout:
+ abc def asjj sadjhasdjh asdjhasd
+ FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd
+expected-stderr-pattern:
+ /^X*echo FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo abc def
+ echo ghi jkl
+ fc -e - abc=AB \?abc
+expected-stdout:
+ abc def
+ ghi jkl
+ AB def
+expected-stderr-pattern:
+ /^X*echo AB def\nX*$/
+---
+name: history-ed-1-old
+description:
+ Basic (ed) editing works (assumes you have generic ed editor
+ 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"
+ PS1=X
+stdin:
+ echo abc def
+ fc echo
+ s/abc/FOOBAR/
+ w
+ q
+expected-stdout:
+ abc def
+ 13
+ 16
+ FOOBAR def
+expected-stderr-pattern:
+ /^X*echo FOOBAR def\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2 is here
+ echo line 3
+ echo line 4
+ fc 2
+ s/is here/is changed/
+ w
+ q
+expected-stdout:
+ line 1
+ line 2 is here
+ line 3
+ line 4
+ 20
+ 23
+ line 2 is changed
+expected-stderr-pattern:
+ /^X*echo line 2 is changed\nX*$/
+---
+name: history-ed-3-old
+description:
+ Newly created multi line commands show up as single command
+ in history.
+ (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"
+ PS1=X
+stdin:
+ echo abc def
+ fc echo
+ s/abc/FOOBAR/
+ $a
+ echo a new line
+ .
+ w
+ q
+ fc -l
+expected-stdout:
+ abc def
+ 13
+ 32
+ FOOBAR def
+ a new line
+ 1 echo abc def
+ 2 echo FOOBAR def
+ echo a new line
+expected-stderr-pattern:
+ /^X*echo FOOBAR def\necho a new line\nX*$/
+---
+name: history-ed-1
+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"
+ PS1=X
+stdin:
+ echo abc def
+ fc echo
+ s/abc/FOOBAR/
+ w
+ q
+expected-stdout:
+ abc def
+ FOOBAR def
+expected-stderr-pattern:
+ /^X*13\n16\necho FOOBAR def\nX*$/
+---
+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"
+ PS1=X
+stdin:
+ echo line 1
+ echo line 2 is here
+ echo line 3
+ echo line 4
+ fc 2
+ s/is here/is changed/
+ w
+ q
+expected-stdout:
+ line 1
+ line 2 is here
+ line 3
+ line 4
+ line 2 is changed
+expected-stderr-pattern:
+ /^X*20\n23\necho line 2 is changed\nX*$/
+---
+name: history-ed-3
+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"
+ PS1=X
+stdin:
+ echo abc def
+ fc echo
+ s/abc/FOOBAR/
+ $a
+ echo a new line
+ .
+ w
+ q
+ fc -l
+expected-stdout:
+ abc def
+ FOOBAR def
+ a new line
+ 1 echo abc def
+ 2 echo FOOBAR def
+ echo a new line
+expected-stderr-pattern:
+ /^X*13\n32\necho FOOBAR def\necho a new line\nX*$/
+---
+name: IFS-space-1
+description:
+ Simple test, default IFS
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ set -- A B C
+ showargs 1 $*
+ showargs 2 "$*"
+ showargs 3 $@
+ showargs 4 "$@"
+expected-stdout:
+ <1> <A> <B> <C> .
+ <2> <A B C> .
+ <3> <A> <B> <C> .
+ <4> <A> <B> <C> .
+---
+name: IFS-colon-1
+description:
+ Simple test, IFS=:
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS=:
+ set -- A B C
+ showargs 1 $*
+ showargs 2 "$*"
+ showargs 3 $@
+ showargs 4 "$@"
+expected-stdout:
+ <1> <A> <B> <C> .
+ <2> <A:B:C> .
+ <3> <A> <B> <C> .
+ <4> <A> <B> <C> .
+---
+name: IFS-null-1
+description:
+ Simple test, IFS=""
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS=""
+ set -- A B C
+ showargs 1 $*
+ showargs 2 "$*"
+ showargs 3 $@
+ showargs 4 "$@"
+expected-stdout:
+ <1> <A> <B> <C> .
+ <2> <ABC> .
+ <3> <A> <B> <C> .
+ <4> <A> <B> <C> .
+---
+name: IFS-space-colon-1
+description:
+ Simple test, IFS=<white-space>:
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="$IFS:"
+ set --
+ showargs 1 $*
+ showargs 2 "$*"
+ showargs 3 $@
+ showargs 4 "$@"
+ showargs 5 : "$@"
+expected-stdout:
+ <1> .
+ <2> <> .
+ <3> .
+ <4> .
+ <5> <:> .
+---
+name: IFS-space-colon-2
+description:
+ Simple test, IFS=<white-space>:
+ AT&T ksh fails this, POSIX says the test is correct.
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="$IFS:"
+ set --
+ showargs :"$@"
+expected-stdout:
+ <:> .
+---
+name: IFS-space-colon-4
+description:
+ Simple test, IFS=<white-space>:
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="$IFS:"
+ set --
+ showargs "$@$@"
+expected-stdout:
+ .
+---
+name: IFS-space-colon-5
+description:
+ Simple test, IFS=<white-space>:
+ Don't know what POSIX thinks of this. AT&T ksh does not do this.
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="$IFS:"
+ set --
+ showargs "${@:-}"
+expected-stdout:
+ <> .
+---
+name: IFS-subst-1
+description:
+ Simple test, IFS=<white-space>:
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="$IFS:"
+ x=":b: :"
+ echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+ echo -n '2:'; for i in :b:: ; do echo -n " [$i]" ; done ; echo
+ showargs 3 $x
+ showargs 4 :b::
+ x="a:b:"
+ echo -n '5:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+ showargs 6 $x
+ x="a::c"
+ echo -n '7:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+ showargs 8 $x
+ echo -n '9:'; for i in ${FOO-`echo -n h:i`th:ere} ; do echo -n " [$i]" ; done ; echo
+ showargs 10 ${FOO-`echo -n h:i`th:ere}
+ showargs 11 "${FOO-`echo -n h:i`th:ere}"
+ x=" A : B::D"
+ echo -n '12:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+ showargs 13 $x
+expected-stdout:
+ 1: [] [b] []
+ 2: [:b::]
+ <3> <> <b> <> .
+ <4> <:b::> .
+ 5: [a] [b]
+ <6> <a> <b> .
+ 7: [a] [] [c]
+ <8> <a> <> <c> .
+ 9: [h] [ith] [ere]
+ <10> <h> <ith> <ere> .
+ <11> <h:ith:ere> .
+ 12: [A] [B] [] [D]
+ <13> <A> <B> <> <D> .
+---
+name: IFS-subst-2
+description:
+ Check leading whitespace after trim does not make a field
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ x="X 1 2"
+ showargs 1 shift ${x#X}
+expected-stdout:
+ <1> <shift> <1> <2> .
+---
+name: IFS-subst-3-arr
+description:
+ Check leading IFS non-whitespace after trim does make a field
+ but leading IFS whitespace does not, nor empty replacements
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ showargs 0 ${-+}
+ IFS=:
+ showargs 1 ${-+:foo:bar}
+ IFS=' '
+ showargs 2 ${-+ foo bar}
+expected-stdout:
+ <0> .
+ <1> <> <foo> <bar> .
+ <2> <foo> <bar> .
+---
+name: IFS-subst-3-ass
+description:
+ Check non-field semantics
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ showargs 0 x=${-+}
+ IFS=:
+ showargs 1 x=${-+:foo:bar}
+ IFS=' '
+ showargs 2 x=${-+ foo bar}
+expected-stdout:
+ <0> <x=> .
+ <1> <x=> <foo> <bar> .
+ <2> <x=> <foo> <bar> .
+---
+name: IFS-subst-3-lcl
+description:
+ Check non-field semantics, smaller corner case (LP#1381965)
+stdin:
+ set -x
+ local regex=${2:-}
+ exit 1
+expected-exit: e != 0
+expected-stderr-pattern:
+ /regex=/
+---
+name: IFS-subst-4-1
+description:
+ reported by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a
+ IFS= ; q="$*" ; nq=$*
+ pfn "$*" $* "$q" "$nq"
+ [ "$q" = "$nq" ] && echo =true || echo =false
+expected-stdout:
+ <spacedivdedargument
+ here>
+ <space>
+ <divded>
+ <argument
+ here>
+ <spacedivdedargument
+ here>
+ <spacedivdedargument
+ here>
+ =true
+---
+name: IFS-subst-4-2
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a
+ IFS= ; q="$@" ; nq=$@
+ pfn "$*" $* "$q" "$nq"
+ [ "$q" = "$nq" ] && echo =true || echo =false
+expected-stdout:
+ <spacedivdedargument
+ here>
+ <space>
+ <divded>
+ <argument
+ here>
+ <space divded argument
+ here>
+ <space divded argument
+ here>
+ =true
+---
+name: IFS-subst-4-3
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a; IFS=
+ qs="$*"
+ nqs=$*
+ qk="$@"
+ nqk=$@
+ print -nr -- '= qs '; pfn "$qs"
+ print -nr -- '=nqs '; pfn "$nqs"
+ print -nr -- '= qk '; pfn "$qk"
+ print -nr -- '=nqk '; pfn "$nqk"
+ print -nr -- '~ qs '; pfn "$*"
+ print -nr -- '~nqs '; pfn $*
+ print -nr -- '~ qk '; pfn "$@"
+ print -nr -- '~nqk '; pfn $@
+expected-stdout:
+ = qs <spacedivdedargument
+ here>
+ =nqs <spacedivdedargument
+ here>
+ = qk <space divded argument
+ here>
+ =nqk <space divded argument
+ here>
+ ~ qs <spacedivdedargument
+ here>
+ ~nqs <space>
+ <divded>
+ <argument
+ here>
+ ~ qk <space>
+ <divded>
+ <argument
+ here>
+ ~nqk <space>
+ <divded>
+ <argument
+ here>
+---
+name: IFS-subst-4-4
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a; IFS=
+ qs="$*"
+ print -nr -- '= qs '; pfn "$qs"
+ print -nr -- '~ qs '; pfn "$*"
+ nqs=$*
+ print -nr -- '=nqs '; pfn "$nqs"
+ print -nr -- '~nqs '; pfn $*
+ qk="$@"
+ print -nr -- '= qk '; pfn "$qk"
+ print -nr -- '~ qk '; pfn "$@"
+ nqk=$@
+ print -nr -- '=nqk '; pfn "$nqk"
+ print -nr -- '~nqk '; pfn $@
+expected-stdout:
+ = qs <spacedivdedargument
+ here>
+ ~ qs <spacedivdedargument
+ here>
+ =nqs <spacedivdedargument
+ here>
+ ~nqs <space>
+ <divded>
+ <argument
+ here>
+ = qk <space divded argument
+ here>
+ ~ qk <space>
+ <divded>
+ <argument
+ here>
+ =nqk <space divded argument
+ here>
+ ~nqk <space>
+ <divded>
+ <argument
+ here>
+---
+name: IFS-subst-4-4p
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a; IFS=
+ unset v
+ qs=${v:-"$*"}
+ print -nr -- '= qs '; pfn "$qs"
+ print -nr -- '~ qs '; pfn ${v:-"$*"}
+ nqs=${v:-$*}
+ print -nr -- '=nqs '; pfn "$nqs"
+ print -nr -- '~nqs '; pfn ${v:-$*}
+ qk=${v:-"$@"}
+ print -nr -- '= qk '; pfn "$qk"
+ print -nr -- '~ qk '; pfn ${v:-"$@"}
+ nqk=${v:-$@}
+ print -nr -- '=nqk '; pfn "$nqk"
+ print -nr -- '~nqk '; pfn ${v:-$@}
+expected-stdout:
+ = qs <spacedivdedargument
+ here>
+ ~ qs <spacedivdedargument
+ here>
+ =nqs <spacedivdedargument
+ here>
+ ~nqs <space>
+ <divded>
+ <argument
+ here>
+ = qk <space divded argument
+ here>
+ ~ qk <space>
+ <divded>
+ <argument
+ here>
+ =nqk <space divded argument
+ here>
+ ~nqk <space>
+ <divded>
+ <argument
+ here>
+---
+name: IFS-subst-4-5
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a; IFS=,
+ qs="$*"
+ print -nr -- '= qs '; pfn "$qs"
+ print -nr -- '~ qs '; pfn "$*"
+ nqs=$*
+ print -nr -- '=nqs '; pfn "$nqs"
+ print -nr -- '~nqs '; pfn $*
+ qk="$@"
+ print -nr -- '= qk '; pfn "$qk"
+ print -nr -- '~ qk '; pfn "$@"
+ nqk=$@
+ print -nr -- '=nqk '; pfn "$nqk"
+ print -nr -- '~nqk '; pfn $@
+expected-stdout:
+ = qs <space,divded,argument
+ here>
+ ~ qs <space,divded,argument
+ here>
+ =nqs <space,divded,argument
+ here>
+ ~nqs <space>
+ <divded>
+ <argument
+ here>
+ = qk <space divded argument
+ here>
+ ~ qk <space>
+ <divded>
+ <argument
+ here>
+ =nqk <space divded argument
+ here>
+ ~nqk <space>
+ <divded>
+ <argument
+ here>
+---
+name: IFS-subst-4-5p
+description:
+ extended testsuite based on problem by mikeserv
+stdin:
+ pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; }
+ a='space divded argument
+ here'
+ IFS=\ ; set -- $a; IFS=,
+ unset v
+ qs=${v:-"$*"}
+ print -nr -- '= qs '; pfn "$qs"
+ print -nr -- '~ qs '; pfn ${v:-"$*"}
+ nqs=${v:-$*}
+ print -nr -- '=nqs '; pfn "$nqs"
+ print -nr -- '~nqs '; pfn ${v:-$*}
+ qk=${v:-"$@"}
+ print -nr -- '= qk '; pfn "$qk"
+ print -nr -- '~ qk '; pfn ${v:-"$@"}
+ nqk=${v:-$@}
+ print -nr -- '=nqk '; pfn "$nqk"
+ print -nr -- '~nqk '; pfn ${v:-$@}
+expected-stdout:
+ = qs <space,divded,argument
+ here>
+ ~ qs <space,divded,argument
+ here>
+ =nqs <space,divded,argument
+ here>
+ ~nqs <space>
+ <divded>
+ <argument
+ here>
+ = qk <space divded argument
+ here>
+ ~ qk <space>
+ <divded>
+ <argument
+ here>
+ =nqk <space divded argument
+ here>
+ ~nqk <space>
+ <divded>
+ <argument
+ here>
+---
+name: IFS-subst-5
+description:
+ extended testsuite based on IFS-subst-3
+ differs slightly from ksh93:
+ - omit trailing field in a3zna, a7ina (unquoted $@ expansion)
+ - has extra middle fields in b5ins, b7ina (IFS_NWS unquoted expansion)
+ differs slightly from bash:
+ - omit leading field in a5ins, a7ina (IFS_NWS unquoted expansion)
+ differs slightly from zsh:
+ - differs in assignment, not expansion; probably zsh bug
+ - has extra middle fields in b5ins, b7ina (IFS_NWS unquoted expansion)
+ 'emulate sh' zsh has extra fields in
+ - a5ins (IFS_NWS unquoted $*)
+ - b5ins, matching mksh’s
+ !!WARNING!! more to come: http://austingroupbugs.net/view.php?id=888
+stdin:
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- "" 2 ""; pfb $*; x=$*; pfn "$x"'
+ echo '=a1zns'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- "" 2 ""; pfb "$*"; x="$*"; pfn "$x"'
+ echo '=a2zqs'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- "" 2 ""; pfb $@; x=$@; pfn "$x"'
+ echo '=a3zna'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- "" 2 ""; pfb "$@"; x="$@"; pfn "$x"'
+ echo '=a4zqa'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- "" 2 ""; pfb $*; x=$*; pfn "$x"'
+ echo '=a5ins'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- "" 2 ""; pfb "$*"; x="$*"; pfn "$x"'
+ echo '=a6iqs'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- "" 2 ""; pfb $@; x=$@; pfn "$x"'
+ echo '=a7ina'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- "" 2 ""; pfb "$@"; x="$@"; pfn "$x"'
+ echo '=a8iqa'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- A B "" "" C; pfb $*; x=$*; pfn "$x"'
+ echo '=b1zns'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- A B "" "" C; pfb "$*"; x="$*"; pfn "$x"'
+ echo '=b2zqs'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- A B "" "" C; pfb $@; x=$@; pfn "$x"'
+ echo '=b3zna'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=; set -- A B "" "" C; pfb "$@"; x="$@"; pfn "$x"'
+ echo '=b4zqa'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- A B "" "" C; pfb $*; x=$*; pfn "$x"'
+ echo '=b5ins'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- A B "" "" C; pfb "$*"; x="$*"; pfn "$x"'
+ echo '=b6iqs'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- A B "" "" C; pfb $@; x=$@; pfn "$x"'
+ echo '=b7ina'
+ "$__progname" -c 'pfb() { for s_arg in "$@"; do print -r -- "[$s_arg]"; done; }; pfn() { for s_arg in "$@"; do print -r -- "<$s_arg>"; done; };
+ IFS=,; set -- A B "" "" C; pfb "$@"; x="$@"; pfn "$x"'
+ echo '=b8iqa'
+expected-stdout:
+ [2]
+ <2>
+ =a1zns
+ [2]
+ <2>
+ =a2zqs
+ [2]
+ < 2 >
+ =a3zna
+ []
+ [2]
+ []
+ < 2 >
+ =a4zqa
+ [2]
+ <,2,>
+ =a5ins
+ [,2,]
+ <,2,>
+ =a6iqs
+ [2]
+ < 2 >
+ =a7ina
+ []
+ [2]
+ []
+ < 2 >
+ =a8iqa
+ [A]
+ [B]
+ [C]
+ <ABC>
+ =b1zns
+ [ABC]
+ <ABC>
+ =b2zqs
+ [A]
+ [B]
+ [C]
+ <A B C>
+ =b3zna
+ [A]
+ [B]
+ []
+ []
+ [C]
+ <A B C>
+ =b4zqa
+ [A]
+ [B]
+ []
+ []
+ [C]
+ <A,B,,,C>
+ =b5ins
+ [A,B,,,C]
+ <A,B,,,C>
+ =b6iqs
+ [A]
+ [B]
+ []
+ []
+ [C]
+ <A B C>
+ =b7ina
+ [A]
+ [B]
+ []
+ []
+ [C]
+ <A B C>
+ =b8iqa
+---
+name: IFS-subst-6
+description:
+ Regression wrt. vector expansion in trim
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS=
+ x=abc
+ set -- a b
+ showargs ${x#$*}
+expected-stdout:
+ <c> .
+---
+name: IFS-subst-7
+description:
+ ksh93 bug wrt. vector expansion in trim
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS="*"
+ a=abcd
+ set -- '' c
+ showargs "$*" ${a##"$*"}
+expected-stdout:
+ <*c> <abcd> .
+---
+name: IFS-subst-8
+description:
+ http://austingroupbugs.net/view.php?id=221
+stdin:
+ n() { echo "$#"; }; n "${foo-$@}"
+expected-stdout:
+ 1
+---
+name: IFS-subst-9
+description:
+ Scalar context for $*/$@ in [[ and case
+stdin:
+ "$__progname" -c 'IFS=; set a b; [[ $* = "$1$2" ]]; echo 1 $?' sh a b
+ "$__progname" -c 'IFS=; [[ $* = ab ]]; echo 2 "$?"' sh a b
+ "$__progname" -c 'IFS=; [[ "$*" = ab ]]; echo 3 "$?"' sh a b
+ "$__progname" -c 'IFS=; [[ $* = a ]]; echo 4 "$?"' sh a b
+ "$__progname" -c 'IFS=; [[ "$*" = a ]]; echo 5 "$?"' sh a b
+ "$__progname" -c 'IFS=; [[ "$@" = a ]]; echo 6 "$?"' sh a b
+ "$__progname" -c 'IFS=; case "$@" in a) echo 7 a;; ab) echo 7 b;; a\ b) echo 7 ok;; esac' sh a b
+ "$__progname" -c 'IFS=; case $* in a) echo 8 a;; ab) echo 8 ok;; esac' sh a b
+ "$__progname" -c 'pfsp() { for s_arg in "$@"; do print -nr -- "<$s_arg> "; done; print .; }; IFS=; star=$* at="$@"; pfsp 9 "$star" "$at"' sh a b
+expected-stdout:
+ 1 0
+ 2 0
+ 3 0
+ 4 1
+ 5 1
+ 6 1
+ 7 ok
+ 8 ok
+ <9> <ab> <a b> .
+---
+name: IFS-subst-10
+description:
+ Scalar context in ${var=$subst}
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ set -- one "two three" four
+ unset -v var
+ save_IFS=$IFS
+ IFS=
+ set -- ${var=$*}
+ IFS=$save_IFS
+ echo "var=$var"
+ showargs "$@"
+expected-stdout:
+ var=onetwo threefour
+ <onetwo threefour> .
+---
+name: IFS-subst-11
+description:
+ Check leading non-whitespace after trim makes only one field
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ v="foo!one!two!three"
+ IFS="!"
+ showargs x ${v:3} y
+expected-stdout:
+ <x> <> <one> <two> <three> <y> .
+---
+name: IFS-arith-1
+description:
+ http://austingroupbugs.net/view.php?id=832
+stdin:
+ ${ZSH_VERSION+false} || emulate sh
+ ${BASH_VERSION+set -o posix}
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ IFS=0
+ showargs $((1230456))
+expected-stdout:
+ <123> <456> .
+---
+name: integer-base-err-1
+description:
+ Can't have 0 base (causes shell to exit)
+expected-exit: e != 0
+stdin:
+ typeset -i i
+ i=3
+ i=0#4
+ echo $i
+expected-stderr-pattern:
+ /^.*:.*0#4.*\n$/
+---
+name: integer-base-err-2
+description:
+ Can't have multiple bases in a 'constant' (causes shell to exit)
+ (ksh88 fails this test)
+expected-exit: e != 0
+stdin:
+ typeset -i i
+ i=3
+ i=2#110#11
+ echo $i
+expected-stderr-pattern:
+ /^.*:.*2#110#11.*\n$/
+---
+name: integer-base-err-3
+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
+ typeset -i4 a=10
+ typeset -i a=2+
+ echo $a
+ typeset -i4 a=10
+ typeset -i2 a=2+
+ echo $a
+expected-stderr-pattern:
+ /^([#\$] )?.*:.*2+.*\n.*:.*2+.*\n$/
+expected-stdout:
+ 4#22
+ 4#22
+---
+name: integer-base-err-4
+description:
+ Are invalid digits (according to base) errors?
+ (ksh93 fails this test)
+expected-exit: e != 0
+stdin:
+ typeset -i i;
+ i=3#4
+expected-stderr-pattern:
+ /^([#\$] )?.*:.*3#4.*\n$/
+---
+name: integer-base-1
+description:
+ Missing number after base is treated as 0.
+stdin:
+ typeset -i i
+ i=3
+ i=2#
+ echo $i
+expected-stdout:
+ 0
+---
+name: integer-base-2
+description:
+ Check 'stickyness' of base in various situations
+stdin:
+ typeset -i i=8
+ echo $i
+ echo ---------- A
+ typeset -i4 j=8
+ echo $j
+ echo ---------- B
+ typeset -i k=8
+ typeset -i4 k=8
+ echo $k
+ echo ---------- C
+ typeset -i4 l
+ l=3#10
+ echo $l
+ echo ---------- D
+ typeset -i m
+ m=3#10
+ echo $m
+ echo ---------- E
+ n=2#11
+ typeset -i n
+ echo $n
+ n=10
+ echo $n
+ echo ---------- F
+ typeset -i8 o=12
+ typeset -i4 o
+ echo $o
+ echo ---------- G
+ typeset -i p
+ let p=8#12
+ echo $p
+expected-stdout:
+ 8
+ ---------- A
+ 4#20
+ ---------- B
+ 4#20
+ ---------- C
+ 4#3
+ ---------- D
+ 3#10
+ ---------- E
+ 2#11
+ 2#1010
+ ---------- F
+ 4#30
+ ---------- G
+ 8#12
+---
+name: integer-base-3
+description:
+ More base parsing (hmm doesn't test much..)
+stdin:
+ typeset -i aa
+ aa=1+12#10+2
+ echo $aa
+ typeset -i bb
+ bb=1+$aa
+ echo $bb
+ typeset -i bb
+ bb=$aa
+ echo $bb
+ typeset -i cc
+ cc=$aa
+ echo $cc
+expected-stdout:
+ 15
+ 16
+ 15
+ 15
+---
+name: integer-base-4
+description:
+ Check that things not declared as integers are not made integers,
+ also, check if base is not reset by -i with no arguments.
+ (ksh93 fails - prints 10#20 - go figure)
+stdin:
+ xx=20
+ let xx=10
+ typeset -i | grep '^xx='
+ typeset -i4 a=10
+ typeset -i a=20
+ echo $a
+expected-stdout:
+ 4#110
+---
+name: integer-base-5
+description:
+ More base stuff
+stdin:
+ typeset -i4 a=3#10
+ echo $a
+ echo --
+ typeset -i j=3
+ j='~3'
+ echo $j
+ echo --
+ typeset -i k=1
+ x[k=k+1]=3
+ echo $k
+ echo --
+ typeset -i l
+ for l in 1 2+3 4; do echo $l; done
+expected-stdout:
+ 4#3
+ --
+ -4
+ --
+ 2
+ --
+ 1
+ 5
+ 4
+---
+name: integer-base-6
+description:
+ Even more base stuff
+ (ksh93 fails this test - prints 0)
+stdin:
+ typeset -i7 i
+ i=
+ echo $i
+expected-stdout:
+ 7#0
+---
+name: integer-base-7
+description:
+ Check that non-integer parameters don't get bases assigned
+stdin:
+ echo $(( zz = 8#100 ))
+ echo $zz
+expected-stdout:
+ 64
+ 64
+---
+name: integer-base-8
+description:
+ Check that base-36 works (full span)
+stdin:
+ echo 1:$((36#109AZ)).
+ typeset -i36 x=1691675
+ echo 2:$x.
+ typeset -Uui36 x
+ echo 3:$x.
+expected-stdout:
+ 1:1691675.
+ 2:36#109az.
+ 3:36#109AZ.
+---
+name: integer-base-check-flat
+description:
+ Check behaviour does not match POSuX (except if set -o posix),
+ because a not type-safe scripting language has *no* business
+ interpreting the string "010" as octal number eight (dangerous).
+stdin:
+ echo 1 "$("$__progname" -c 'echo :$((10))/$((010)),$((0x10)):')" .
+ echo 2 "$("$__progname" -o posix -c 'echo :$((10))/$((010)),$((0x10)):')" .
+ echo 3 "$("$__progname" -o sh -c 'echo :$((10))/$((010)),$((0x10)):')" .
+expected-stdout:
+ 1 :10/10,16: .
+ 2 :10/8,16: .
+ 3 :10/10,16: .
+---
+name: integer-base-check-numeric-from-1
+description:
+ Check behaviour for base one
+category: !shell:ebcdic-yes
+stdin:
+ echo 1:$((1#1))0.
+expected-stdout:
+ 1:490.
+---
+name: integer-base-check-numeric-from-1-ebcdic
+description:
+ Check behaviour for base one
+category: !shell:ebcdic-no
+stdin:
+ echo 1:$((1#1))0.
+expected-stdout:
+ 1:2410.
+---
+name: integer-base-check-numeric-from-2
+description:
+ Check behaviour for base two to 36, and that 37 degrades to 10
+stdin:
+ i=1
+ while (( ++i <= 37 )); do
+ eval 'echo '$i':$(('$i'#10)).'
+ done
+ echo 37:$($__progname -c 'echo $((37#10))').$?:
+expected-stdout:
+ 2:2.
+ 3:3.
+ 4:4.
+ 5:5.
+ 6:6.
+ 7:7.
+ 8:8.
+ 9:9.
+ 10:10.
+ 11:11.
+ 12:12.
+ 13:13.
+ 14:14.
+ 15:15.
+ 16:16.
+ 17:17.
+ 18:18.
+ 19:19.
+ 20:20.
+ 21:21.
+ 22:22.
+ 23:23.
+ 24:24.
+ 25:25.
+ 26:26.
+ 27:27.
+ 28:28.
+ 29:29.
+ 30:30.
+ 31:31.
+ 32:32.
+ 33:33.
+ 34:34.
+ 35:35.
+ 36:36.
+ 37:10.
+ 37:10.0:
+---
+name: integer-base-check-numeric-to-1
+description:
+ Check behaviour for base one
+category: !shell:ebcdic-yes
+stdin:
+ i=1
+ typeset -Uui$i x=0x40
+ eval "typeset -i10 y=$x"
+ print $i:$x.$y.
+expected-stdout:
+ 1:1#@.64.
+---
+name: integer-base-check-numeric-to-1-ebcdic
+description:
+ Check behaviour for base one
+category: !shell:ebcdic-no
+stdin:
+ i=1
+ typeset -Uui$i x=0x7C
+ eval "typeset -i10 y=$x"
+ print $i:$x.$y.
+expected-stdout:
+ 1:1#@.124.
+---
+name: integer-base-check-numeric-to-2
+description:
+ Check behaviour for base two to 36, and that 37 degrades to 10
+stdin:
+ i=1
+ while (( ++i <= 37 )); do
+ typeset -Uui$i x=0x40
+ eval "typeset -i10 y=$x"
+ print $i:$x.$y.
+ done
+expected-stdout:
+ 2:2#1000000.64.
+ 3:3#2101.64.
+ 4:4#1000.64.
+ 5:5#224.64.
+ 6:6#144.64.
+ 7:7#121.64.
+ 8:8#100.64.
+ 9:9#71.64.
+ 10:64.64.
+ 11:11#59.64.
+ 12:12#54.64.
+ 13:13#4C.64.
+ 14:14#48.64.
+ 15:15#44.64.
+ 16:16#40.64.
+ 17:17#3D.64.
+ 18:18#3A.64.
+ 19:19#37.64.
+ 20:20#34.64.
+ 21:21#31.64.
+ 22:22#2K.64.
+ 23:23#2I.64.
+ 24:24#2G.64.
+ 25:25#2E.64.
+ 26:26#2C.64.
+ 27:27#2A.64.
+ 28:28#28.64.
+ 29:29#26.64.
+ 30:30#24.64.
+ 31:31#22.64.
+ 32:32#20.64.
+ 33:33#1V.64.
+ 34:34#1U.64.
+ 35:35#1T.64.
+ 36:36#1S.64.
+ 37:64.64.
+---
+name: integer-arithmetic-span
+description:
+ Check wraparound and size that is defined in mksh
+category: int:32
+stdin:
+ echo s:$((2147483647+1)).$(((2147483647*2)+1)).$(((2147483647*2)+2)).
+ echo u:$((#2147483647+1)).$((#(2147483647*2)+1)).$((#(2147483647*2)+2)).
+expected-stdout:
+ s:-2147483648.-1.0.
+ u:2147483648.4294967295.0.
+---
+name: integer-arithmetic-span-64
+description:
+ Check wraparound and size that is defined in mksh
+category: int:64
+stdin:
+ echo s:$((9223372036854775807+1)).$(((9223372036854775807*2)+1)).$(((9223372036854775807*2)+2)).
+ echo u:$((#9223372036854775807+1)).$((#(9223372036854775807*2)+1)).$((#(9223372036854775807*2)+2)).
+expected-stdout:
+ s:-9223372036854775808.-1.0.
+ u:9223372036854775808.18446744073709551615.0.
+---
+name: integer-size-FAIL-to-detect
+description:
+ Notify the user that their ints are not 32 or 64 bit
+category: int:u
+stdin:
+ :
+---
+name: lineno-stdin
+description:
+ See if $LINENO is updated and can be modified.
+stdin:
+ echo A $LINENO
+ echo B $LINENO
+ LINENO=20
+ echo C $LINENO
+expected-stdout:
+ A 1
+ B 2
+ C 20
+---
+name: lineno-inc
+description:
+ See if $LINENO is set for .'d files.
+file-setup: file 644 "dotfile"
+ echo dot A $LINENO
+ echo dot B $LINENO
+ LINENO=20
+ echo dot C $LINENO
+stdin:
+ echo A $LINENO
+ echo B $LINENO
+ . ./dotfile
+expected-stdout:
+ A 1
+ B 2
+ dot A 1
+ dot B 2
+ dot C 20
+---
+name: lineno-func
+description:
+ See if $LINENO is set for commands in a function.
+stdin:
+ echo A $LINENO
+ echo B $LINENO
+ bar() {
+ echo func A $LINENO
+ echo func B $LINENO
+ }
+ bar
+ echo C $LINENO
+expected-stdout:
+ A 1
+ B 2
+ func A 4
+ func B 5
+ C 8
+---
+name: lineno-unset
+description:
+ See if unsetting LINENO makes it non-magic.
+file-setup: file 644 "dotfile"
+ echo dot A $LINENO
+ echo dot B $LINENO
+stdin:
+ unset LINENO
+ echo A $LINENO
+ echo B $LINENO
+ bar() {
+ echo func A $LINENO
+ echo func B $LINENO
+ }
+ bar
+ . ./dotfile
+ echo C $LINENO
+expected-stdout:
+ A
+ B
+ func A
+ func B
+ dot A
+ dot B
+ C
+---
+name: lineno-unset-use
+description:
+ See if unsetting LINENO makes it non-magic even
+ when it is re-used.
+file-setup: file 644 "dotfile"
+ echo dot A $LINENO
+ echo dot B $LINENO
+stdin:
+ unset LINENO
+ LINENO=3
+ echo A $LINENO
+ echo B $LINENO
+ bar() {
+ echo func A $LINENO
+ echo func B $LINENO
+ }
+ bar
+ . ./dotfile
+ echo C $LINENO
+expected-stdout:
+ A 3
+ B 3
+ func A 3
+ func B 3
+ dot A 3
+ dot B 3
+ C 3
+---
+name: lineno-trap
+description:
+ Check if LINENO is tracked in traps
+stdin:
+ fail() {
+ echo "line <$1>"
+ exit 1
+ }
+ trap 'fail $LINENO' INT ERR
+ false
+expected-stdout:
+ line <6>
+expected-exit: 1
+---
+name: lineno-eval-alias
+description:
+ Check if LINENO is trapped in eval and aliases
+stdin:
+ ${ZSH_VERSION+false} || emulate sh; echo $LINENO
+ echo $LINENO
+ eval ' echo $LINENO
+ echo $LINENO
+ echo $LINENO'
+ echo $LINENO
+expected-stdout:
+ 1
+ 2
+ 3
+ 3
+ 3
+ 6
+---
+name: unknown-trap
+description:
+ Ensure unknown traps are not a syntax error
+stdin:
+ (
+ trap "echo trap 1 executed" UNKNOWNSIGNAL || echo "foo"
+ echo =1
+ trap "echo trap 2 executed" UNKNOWNSIGNAL EXIT 999999 FNORD
+ echo = $?
+ ) 2>&1 | sed "s^${__progname%.exe}\.*e*x*e*: <stdin>\[[0-9]*]PROG"
+expected-stdout:
+ PROG: trap: bad signal 'UNKNOWNSIGNAL'
+ foo
+ =1
+ PROG: trap: bad signal 'UNKNOWNSIGNAL'
+ PROG: trap: bad signal '999999'
+ PROG: trap: bad signal 'FNORD'
+ = 1
+ trap 2 executed
+---
+name: read-IFS-1
+description:
+ Simple test, default IFS
+stdin:
+ echo "A B " > IN
+ unset x y z
+ read x y z < IN
+ echo 1: "x[$x] y[$y] z[$z]"
+ echo 1a: ${z-z not set}
+ read x < IN
+ echo 2: "x[$x]"
+expected-stdout:
+ 1: x[A] y[B] z[]
+ 1a:
+ 2: x[A B]
+---
+name: read-IFS-2
+description:
+ Complex tests, IFS either colon (IFS-NWS) or backslash (tricky)
+stdin:
+ n=0
+ showargs() { print -nr "$1"; shift; for s_arg in "$@"; do print -nr -- " [$s_arg]"; done; print; }
+ (IFS=\\ a=\<\\\>; showargs 3 $a)
+ (IFS=: b=\<:\>; showargs 4 $b)
+ print -r '<\>' | (IFS=\\ read f g; showargs 5 "$f" "$g")
+ print -r '<\\>' | (IFS=\\ read f g; showargs 6 "$f" "$g")
+ print '<\\\n>' | (IFS=\\ read f g; showargs 7 "$f" "$g")
+ print -r '<\>' | (IFS=\\ read f; showargs 8 "$f")
+ print -r '<\\>' | (IFS=\\ read f; showargs 9 "$f")
+ print '<\\\n>' | (IFS=\\ read f; showargs 10 "$f")
+ print -r '<\>' | (IFS=\\ read -r f g; showargs 11 "$f" "$g")
+ print -r '<\\>' | (IFS=\\ read -r f g; showargs 12 "$f" "$g")
+ print '<\\\n>' | (IFS=\\ read -r f g; showargs 13 "$f" "$g")
+ print -r '<\>' | (IFS=\\ read -r f; showargs 14 "$f")
+ print -r '<\\>' | (IFS=\\ read -r f; showargs 15 "$f")
+ print '<\\\n>' | (IFS=\\ read -r f; showargs 16 "$f")
+ print -r '<:>' | (IFS=: read f g; showargs 17 "$f" "$g")
+ print -r '<::>' | (IFS=: read f g; showargs 18 "$f" "$g")
+ print '<:\n>' | (IFS=: read f g; showargs 19 "$f" "$g")
+ print -r '<:>' | (IFS=: read f; showargs 20 "$f")
+ print -r '<::>' | (IFS=: read f; showargs 21 "$f")
+ print '<:\n>' | (IFS=: read f; showargs 22 "$f")
+ print -r '<:>' | (IFS=: read -r f g; showargs 23 "$f" "$g")
+ print -r '<::>' | (IFS=: read -r f g; showargs 24 "$f" "$g")
+ print '<:\n>' | (IFS=: read -r f g; showargs 25 "$f" "$g")
+ print -r '<:>' | (IFS=: read -r f; showargs 26 "$f")
+ print -r '<::>' | (IFS=: read -r f; showargs 27 "$f")
+ print '<:\n>' | (IFS=: read -r f; showargs 28 "$f")
+expected-stdout:
+ 3 [<] [>]
+ 4 [<] [>]
+ 5 [<] [>]
+ 6 [<] [>]
+ 7 [<>] []
+ 8 [<>]
+ 9 [<\>]
+ 10 [<>]
+ 11 [<] [>]
+ 12 [<] [\>]
+ 13 [<] []
+ 14 [<\>]
+ 15 [<\\>]
+ 16 [<]
+ 17 [<] [>]
+ 18 [<] [:>]
+ 19 [<] []
+ 20 [<:>]
+ 21 [<::>]
+ 22 [<]
+ 23 [<] [>]
+ 24 [<] [:>]
+ 25 [<] []
+ 26 [<:>]
+ 27 [<::>]
+ 28 [<]
+---
+name: read-ksh-1
+description:
+ If no var specified, REPLY is used
+stdin:
+ echo "abc" > IN
+ read < IN
+ echo "[$REPLY]";
+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() {
+ print -n '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.
+stdin:
+ echo foo[
+ n=bar
+ echo "hi[ $n ]=1"
+expected-stdout:
+ foo[
+ hi[ bar ]=1
+---
+name: regression-2
+description:
+ When PATH is set before running a command, the new path is
+ not used in doing the path search
+ $ echo echo hi > /tmp/q ; chmod a+rx /tmp/q
+ $ PATH=/tmp q
+ q: not found
+ $
+ in comexec() the two lines
+ while (*vp != NULL)
+ (void) typeset(*vp++, xxx, 0);
+ need to be moved out of the switch to before findcom() is
+ called - I don't know what this will break.
+stdin:
+ : "${PWD:-`pwd 2> /dev/null`}"
+ : "${PWD:?"PWD not set - cannot do test"}"
+ mkdir Y
+ cat > Y/xxxscript << EOF
+ #!/bin/sh
+ # Need to restore path so echo can be found (some shells don't have
+ # it as a built-in)
+ PATH=\$OLDPATH
+ echo hi
+ exit 0
+ EOF
+ chmod a+rx Y/xxxscript
+ export OLDPATH="$PATH"
+ PATH=$PWD/Y xxxscript
+ exit $?
+expected-stdout:
+ hi
+---
+name: regression-6
+description:
+ Parsing of $(..) expressions is non-optimal. It is
+ impossible to have any parentheses inside the expression.
+ I.e.,
+ $ ksh -c 'echo $(echo \( )'
+ no closing quote
+ $ ksh -c 'echo $(echo "(" )'
+ no closing quote
+ $
+ The solution is to hack the parsing clode in lex.c, the
+ question is how to hack it: should any parentheses be
+ escaped by a backslash, or should recursive parsing be done
+ (so quotes could also be used to hide hem). The former is
+ easier, the later better...
+stdin:
+ echo $(echo \( )
+ echo $(echo "(" )
+expected-stdout:
+ (
+ (
+---
+name: regression-9
+description:
+ Continue in a for loop does not work right:
+ for i in a b c ; do
+ if [ $i = b ] ; then
+ continue
+ fi
+ echo $i
+ done
+ Prints a forever...
+stdin:
+ first=yes
+ for i in a b c ; do
+ if [ $i = b ] ; then
+ if [ $first = no ] ; then
+ echo 'continue in for loop broken'
+ break # hope break isn't broken too :-)
+ fi
+ first=no
+ continue
+ fi
+ done
+ echo bye
+expected-stdout:
+ bye
+---
+name: regression-10
+description:
+ The following:
+ set -- `false`
+ echo $?
+ should print 0 according to POSIX (dash, bash, ksh93, posh)
+ but not 0 according to the getopt(1) manual page, ksh88, and
+ Bourne sh (such as /bin/sh on Solaris).
+ We honour POSIX except when -o sh is set.
+category: shell:legacy-no
+stdin:
+ showf() {
+ [[ -o posix ]]; FPOSIX=$((1-$?))
+ [[ -o sh ]]; FSH=$((1-$?))
+ echo -n "FPOSIX=$FPOSIX FSH=$FSH "
+ }
+ set +o posix +o sh
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o sh
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o posix
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o posix -o sh
+ showf
+ set -- `false`
+ echo rv=$?
+expected-stdout:
+ FPOSIX=0 FSH=0 rv=0
+ FPOSIX=0 FSH=1 rv=1
+ FPOSIX=1 FSH=0 rv=0
+ FPOSIX=1 FSH=1 rv=0
+---
+name: regression-10-legacy
+description:
+ The following:
+ set -- `false`
+ echo $?
+ should print 0 according to POSIX (dash, bash, ksh93, posh)
+ but not 0 according to the getopt(1) manual page, ksh88, and
+ Bourne sh (such as /bin/sh on Solaris).
+category: shell:legacy-yes
+stdin:
+ showf() {
+ [[ -o posix ]]; FPOSIX=$((1-$?))
+ [[ -o sh ]]; FSH=$((1-$?))
+ echo -n "FPOSIX=$FPOSIX FSH=$FSH "
+ }
+ set +o posix +o sh
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o sh
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o posix
+ showf
+ set -- `false`
+ echo rv=$?
+ set -o posix -o sh
+ showf
+ set -- `false`
+ echo rv=$?
+expected-stdout:
+ FPOSIX=0 FSH=0 rv=1
+ FPOSIX=0 FSH=1 rv=1
+ FPOSIX=1 FSH=0 rv=0
+ FPOSIX=1 FSH=1 rv=0
+---
+name: regression-11
+description:
+ The following:
+ x=/foo/bar/blah
+ echo ${x##*/}
+ should echo blah but on some machines echos /foo/bar/blah.
+stdin:
+ x=/foo/bar/blah
+ echo ${x##*/}
+expected-stdout:
+ blah
+---
+name: regression-12
+description:
+ Both of the following echos produce the same output under sh/ksh.att:
+ #!/bin/sh
+ x="foo bar"
+ echo "`echo \"$x\"`"
+ echo "`echo "$x"`"
+ pdksh produces different output for the former (foo instead of foo\tbar)
+stdin:
+ x="foo bar"
+ echo "`echo \"$x\"`"
+ echo "`echo "$x"`"
+expected-stdout:
+ foo bar
+ foo bar
+---
+name: regression-13
+description:
+ The following command hangs forever:
+ $ (: ; cat /etc/termcap) | sleep 2
+ This is because the shell forks a shell to run the (..) command
+ and this shell has the pipe open. When the sleep dies, the cat
+ doesn't get a SIGPIPE 'cause a process (ie, the second shell)
+ still has the pipe open.
+
+ NOTE: this test provokes a bizarre bug in ksh93 (shell starts reading
+ commands from /etc/termcap..)
+time-limit: 10
+stdin:
+ echo A line of text that will be duplicated quite a number of times.> t1
+ cat t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 > t2
+ cat t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 > t1
+ cat t1 t1 t1 t1 > t2
+ (: ; cat t2 2>/dev/null) | sleep 1
+---
+name: regression-14
+description:
+ The command
+ $ (foobar) 2> /dev/null
+ generates no output under /bin/sh, but pdksh produces the error
+ foobar: not found
+ Also, the command
+ $ foobar 2> /dev/null
+ generates an error under /bin/sh and pdksh, but AT&T ksh88 produces
+ no error (redirected to /dev/null).
+stdin:
+ (you/should/not/see/this/error/1) 2> /dev/null
+ you/should/not/see/this/error/2 2> /dev/null
+ true
+---
+name: regression-15
+description:
+ The command
+ $ whence foobar
+ generates a blank line under pdksh and sets the exit status to 0.
+ AT&T ksh88 generates no output and sets the exit status to 1. Also,
+ the command
+ $ whence foobar cat
+ generates no output under AT&T ksh88 (pdksh generates a blank line
+ and /bin/cat).
+stdin:
+ whence does/not/exist > /dev/null
+ echo 1: $?
+ echo 2: $(whence does/not/exist | wc -l)
+ echo 3: $(whence does/not/exist cat | wc -l)
+expected-stdout:
+ 1: 1
+ 2: 0
+ 3: 0
+---
+name: regression-16
+description:
+ ${var%%expr} seems to be broken in many places. On the mips
+ the commands
+ $ read line < /etc/passwd
+ $ echo $line
+ root:0:1:...
+ $ echo ${line%%:*}
+ root
+ $ echo $line
+ root
+ $
+ change the value of line. On sun4s & pas, the echo ${line%%:*} doesn't
+ work. Haven't checked elsewhere...
+script:
+ read x
+ y=$x
+ echo ${x%%:*}
+ echo $x
+stdin:
+ root:asdjhasdasjhs:0:1:Root:/:/bin/sh
+expected-stdout:
+ root
+ root:asdjhasdasjhs:0:1:Root:/:/bin/sh
+---
+name: regression-17
+description:
+ The command
+ . /foo/bar
+ should set the exit status to non-zero (sh and AT&T ksh88 do).
+ XXX doting a non existent file is a fatal error for a script
+stdin:
+ . does/not/exist
+expected-exit: e != 0
+expected-stderr-pattern: /.?/
+---
+name: regression-19
+description:
+ Both of the following echos should produce the same thing, but don't:
+ $ x=foo/bar
+ $ echo ${x%/*}
+ foo
+ $ echo "${x%/*}"
+ foo/bar
+stdin:
+ x=foo/bar
+ echo "${x%/*}"
+expected-stdout:
+ foo
+---
+name: regression-21
+description:
+ backslash does not work as expected in case labels:
+ $ x='-x'
+ $ case $x in
+ -\?) echo hi
+ esac
+ hi
+ $ x='-?'
+ $ case $x in
+ -\\?) echo hi
+ esac
+ hi
+ $
+stdin:
+ case -x in
+ -\?) echo fail
+ esac
+---
+name: regression-22
+description:
+ Quoting backquotes inside backquotes doesn't work:
+ $ echo `echo hi \`echo there\` folks`
+ asks for more info. sh and AT&T ksh88 both echo
+ hi there folks
+stdin:
+ echo `echo hi \`echo there\` folks`
+expected-stdout:
+ hi there folks
+---
+name: regression-23
+description:
+ )) is not treated `correctly':
+ $ (echo hi ; (echo there ; echo folks))
+ missing ((
+ $
+ instead of (as sh and ksh.att)
+ $ (echo hi ; (echo there ; echo folks))
+ hi
+ there
+ folks
+ $
+stdin:
+ ( : ; ( : ; echo hi))
+expected-stdout:
+ hi
+---
+name: regression-25
+description:
+ Check reading stdin in a while loop. The read should only read
+ a single line, not a whole stdio buffer; the cat should get
+ the rest.
+stdin:
+ (echo a; echo b) | while read x ; do
+ echo $x
+ cat > /dev/null
+ done
+expected-stdout:
+ a
+---
+name: regression-26
+description:
+ Check reading stdin in a while loop. The read should read both
+ lines, not just the first.
+script:
+ a=
+ while [ "$a" != xxx ] ; do
+ last=$x
+ read x
+ cat /dev/null | sed 's/x/y/'
+ a=x$a
+ done
+ echo $last
+stdin:
+ a
+ b
+expected-stdout:
+ b
+---
+name: regression-27
+description:
+ The command
+ . /does/not/exist
+ should cause a script to exit.
+stdin:
+ . does/not/exist
+ echo hi
+expected-exit: e != 0
+expected-stderr-pattern: /does\/not\/exist/
+---
+name: regression-28
+description:
+ variable assignements not detected well
+stdin:
+ a.x=1 echo hi
+expected-exit: e != 0
+expected-stderr-pattern: /a\.x=1/
+---
+name: regression-29
+description:
+ alias expansion different from AT&T ksh88
+stdin:
+ alias a='for ' b='i in'
+ a b hi ; do echo $i ; done
+expected-stdout:
+ hi
+---
+name: regression-30
+description:
+ strange characters allowed inside ${...}
+stdin:
+ echo ${a{b}}
+expected-exit: e != 0
+expected-stderr-pattern: /.?/
+---
+name: regression-31
+description:
+ Does read handle partial lines correctly
+script:
+ a= ret=
+ while [ "$a" != xxx ] ; do
+ read x y z
+ ret=$?
+ a=x$a
+ done
+ echo "[$x]"
+ echo $ret
+stdin: !
+ a A aA
+ b B Bb
+ c
+expected-stdout:
+ [c]
+ 1
+---
+name: regression-32
+description:
+ Does read set variables to null at eof?
+script:
+ a=
+ while [ "$a" != xxx ] ; do
+ read x y z
+ a=x$a
+ done
+ echo 1: ${x-x not set} ${y-y not set} ${z-z not set}
+ echo 2: ${x:+x not null} ${y:+y not null} ${z:+z not null}
+stdin:
+ a A Aa
+ b B Bb
+expected-stdout:
+ 1:
+ 2:
+---
+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:
+ 0222
+---
+name: regression-35
+description:
+ Tempory files used for here-docs in functions get trashed after
+ the function is parsed (before it is executed)
+stdin:
+ f1() {
+ cat <<- EOF
+ F1
+ EOF
+ f2() {
+ cat <<- EOF
+ F2
+ EOF
+ }
+ }
+ f1
+ f2
+ unset -f f1
+ f2
+expected-stdout:
+ F1
+ F2
+ F2
+---
+name: regression-36
+description:
+ Command substitution breaks reading in while loop
+ (test from <sjg@void.zen.oz.au>)
+stdin:
+ (echo abcdef; echo; echo 123) |
+ while read line
+ do
+ # the following line breaks it
+ c=`echo $line | wc -c`
+ echo $c
+ done
+expected-stdout:
+ 7
+ 1
+ 4
+---
+name: regression-37
+description:
+ Machines with broken times() (reported by <sjg@void.zen.oz.au>)
+ time does not report correct real time
+stdin:
+ time sleep 1
+expected-stderr-pattern: !/^\s*0\.0[\s\d]+real|^\s*real[\s]+0+\.0/
+---
+name: regression-38
+description:
+ set -e doesn't ignore exit codes for if/while/until/&&/||/!.
+arguments: !-e!
+stdin:
+ if false; then echo hi ; fi
+ false || true
+ false && true
+ while false; do echo hi; done
+ echo ok
+expected-stdout:
+ ok
+---
+name: regression-39
+description:
+ Only posh and oksh(2013-07) say “hi” below; FreeBSD sh,
+ GNU bash in POSIX mode, dash, ksh93, mksh don’t. All of
+ them exit 0. The POSIX behaviour is needed by BSD make.
+stdin:
+ set -e
+ echo `false; echo hi` $(<this-file-does-not-exist)
+ echo $?
+expected-stdout:
+
+ 0
+expected-stderr-pattern: /this-file-does-not-exist/
+---
+name: regression-40
+description:
+ This used to cause a core dump
+env-setup: !RANDOM=12!
+stdin:
+ echo hi
+expected-stdout:
+ hi
+---
+name: regression-41
+description:
+ foo should be set to bar (should not be empty)
+stdin:
+ foo=`
+ echo bar`
+ echo "($foo)"
+expected-stdout:
+ (bar)
+---
+name: regression-42
+description:
+ Can't use command line assignments to assign readonly parameters.
+stdin:
+ print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \
+ 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \
+ done >env; chmod +x env; PATH=.$PATHSEP$PATH
+ foo=bar
+ readonly foo
+ foo=stuff env | grep '^foo'
+expected-exit: e != 0
+expected-stderr-pattern:
+ /read-only/
+---
+name: regression-43
+description:
+ Can subshells be prefixed by redirections (historical shells allow
+ this)
+stdin:
+ < /dev/null (sed 's/^/X/')
+---
+name: regression-45
+description:
+ Parameter assignments with [] recognised correctly
+stdin:
+ FOO=*[12]
+ BAR=abc[
+ MORE=[abc]
+ JUNK=a[bc
+ echo "<$FOO>"
+ echo "<$BAR>"
+ echo "<$MORE>"
+ echo "<$JUNK>"
+expected-stdout:
+ <*[12]>
+ <abc[>
+ <[abc]>
+ <a[bc>
+---
+name: regression-46
+description:
+ Check that alias expansion works in command substitutions and
+ at the end of file.
+stdin:
+ alias x='echo hi'
+ FOO="`x` "
+ echo "[$FOO]"
+ x
+expected-stdout:
+ [hi ]
+ hi
+---
+name: regression-47
+description:
+ Check that aliases are fully read.
+stdin:
+ alias x='echo hi;
+ echo there'
+ x
+ echo done
+expected-stdout:
+ hi
+ there
+ done
+---
+name: regression-48
+description:
+ Check that (here doc) temp files are not left behind after an exec.
+stdin:
+ mkdir foo || exit 1
+ TMPDIR=$PWD/foo "$__progname" <<- 'EOF'
+ x() {
+ sed 's/^/X /' << E_O_F
+ hi
+ there
+ folks
+ E_O_F
+ echo "done ($?)"
+ }
+ echo=echo; [ -x /bin/echo ] && echo=/bin/echo
+ exec $echo subtest-1 hi
+ EOF
+ echo subtest-1 foo/*
+ TMPDIR=$PWD/foo "$__progname" <<- 'EOF'
+ echo=echo; [ -x /bin/echo ] && echo=/bin/echo
+ sed 's/^/X /' << E_O_F; exec $echo subtest-2 hi
+ a
+ few
+ lines
+ E_O_F
+ EOF
+ echo subtest-2 foo/*
+expected-stdout:
+ subtest-1 hi
+ subtest-1 foo/*
+ X a
+ X few
+ X lines
+ subtest-2 hi
+ subtest-2 foo/*
+---
+name: regression-49
+description:
+ Check that unset params with attributes are reported by set, those
+ sans attributes are not.
+stdin:
+ unset FOO BAR
+ echo X$FOO
+ export BAR
+ typeset -i BLAH
+ set | grep FOO
+ set | grep BAR
+ set | grep BLAH
+expected-stdout:
+ X
+ BAR
+ BLAH
+---
+name: regression-50
+description:
+ Check that aliases do not use continuation prompt after trailing
+ semi-colon.
+file-setup: file 644 "envf"
+ PS1=Y
+ PS2=X
+env-setup: !ENV=./envf!
+need-ctty: yes
+arguments: !-i!
+stdin:
+ alias foo='echo hi ; '
+ foo
+ foo echo there
+expected-stdout:
+ hi
+ hi
+ there
+expected-stderr: !
+ YYYY
+---
+name: regression-51
+description:
+ Check that set allows both +o and -o options on same command line.
+stdin:
+ set a b c
+ set -o noglob +o allexport
+ echo A: $*, *
+expected-stdout:
+ A: a b c, *
+---
+name: regression-52
+description:
+ Check that globbing works in pipelined commands
+file-setup: file 644 "envf"
+ PS1=P
+file-setup: file 644 "abc"
+ stuff
+env-setup: !ENV=./envf!
+need-ctty: yes
+arguments: !-i!
+stdin:
+ sed 's/^/X /' < ab*
+ echo mark 1
+ sed 's/^/X /' < ab* | sed 's/^/Y /'
+ echo mark 2
+expected-stdout:
+ X stuff
+ mark 1
+ Y X stuff
+ mark 2
+expected-stderr: !
+ PPPPP
+---
+name: regression-53
+description:
+ Check that getopts works in functions
+stdin:
+ bfunc() {
+ echo bfunc: enter "(args: $*; OPTIND=$OPTIND)"
+ while getopts B oc; do
+ case $oc in
+ (B)
+ echo bfunc: B option
+ ;;
+ (*)
+ echo bfunc: odd option "($oc)"
+ ;;
+ esac
+ done
+ echo bfunc: leave
+ }
+
+ function kfunc {
+ echo kfunc: enter "(args: $*; OPTIND=$OPTIND)"
+ while getopts K oc; do
+ case $oc in
+ (K)
+ echo kfunc: K option
+ ;;
+ (*)
+ echo bfunc: odd option "($oc)"
+ ;;
+ esac
+ done
+ echo kfunc: leave
+ }
+
+ set -- -f -b -k -l
+ echo "line 1: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 2: ret=$?, optc=$optc, OPTIND=$OPTIND"
+ bfunc -BBB blah
+ echo "line 3: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 4: ret=$?, optc=$optc, OPTIND=$OPTIND"
+ kfunc -KKK blah
+ echo "line 5: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 6: ret=$?, optc=$optc, OPTIND=$OPTIND"
+ echo
+
+ OPTIND=1
+ set -- -fbkl
+ echo "line 10: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 20: ret=$?, optc=$optc, OPTIND=$OPTIND"
+ bfunc -BBB blah
+ echo "line 30: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 40: ret=$?, optc=$optc, OPTIND=$OPTIND"
+ kfunc -KKK blah
+ echo "line 50: OPTIND=$OPTIND"
+ getopts kbfl optc
+ echo "line 60: ret=$?, optc=$optc, OPTIND=$OPTIND"
+expected-stdout:
+ line 1: OPTIND=1
+ line 2: ret=0, optc=f, OPTIND=2
+ bfunc: enter (args: -BBB blah; OPTIND=2)
+ bfunc: B option
+ bfunc: B option
+ bfunc: leave
+ line 3: OPTIND=2
+ line 4: ret=0, optc=b, OPTIND=3
+ kfunc: enter (args: -KKK blah; OPTIND=1)
+ kfunc: K option
+ kfunc: K option
+ kfunc: K option
+ kfunc: leave
+ line 5: OPTIND=3
+ line 6: ret=0, optc=k, OPTIND=4
+
+ line 10: OPTIND=1
+ line 20: ret=0, optc=f, OPTIND=2
+ bfunc: enter (args: -BBB blah; OPTIND=2)
+ bfunc: B option
+ bfunc: B option
+ bfunc: leave
+ line 30: OPTIND=2
+ line 40: ret=1, optc=?, OPTIND=2
+ kfunc: enter (args: -KKK blah; OPTIND=1)
+ kfunc: K option
+ kfunc: K option
+ kfunc: K option
+ kfunc: leave
+ line 50: OPTIND=2
+ line 60: ret=1, optc=?, OPTIND=2
+---
+name: regression-54
+description:
+ Check that ; is not required before the then in if (( ... )) then ...
+stdin:
+ if (( 1 )) then
+ echo ok dparen
+ fi
+ if [[ -n 1 ]] then
+ echo ok dbrackets
+ fi
+expected-stdout:
+ ok dparen
+ ok dbrackets
+---
+name: regression-55
+description:
+ Check ${foo:%bar} is allowed (ksh88 allows it...)
+stdin:
+ x=fooXbarXblah
+ echo 1 ${x%X*}
+ echo 2 ${x:%X*}
+ echo 3 ${x%%X*}
+ echo 4 ${x:%%X*}
+ echo 5 ${x#*X}
+ echo 6 ${x:#*X}
+ echo 7 ${x##*X}
+ echo 8 ${x:##*X}
+expected-stdout:
+ 1 fooXbar
+ 2 fooXbar
+ 3 foo
+ 4 foo
+ 5 barXblah
+ 6 barXblah
+ 7 blah
+ 8 blah
+---
+name: regression-57
+description:
+ Check if typeset output is correct for
+ uninitialised array elements.
+stdin:
+ typeset -i xxx[4]
+ echo A
+ typeset -i | grep xxx | sed 's/^/ /'
+ echo B
+ typeset | grep xxx | sed 's/^/ /'
+
+ xxx[1]=2+5
+ echo M
+ typeset -i | grep xxx | sed 's/^/ /'
+ echo N
+ typeset | grep xxx | sed 's/^/ /'
+expected-stdout:
+ A
+ xxx
+ B
+ typeset -i xxx
+ M
+ xxx[1]=7
+ N
+ set -A xxx
+ typeset -i xxx[1]
+---
+name: regression-58
+description:
+ Check if trap exit is ok (exit not mistaken for signal name)
+stdin:
+ trap 'echo hi' exit
+ trap exit 1
+expected-stdout:
+ hi
+---
+name: regression-59
+description:
+ Check if ${#array[*]} is calculated correctly.
+stdin:
+ a[12]=hi
+ a[8]=there
+ echo ${#a[*]}
+expected-stdout:
+ 2
+---
+name: regression-60
+description:
+ Check if default exit status is previous command
+stdin:
+ (true; exit)
+ echo A $?
+ (false; exit)
+ echo B $?
+ ( (exit 103) ; exit)
+ echo C $?
+expected-stdout:
+ A 0
+ B 1
+ C 103
+---
+name: regression-61
+description:
+ Check if EXIT trap is executed for sub shells.
+stdin:
+ trap 'echo parent exit' EXIT
+ echo start
+ (echo A; echo A last)
+ echo B
+ (echo C; trap 'echo sub exit' EXIT; echo C last)
+ echo parent last
+expected-stdout:
+ start
+ A
+ A last
+ B
+ C
+ C last
+ sub exit
+ parent last
+ parent exit
+---
+name: regression-62
+description:
+ Check if test -nt/-ot succeeds if second(first) file is missing.
+stdin:
+ matrix() {
+ local a b c d e f g h
+ test a -nt b; a=$?
+ test b -nt a; b=$?
+ test a -ot b; c=$?
+ test b -ot a; d=$?
+ test a -nt a; e=$?
+ test b -nt b; f=$?
+ test a -ot a; g=$?
+ test b -ot b; h=$?
+ echo $1 $a $b $c $d / $e $f $g $h .
+ }
+ matrix a
+ :>a
+ matrix b
+ sleep 2 # mtime granularity for OS/2 and FAT
+ :>b
+ matrix c
+ sleep 2
+ echo dummy >a # Debian GNU/Hurd #955270
+ matrix d
+ rm a
+ matrix e
+expected-stdout:
+ a 1 1 1 1 / 1 1 1 1 .
+ b 0 1 1 0 / 1 1 1 1 .
+ c 1 0 0 1 / 1 1 1 1 .
+ d 0 1 1 0 / 1 1 1 1 .
+ e 1 0 0 1 / 1 1 1 1 .
+---
+name: regression-63
+description:
+ Check if typeset, export, and readonly work
+stdin:
+ {
+ echo FNORD-0
+ FNORD_A=1
+ FNORD_B=2
+ FNORD_C=3
+ FNORD_D=4
+ FNORD_E=5
+ FNORD_F=6
+ FNORD_G=7
+ FNORD_H=8
+ integer FNORD_E FNORD_F FNORD_G FNORD_H
+ export FNORD_C FNORD_D FNORD_G FNORD_H
+ readonly FNORD_B FNORD_D FNORD_F FNORD_H
+ echo FNORD-1
+ export
+ echo FNORD-2
+ export -p
+ echo FNORD-3
+ readonly
+ echo FNORD-4
+ readonly -p
+ echo FNORD-5
+ typeset
+ echo FNORD-6
+ typeset -p
+ echo FNORD-7
+ typeset -
+ echo FNORD-8
+ } | fgrep FNORD
+ fnord=(42 23)
+ typeset -p fnord
+ echo FNORD-9
+expected-stdout:
+ FNORD-0
+ FNORD-1
+ FNORD_C
+ FNORD_D
+ FNORD_G
+ FNORD_H
+ FNORD-2
+ export FNORD_C=3
+ export FNORD_D=4
+ export FNORD_G=7
+ export FNORD_H=8
+ FNORD-3
+ FNORD_B
+ FNORD_D
+ FNORD_F
+ FNORD_H
+ FNORD-4
+ readonly FNORD_B=2
+ readonly FNORD_D=4
+ readonly FNORD_F=6
+ readonly FNORD_H=8
+ FNORD-5
+ typeset FNORD_A
+ typeset -r FNORD_B
+ typeset -x FNORD_C
+ typeset -x -r FNORD_D
+ typeset -i FNORD_E
+ typeset -i -r FNORD_F
+ typeset -i -x FNORD_G
+ typeset -i -x -r FNORD_H
+ FNORD-6
+ typeset FNORD_A=1
+ typeset -r FNORD_B=2
+ typeset -x FNORD_C=3
+ typeset -x -r FNORD_D=4
+ typeset -i FNORD_E=5
+ typeset -i -r FNORD_F=6
+ typeset -i -x FNORD_G=7
+ typeset -i -x -r FNORD_H=8
+ FNORD-7
+ FNORD_A=1
+ FNORD_B=2
+ FNORD_C=3
+ FNORD_D=4
+ FNORD_E=5
+ FNORD_F=6
+ FNORD_G=7
+ FNORD_H=8
+ FNORD-8
+ set -A fnord
+ typeset fnord[0]=42
+ typeset fnord[1]=23
+ FNORD-9
+---
+name: regression-64
+description:
+ Check that we can redefine functions calling time builtin
+stdin:
+ t() {
+ time >/dev/null
+ }
+ t 2>/dev/null
+ t() {
+ time
+ }
+---
+name: regression-65
+description:
+ check for a regression with sleep builtin and signal mask
+category: !nojsig
+time-limit: 5
+stdin:
+ sleep 1
+ echo blub |&
+ while read -p line; do :; done
+ echo ok
+expected-stdout:
+ ok
+---
+name: regression-66
+description:
+ Check that quoting is sane
+category: !nojsig
+stdin:
+ ac_space=' '
+ ac_newline='
+ '
+ set | grep ^ac_ |&
+ set -A lines
+ while IFS= read -pr line; do
+ if [[ $line = *space* ]]; then
+ lines[0]=$line
+ else
+ lines[1]=$line
+ fi
+ done
+ for line in "${lines[@]}"; do
+ print -r -- "$line"
+ done
+expected-stdout:
+ ac_space=' '
+ ac_newline=$'\n'
+---
+name: regression-67
+description:
+ Check that we can both break and use source on the same line
+stdin:
+ for s in s; do break; done; print -s s
+---
+name: regression-68
+description:
+ Check that all common arithmetic operators work as expected
+stdin:
+ echo 1 $(( a = 5 )) .
+ echo 2 $(( ++a )) , $(( a++ )) , $(( a )) .
+ echo 3 $(( --a )) , $(( a-- )) , $(( a )) .
+ echo 4 $(( a == 5 )) , $(( a == 6 )) .
+ echo 5 $(( a != 5 )) , $(( a != 6 )) .
+ echo 6 $(( a *= 3 )) .
+ echo 7 $(( a /= 5 )) .
+ echo 8 $(( a %= 2 )) .
+ echo 9 $(( a += 9 )) .
+ echo 10 $(( a -= 4 )) .
+ echo 11 $(( a <<= 1 )) .
+ echo 12 $(( a >>= 1 )) .
+ echo 13 $(( a &= 4 )) .
+ echo 14 $(( a ^= a )) .
+ echo 15 $(( a |= 5 )) .
+ echo 16 $(( 5 << 1 )) .
+ echo 17 $(( 5 >> 1 )) .
+ echo 18 $(( 5 <= 6 )) , $(( 5 <= 5 )) , $(( 5 <= 4 )) .
+ echo 19 $(( 5 >= 6 )) , $(( 5 >= 5 )) , $(( 5 >= 4 )) .
+ echo 20 $(( 5 < 6 )) , $(( 5 < 5 )) , $(( 5 < 4 )) .
+ echo 21 $(( 5 > 6 )) , $(( 5 > 5 )) , $(( 5 > 4 )) .
+ echo 22 $(( 0 && 0 )) , $(( 0 && 1 )) , $(( 1 && 0 )) , $(( 1 && 1 )) .
+ echo 23 $(( 0 || 0 )) , $(( 0 || 1 )) , $(( 1 || 0 )) , $(( 1 || 1 )) .
+ echo 24 $(( 5 * 3 )) .
+ echo 25 $(( 7 / 2 )) .
+ echo 26 $(( 5 % 5 )) , $(( 5 % 4 )) , $(( 5 % 1 )) , $(( 5 % -1 )) , $(( 5 % -2 )) .
+ echo 27 $(( 5 + 2 )) , $(( 5 + 0 )) , $(( 5 + -2 )) .
+ echo 28 $(( 5 - 2 )) , $(( 5 - 0 )) , $(( 5 - -2 )) .
+ echo 29 $(( 6 & 4 )) , $(( 6 & 8 )) .
+ echo 30 $(( 4 ^ 2 )) , $(( 4 ^ 4 )) .
+ echo 31 $(( 4 | 2 )) , $(( 4 | 4 )) , $(( 4 | 0 )) .
+ echo 32 $(( 0 ? 1 : 2 )) , $(( 3 ? 4 : 5 )) .
+ echo 33 $(( 5 , 2 , 3 )) .
+ echo 34 $(( ~0 )) , $(( ~1 )) , $(( ~~1 )) , $(( ~~2 )) .
+ echo 35 $(( !0 )) , $(( !1 )) , $(( !!1 )) , $(( !!2 )) .
+ echo 36 $(( (5) )) .
+expected-stdout:
+ 1 5 .
+ 2 6 , 6 , 7 .
+ 3 6 , 6 , 5 .
+ 4 1 , 0 .
+ 5 0 , 1 .
+ 6 15 .
+ 7 3 .
+ 8 1 .
+ 9 10 .
+ 10 6 .
+ 11 12 .
+ 12 6 .
+ 13 4 .
+ 14 0 .
+ 15 5 .
+ 16 10 .
+ 17 2 .
+ 18 1 , 1 , 0 .
+ 19 0 , 1 , 1 .
+ 20 1 , 0 , 0 .
+ 21 0 , 0 , 1 .
+ 22 0 , 0 , 0 , 1 .
+ 23 0 , 1 , 1 , 1 .
+ 24 15 .
+ 25 3 .
+ 26 0 , 1 , 0 , 0 , 1 .
+ 27 7 , 5 , 3 .
+ 28 3 , 5 , 7 .
+ 29 4 , 0 .
+ 30 6 , 0 .
+ 31 6 , 4 , 4 .
+ 32 2 , 4 .
+ 33 3 .
+ 34 -1 , -2 , 1 , 2 .
+ 35 1 , 0 , 1 , 1 .
+ 36 5 .
+---
+name: regression-69
+description:
+ Check that all non-lksh arithmetic operators work as expected
+category: shell:legacy-no
+stdin:
+ a=5 b=0x80000005
+ echo 1 $(( a ^<= 1 )) , $(( b ^<= 1 )) .
+ echo 2 $(( a ^>= 2 )) , $(( b ^>= 2 )) .
+ echo 3 $(( 5 ^< 1 )) .
+ echo 4 $(( 5 ^> 1 )) .
+expected-stdout:
+ 1 10 , 11 .
+ 2 -2147483646 , -1073741822 .
+ 3 10 .
+ 4 -2147483646 .
+---
+name: export-1
+description:
+ Check allexport works, basic
+stdin:
+ qa=1
+ set -A qb 2 3
+ set -a
+ qc=4
+ set -A qd 5 6
+ export -p | grep '^export q'
+expected-stdout:
+ export qc=4
+ export qd[0]=5
+ export qd[1]=6
+---
+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: readonly-4
+description:
+ Do not permit bypassing readonly for first array item
+stdin:
+ set -A arr -- foo bar
+ readonly arr
+ arr=baz
+ print -r -- "${arr[@]}"
+expected-exit: e != 0
+expected-stderr-pattern:
+ /read[ -]?only/
+---
+name: readonly-5
+description:
+ Ensure readonly is idempotent
+stdin:
+ readonly x=1
+ readonly x
+---
+name: syntax-1
+description:
+ Check that lone ampersand is a syntax error
+stdin:
+ &
+expected-exit: e != 0
+expected-stderr-pattern:
+ /syntax error/
+---
+name: xxx-quoted-newline-1
+description:
+ Check that \<newline> works inside of ${}
+stdin:
+ abc=2
+ echo ${ab\
+ c}
+expected-stdout:
+ 2
+---
+name: xxx-quoted-newline-2
+description:
+ Check that \<newline> works at the start of a here document
+stdin:
+ cat << EO\
+ F
+ hi
+ EOF
+expected-stdout:
+ hi
+---
+name: xxx-quoted-newline-3
+description:
+ Check that \<newline> works at the end of a here document
+stdin:
+ cat << EOF
+ hi
+ EO\
+ F
+expected-stdout:
+ hi
+---
+name: xxx-multi-assignment-cmd
+description:
+ Check that assignments in a command affect subsequent assignments
+ in the same command
+stdin:
+ FOO=abc
+ FOO=123 BAR=$FOO
+ echo $BAR
+expected-stdout:
+ 123
+---
+name: xxx-multi-assignment-posix-cmd
+description:
+ Check that the behaviour for multiple assignments with a
+ command name matches POSIX. See:
+ http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925
+stdin:
+ X=a Y=b; X=$Y Y=$X "$__progname" -c 'echo 1 $X $Y .'; echo 2 $X $Y .
+ unset X Y Z
+ X=a Y=${X=b} Z=$X "$__progname" -c 'echo 3 $Z .'
+ unset X Y Z
+ X=a Y=${X=b} Z=$X; echo 4 $Z .
+expected-stdout:
+ 1 b a .
+ 2 a b .
+ 3 b .
+ 4 a .
+---
+name: xxx-multi-assignment-posix-nocmd
+description:
+ Check that the behaviour for multiple assignments with no
+ command name matches POSIX (Debian #334182). See:
+ http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925
+stdin:
+ X=a Y=b; X=$Y Y=$X; echo 1 $X $Y .
+expected-stdout:
+ 1 b b .
+---
+name: xxx-multi-assignment-posix-subassign
+description:
+ Check that the behaviour for multiple assignments matches POSIX:
+ - The assignment words shall be expanded in the current execution
+ environment.
+ - The assignments happen in the temporary execution environment.
+stdin:
+ unset X Y Z
+ Z=a Y=${X:=b} sh -c 'echo +$X+ +$Y+ +$Z+'
+ echo /$X/
+ # Now for the special case:
+ unset X Y Z
+ X= Y=${X:=b} sh -c 'echo +$X+ +$Y+'
+ echo /$X/
+expected-stdout:
+ ++ +b+ +a+
+ /b/
+ ++ +b+
+ /b/
+---
+name: xxx-exec-environment-1
+description:
+ Check to see if exec sets it's environment correctly
+stdin:
+ print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \
+ 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \
+ done >env; chmod +x env; PATH=.$PATHSEP$PATH
+ FOO=bar exec env
+expected-stdout-pattern:
+ /(^|.*\n)FOO=bar\n/
+---
+name: xxx-exec-environment-2
+description:
+ Check to make sure exec doesn't change environment if a program
+ isn't exec-ed
+stdin:
+ print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \
+ 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \
+ done >env; chmod +x env; PATH=.$PATHSEP$PATH
+ env >bar1
+ FOO=bar exec; env >bar2
+ cmp -s bar1 bar2
+---
+name: exec-function-environment-1
+description:
+ Check assignments in function calls and whether they affect
+ the current execution environment
+stdin:
+ f() { a=2; }; g() { b=3; echo y$c-; }; a=1 f; b=2; c=1 g
+ echo x$a-$b- z$c-
+expected-stdout:
+ y1-
+ x-3- z-
+---
+name: exec-modern-korn-shell
+description:
+ Check that exec can execute any command that makes it
+ through syntax and parser
+stdin:
+ print '#!'"$__progname"'\necho tf' >lq
+ chmod +x lq
+ PATH=$PWD
+ exec 2>&1
+ foo() { print two; }
+ print =1
+ (exec print one)
+ print =2
+ (exec foo)
+ print =3
+ (exec ls)
+ print =4
+ (exec lq)
+expected-stdout-pattern:
+ /=1\none\n=2\ntwo\n=3\n.*: ls: inaccessible or not found\n=4\ntf\n/
+---
+name: exec-ksh88
+description:
+ Check that exec only executes after a PATH search
+arguments: !-o!posix!
+stdin:
+ print '#!'"$__progname"'\necho tf' >lq
+ chmod +x lq
+ PATH=$PWD
+ exec 2>&1
+ foo() { print two; }
+ print =1
+ (exec print one)
+ print =2
+ (exec foo)
+ print =3
+ (exec ls)
+ print =4
+ (exec lq)
+expected-stdout-pattern:
+ /=1\n.*: print: inaccessible or not found\n=2\n.*: foo: inaccessible or not found\n=3\n.*: ls: inaccessible or not found\n=4\ntf\n/
+---
+name: xxx-what-do-you-call-this-1
+stdin:
+ echo "${foo:-"a"}*"
+expected-stdout:
+ a*
+---
+name: xxx-prefix-strip-1
+stdin:
+ foo='a cdef'
+ echo ${foo#a c}
+expected-stdout:
+ def
+---
+name: xxx-prefix-strip-2
+stdin:
+ set a c
+ x='a cdef'
+ echo ${x#$*}
+expected-stdout:
+ def
+---
+name: xxx-variable-syntax-1
+stdin:
+ echo ${:}
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: xxx-variable-syntax-2
+stdin:
+ set 0
+ echo ${*:0}
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: xxx-variable-syntax-3
+stdin:
+ set -A foo 0
+ echo ${foo[*]:0}
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: xxx-variable-syntax-4
+description:
+ Not all kinds of trims are currently impossible, check those who do
+stdin:
+ foo() {
+ echo "<$*> X${*:+ }X"
+ }
+ foo a b
+ foo "" c
+ foo ""
+ foo "" ""
+ IFS=:
+ foo a b
+ foo "" c
+ foo ""
+ foo "" ""
+ IFS=
+ foo a b
+ foo "" c
+ foo ""
+ foo "" ""
+expected-stdout:
+ <a b> X X
+ < c> X X
+ <> XX
+ < > X X
+ <a:b> X X
+ <:c> X X
+ <> XX
+ <:> X X
+ <ab> X X
+ <c> X X
+ <> XX
+ <> XX
+---
+name: xxx-substitution-eval-order
+description:
+ Check order of evaluation of expressions
+stdin:
+ i=1 x= y=
+ set -A A abc def GHI j G k
+ echo ${A[x=(i+=1)]#${A[y=(i+=2)]}}
+ echo $x $y
+expected-stdout:
+ HI
+ 2 4
+---
+name: xxx-substitution-eval-order-2
+description:
+ Check some corner cases
+stdin:
+ unset var
+ i=42
+ : ${var+${q[i=777]}} required to be lazy by POSIX
+ echo 1=$i
+ var=meow
+ i=42
+ : ${var+${q[i=777]}} eval since var is now set
+ echo 2=$i
+ unset var
+ i=42
+ : ${var#${q[i=777]}} pattern is needed even if var is empty
+ echo 3=$i
+ var=meow
+ i=42
+ : ${var#${q[i=777]}}
+ echo 4=$i
+expected-stdout:
+ 1=42
+ 2=777
+ 3=777
+ 4=777
+---
+name: xxx-set-option-1
+description:
+ Check option parsing in set
+stdin:
+ set -vsA foo -- A 1 3 2
+ echo ${foo[*]}
+expected-stderr:
+ echo ${foo[*]}
+expected-stdout:
+ 1 2 3 A
+---
+name: xxx-exec-1
+description:
+ Check that exec exits for built-ins
+need-ctty: yes
+arguments: !-i!
+stdin:
+ exec echo hi
+ echo still herre
+expected-stdout:
+ hi
+expected-stderr-pattern: /.*/
+---
+name: xxx-while-1
+description:
+ Check the return value of while loops
+ XXX need to do same for for/select/until loops
+stdin:
+ i=x
+ while [ $i != xxx ] ; do
+ i=x$i
+ if [ $i = xxx ] ; then
+ false
+ continue
+ fi
+ done
+ echo loop1=$?
+
+ i=x
+ while [ $i != xxx ] ; do
+ i=x$i
+ if [ $i = xxx ] ; then
+ false
+ break
+ fi
+ done
+ echo loop2=$?
+
+ i=x
+ while [ $i != xxx ] ; do
+ i=x$i
+ false
+ done
+ echo loop3=$?
+expected-stdout:
+ loop1=0
+ loop2=0
+ loop3=1
+---
+name: xxx-status-1
+description:
+ Check that blank lines don't clear $?
+need-ctty: yes
+arguments: !-i!
+stdin:
+ (exit 1)
+ echo $?
+ (exit 1)
+
+ echo $?
+ true
+expected-stdout:
+ 1
+ 1
+expected-stderr-pattern: /.*/
+---
+name: xxx-status-2
+description:
+ Check that $? is preserved in subshells, includes, traps.
+stdin:
+ (exit 1)
+
+ echo blank: $?
+
+ (exit 2)
+ (echo subshell: $?)
+
+ echo 'echo include: $?' > foo
+ (exit 3)
+ . ./foo
+
+ trap 'echo trap: $?' ERR
+ (exit 4)
+ echo exit: $?
+expected-stdout:
+ blank: 1
+ subshell: 2
+ include: 3
+ trap: 4
+ exit: 4
+---
+name: xxx-stat-1
+description:
+ Check that tests on files are consistent
+ (fails when run as root, unfortunately)
+category: disabled
+stdin:
+ mkdir a
+ echo x >a/b
+ test -e a/b; echo 1e $? .
+ test -f a/b; echo 1f $? .
+ chmod 0 a
+ test -e a/b; echo 2e $? .
+ test -f a/b; echo 2f $? .
+ chmod 700 a
+ test -e a/b; echo 3e $? .
+ test -f a/b; echo 3f $? .
+expected-stdout:
+ 1e 0 .
+ 1f 0 .
+ 2e 1 .
+ 2f 1 .
+ 3e 0 .
+ 3f 0 .
+---
+name: xxx-clean-chars-1
+description:
+ Check MAGIC character is stuffed correctly
+stdin:
+ echo `echo [Ł`
+expected-stdout:
+ [Ł
+---
+name: xxx-param-subst-qmark-1
+description:
+ Check suppresion of error message with null string. According to
+ POSIX, it shouldn't print the error as 'word' isn't ommitted.
+ ksh88/93, Solaris /bin/sh and /usr/xpg4/bin/sh all print the error.
+stdin:
+ unset foo
+ x=
+ echo x${foo?$x}
+expected-exit: 1
+expected-stderr-pattern: !/not set/
+---
+name: xxx-param-subst-qmark-namespec
+description:
+ Check special names are output correctly
+stdin:
+ doit() {
+ "$__progname" -c "$@" >o1 2>o2
+ rv=$?
+ echo RETVAL: $rv
+ sed -e "s^${__progname%.exe}\.*e*x*e*: PROG: " -e 's/^/STDOUT: /g' <o1
+ sed -e "s^${__progname%.exe}\.*e*x*e*: PROG: " -e 's/^/STDERR: /g' <o2
+ }
+ doit 'echo ${1x}'
+ doit 'echo "${1x}"'
+ doit 'echo ${1?}'
+ doit 'echo ${19?}'
+ doit 'echo ${!:?}'
+ doit -u 'echo ${*:?}' foo ""
+expected-stdout:
+ RETVAL: 1
+ STDERR: PROG: ${1x}: bad substitution
+ RETVAL: 1
+ STDERR: PROG: ${1x}: bad substitution
+ RETVAL: 1
+ STDERR: PROG: 1: parameter null or not set
+ RETVAL: 1
+ STDERR: PROG: 19: parameter null or not set
+ RETVAL: 1
+ STDERR: PROG: !: parameter null or not set
+ RETVAL: 1
+ STDERR: foo: ${*:?}: bad substitution
+---
+name: xxx-param-_-1
+# fails due to weirdness of execv stuff
+category: !os:uwin-nt
+description:
+ Check c flag is set.
+arguments: !-c!echo "[$-]"!
+expected-stdout-pattern: /^\[.*c.*\]$/
+---
+name: tilde-expand-1
+description:
+ Check tilde expansion after equal signs
+env-setup: !HOME=/sweet!
+stdin:
+ echo ${A=a=}~ b=~ c=d~ ~
+ export e=~ f=d~
+ command command export g=~ h=d~
+ echo ". $e . $f ."
+ echo ". $g . $h ."
+ set -o posix
+ unset A e f g h
+ echo ${A=a=}~ b=~ c=d~ ~
+ export e=~ f=d~
+ command command export g=~ h=d~
+ echo ". $e . $f ."
+ echo ". $g . $h ."
+expected-stdout:
+ a=/sweet b=/sweet c=d~ /sweet
+ . /sweet . d~ .
+ . /sweet . d~ .
+ a=~ b=~ c=d~ /sweet
+ . /sweet . d~ .
+ . /sweet . d~ .
+---
+name: tilde-expand-2
+description:
+ Check tilde expansion works
+env-setup: !HOME=/sweet!
+stdin:
+ :>'c=a'
+ typeset c=[ab]
+ :>'d=a'
+ x=typeset; $x d=[ab]
+ echo "<$c>" "<$d>"
+ wd=$PWD
+ cd /
+ plus=$(print -r -- ~+)
+ minus=$(print -r -- ~-)
+ nix=$(print -r -- ~)
+ [[ $plus = / ]]; echo one $? .
+ [[ $minus = "$wd" ]]; echo two $? .
+ [[ $nix = /sweet ]]; echo nix $? .
+expected-stdout:
+ <[ab]> <a>
+ one 0 .
+ two 0 .
+ nix 0 .
+---
+name: tilde-expand-3
+description:
+ Check mostly Austin 351 stuff
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ set "1 b=2" "3 d=4"
+ export a=$1 \c=$2
+ showargs 1 "$a" "$b" "$c" "$d"
+ unset a b c d
+ HOME=/tmp
+ export \a=~ b=~
+ command export c=~
+ builtin export d=~
+ \\builtin export e=~
+ showargs 2 "$a" "$b" "$c" "$d" "$e" ksh
+ unset a b c d e
+ set -o posix
+ export \a=~ b=~
+ command export c=~
+ builtin export d=~
+ \\builtin export e=~
+ showargs 3 "$a" "$b" "$c" "$d" "$e" posix
+ unset a b c d e
+ set +o posix
+ export a=$1
+ showargs 4 "$a" "$b" ksh
+ unset a b
+ showargs 5 a=$1 ksh
+ export \a=$1
+ showargs 6 "$a" "$b" ksh
+ unset a b
+ set -o posix
+ export a=$1
+ showargs 7 "$a" "$b" posix
+ unset a b
+ showargs 8 a=$1 posix
+ export \a=$1
+ showargs 9 "$a" "$b" posix
+ unset a b
+ set +o posix
+ command echo 10 ksh a=~
+ command command export a=~
+ showargs 11 "$a"
+ unset a
+ set -o posix
+ command echo 12 posix a=~
+ command command export a=~
+ showargs 13 "$a"
+ unset a
+ # unspecified whether /tmp or ~
+ var=export; command $var a=~
+ showargs 14 "$a"
+ echo 'echo "<$foo>"' >bar
+ "$__progname" bar
+ var=foo
+ export $var=1
+ "$__progname" bar
+ export $var=~
+ "$__progname" bar
+ # unspecified
+ command -- export a=~
+ showargs 18 "$a"
+ set -A bla
+ typeset bla[1]=~:~
+ typeset -g gbl=~ g2=$1
+ local lcl=~ l2=$1
+ readonly ro=~ r2=$1
+ showargs 19 "${bla[1]}" a=~ "$gbl" "$lcl" "$ro" "$g2" "$l2" "$r2"
+ set +o posix
+ echo "20 some arbitrary stuff "=~
+ set -o posix
+ echo "21 some arbitrary stuff "=~
+expected-stdout:
+ <1> <1 b=2> <> <3> <4> .
+ <2> </tmp> </tmp> </tmp> </tmp> </tmp> <ksh> .
+ <3> <~> </tmp> </tmp> <~> </tmp> <posix> .
+ <4> <1 b=2> <> <ksh> .
+ <5> <a=1> <b=2> <ksh> .
+ <6> <1> <2> <ksh> .
+ <7> <1 b=2> <> <posix> .
+ <8> <a=1> <b=2> <posix> .
+ <9> <1> <2> <posix> .
+ 10 ksh a=/tmp
+ <11> </tmp> .
+ 12 posix a=~
+ <13> </tmp> .
+ <14> <~> .
+ <>
+ <1>
+ <~>
+ <18> <~> .
+ <19> </tmp:/tmp> <a=~> </tmp> </tmp> </tmp> <1 b=2> <1 b=2> <1 b=2> .
+ 20 some arbitrary stuff =/tmp
+ 21 some arbitrary stuff =~
+---
+name: exit-err-1
+description:
+ Check some "exit on error" conditions
+stdin:
+ print '#!'"$__progname"'\nexec "$1"' >env
+ print '#!'"$__progname"'\nexit 1' >false
+ chmod +x env false
+ PATH=.$PATHSEP$PATH
+ set -ex
+ env false && echo something
+ echo END
+expected-stdout:
+ END
+expected-stderr:
+ + env false
+ + echo END
+---
+name: exit-err-2
+description:
+ Check some "exit on error" edge conditions (POSIXly)
+stdin:
+ print '#!'"$__progname"'\nexec "$1"' >env
+ print '#!'"$__progname"'\nexit 1' >false
+ print '#!'"$__progname"'\nexit 0' >true
+ chmod +x env false
+ PATH=.$PATHSEP$PATH
+ set -ex
+ if env true; then
+ env false && echo something
+ fi
+ echo END
+expected-stdout:
+ END
+expected-stderr:
+ + env true
+ + env false
+ + echo END
+---
+name: exit-err-3
+description:
+ pdksh regression which AT&T ksh does right
+ TFM says: [set] -e | errexit
+ Exit (after executing the ERR trap) ...
+stdin:
+ trap 'echo EXIT' EXIT
+ trap 'echo ERR' ERR
+ set -e
+ cd /XXXXX 2>/dev/null
+ echo DONE
+ exit 0
+expected-stdout:
+ ERR
+ EXIT
+expected-exit: e != 0
+---
+name: exit-err-4
+description:
+ "set -e" test suite (POSIX)
+stdin:
+ set -e
+ echo pre
+ if true ; then
+ false && echo foo
+ fi
+ echo bar
+expected-stdout:
+ pre
+ bar
+---
+name: exit-err-5
+description:
+ "set -e" test suite (POSIX)
+stdin:
+ set -e
+ foo() {
+ while [ "$1" ]; do
+ for E in $x; do
+ [ "$1" = "$E" ] && { shift ; continue 2 ; }
+ done
+ x="$x $1"
+ shift
+ done
+ echo $x
+ }
+ echo pre
+ foo a b b c
+ echo post
+expected-stdout:
+ pre
+ a b c
+ post
+---
+name: exit-err-6
+description:
+ "set -e" test suite (BSD make)
+category: os:mirbsd
+stdin:
+ mkdir zd zd/a zd/b
+ print 'all:\n\t@echo eins\n\t@exit 42\n' >zd/a/Makefile
+ print 'all:\n\t@echo zwei\n' >zd/b/Makefile
+ wd=$(pwd)
+ set -e
+ for entry in a b; do ( set -e; if [[ -d $wd/zd/$entry.i386 ]]; then _newdir_="$entry.i386"; else _newdir_="$entry"; fi; if [[ -z $_THISDIR_ ]]; then _nextdir_="$_newdir_"; else _nextdir_="$_THISDIR_/$_newdir_"; fi; _makefile_spec_=; [[ ! -f $wd/zd/$_newdir_/Makefile.bsd-wrapper ]] || _makefile_spec_="-f Makefile.bsd-wrapper"; subskipdir=; for skipdir in ; do subentry=${skipdir#$entry}; if [[ $subentry != $skipdir ]]; then if [[ -z $subentry ]]; then echo "($_nextdir_ skipped)"; break; fi; subskipdir="$subskipdir ${subentry#/}"; fi; done; if [[ -z $skipdir || -n $subentry ]]; then echo "===> $_nextdir_"; cd $wd/zd/$_newdir_; make SKIPDIR="$subskipdir" $_makefile_spec_ _THISDIR_="$_nextdir_" all; fi; ) done 2>&1 | sed "s!$wd!WD!g"
+expected-stdout:
+ ===> a
+ eins
+ *** Error code 42
+
+ Stop in WD/zd/a (line 2 of Makefile).
+---
+name: exit-err-7
+description:
+ "set -e" regression (LP#1104543)
+stdin:
+ set -e
+ bla() {
+ [ -x $PWD/nonexistant ] && $PWD/nonexistant
+ }
+ echo x
+ bla
+ echo y$?
+expected-stdout:
+ x
+expected-exit: 1
+---
+name: exit-err-8
+description:
+ "set -e" regression (Debian #700526)
+stdin:
+ set -e
+ _db_cmd() { return $1; }
+ db_input() { _db_cmd 30; }
+ db_go() { _db_cmd 0; }
+ db_input || :
+ db_go
+ exit 0
+---
+name: exit-err-9
+description:
+ "set -e" versus bang pipelines
+stdin:
+ set -e
+ ! false | false
+ echo 1 ok
+ ! false && false
+ echo 2 wrong
+expected-stdout:
+ 1 ok
+expected-exit: 1
+---
+name: exit-err-10
+description:
+ Debian #269067 (cf. regression-38 but with eval)
+arguments: !-e!
+stdin:
+ eval false || true
+ echo = $? .
+expected-stdout:
+ = 0 .
+---
+name: exit-err-11
+description:
+ Fix -e inside eval, from Martijn Dekker; expected-stdout from ksh93
+stdin:
+ "$__progname" -c 'eval '\''echo ${-//[!eh]}; false; echo phantom e'\''; echo x$?'
+ echo = $?
+ "$__progname" -ec 'eval '\''echo ${-//[!eh]}; false; echo phantom e'\''; echo x$?'
+ echo = $?
+expected-stdout:
+ h
+ phantom e
+ x0
+ = 0
+ eh
+ = 1
+---
+name: exit-enoent-1
+description:
+ SUSv4 says that the shell should exit with 126/127 in some situations
+stdin:
+ i=0
+ (echo; echo :) >x
+ "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+ "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+ echo exit 42 >x
+ "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+ "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+ rm -f x
+ "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+ "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+expected-stdout:
+ 0 0 .
+ 1 126 .
+ 2 42 .
+ 3 126 .
+ 4 127 .
+ 5 127 .
+---
+name: exit-eval-1
+description:
+ Check eval vs substitution exit codes (ksh93 alike)
+stdin:
+ (exit 12)
+ eval $(false)
+ echo A $?
+ (exit 12)
+ eval ' $(false)'
+ echo B $?
+ (exit 12)
+ eval " $(false)"
+ echo C $?
+ (exit 12)
+ eval "eval $(false)"
+ echo D $?
+ (exit 12)
+ eval 'eval '"$(false)"
+ echo E $?
+ IFS="$IFS:"
+ (exit 12)
+ eval $(echo :; false)
+ echo F $?
+ echo -n "G "
+ (exit 12)
+ eval 'echo $?'
+ echo H $?
+expected-stdout:
+ A 0
+ B 1
+ C 0
+ D 0
+ E 0
+ F 0
+ G 12
+ H 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: exit-trap-2
+description:
+ Check that ERR and EXIT traps are run just like GNU bash does.
+ ksh93 runs ERtrap after “parameter null or not set” (which mksh
+ used to do) but (bug) continues “and out”, exit 0, in +e eval-undef.
+file-setup: file 644 "x"
+ v=; unset v
+ trap 'echo EXtrap' EXIT
+ trap 'echo ERtrap' ERR
+ set $1
+ echo "and run $2"
+ eval $2
+ echo and out
+file-setup: file 644 "xt"
+ v=; unset v
+ trap 'echo EXtrap' EXIT
+ trap 'echo ERtrap' ERR
+ set $1
+ echo 'and run true'
+ true
+ echo and out
+file-setup: file 644 "xf"
+ v=; unset v
+ trap 'echo EXtrap' EXIT
+ trap 'echo ERtrap' ERR
+ set $1
+ echo 'and run false'
+ false
+ echo and out
+file-setup: file 644 "xu"
+ v=; unset v
+ trap 'echo EXtrap' EXIT
+ trap 'echo ERtrap' ERR
+ set $1
+ echo 'and run ${v?}'
+ ${v?}
+ echo and out
+stdin:
+ runtest() {
+ rm -f rc
+ (
+ "$__progname" "$@"
+ echo $? >rc
+ ) 2>&1 | sed \
+ -e 's/parameter not set/parameter null or not set/' \
+ -e 's/[[]6]//' -e 's/: eval: line 1//' -e 's/: line 6//' \
+ -e "s^${__progname%.exe}\.*e*x*e*: <stdin>\[[0-9]*]PROG"
+ }
+ xe=-e
+ echo : $xe
+ runtest x $xe true
+ echo = eval-true $(<rc) .
+ runtest x $xe false
+ echo = eval-false $(<rc) .
+ runtest x $xe '${v?}'
+ echo = eval-undef $(<rc) .
+ runtest xt $xe
+ echo = noeval-true $(<rc) .
+ runtest xf $xe
+ echo = noeval-false $(<rc) .
+ runtest xu $xe
+ echo = noeval-undef $(<rc) .
+ xe=+e
+ echo : $xe
+ runtest x $xe true
+ echo = eval-true $(<rc) .
+ runtest x $xe false
+ echo = eval-false $(<rc) .
+ runtest x $xe '${v?}'
+ echo = eval-undef $(<rc) .
+ runtest xt $xe
+ echo = noeval-true $(<rc) .
+ runtest xf $xe
+ echo = noeval-false $(<rc) .
+ runtest xu $xe
+ echo = noeval-undef $(<rc) .
+expected-stdout:
+ : -e
+ and run true
+ and out
+ EXtrap
+ = eval-true 0 .
+ and run false
+ ERtrap
+ EXtrap
+ = eval-false 1 .
+ and run ${v?}
+ x: v: parameter null or not set
+ EXtrap
+ = eval-undef 1 .
+ and run true
+ and out
+ EXtrap
+ = noeval-true 0 .
+ and run false
+ ERtrap
+ EXtrap
+ = noeval-false 1 .
+ and run ${v?}
+ xu: v: parameter null or not set
+ EXtrap
+ = noeval-undef 1 .
+ : +e
+ and run true
+ and out
+ EXtrap
+ = eval-true 0 .
+ and run false
+ ERtrap
+ ERtrap
+ and out
+ EXtrap
+ = eval-false 0 .
+ and run ${v?}
+ x: v: parameter null or not set
+ EXtrap
+ = eval-undef 1 .
+ and run true
+ and out
+ EXtrap
+ = noeval-true 0 .
+ and run false
+ ERtrap
+ and out
+ EXtrap
+ = noeval-false 0 .
+ and run ${v?}
+ xu: v: parameter null or not set
+ EXtrap
+ = noeval-undef 1 .
+---
+name: exit-trap-3
+description:
+ Check that the EXIT trap is run in many places, Debian #910276
+stdin:
+ fkt() {
+ trap -- "echo $1 >&2" EXIT
+ }
+ fkt shell_exit
+ $(fkt fn_exit)
+ $(trap -- "echo comsub_exit >&2" EXIT)
+ (trap -- "echo subshell_exit >&2" EXIT)
+expected-stderr:
+ fn_exit
+ comsub_exit
+ subshell_exit
+ shell_exit
+---
+name: exit-trap-interactive
+description:
+ Check that interactive shell doesn't exit via EXIT trap on syntax error
+arguments: !-i!
+stdin:
+ trap -- EXIT
+ echo Syntax error <
+ echo 'After error 1'
+ trap 'echo Exit trap' EXIT
+ echo Syntax error <
+ echo 'After error 2'
+ trap 'echo Exit trap' EXIT
+ exit
+ echo 'After exit'
+expected-stdout:
+ After error 1
+ After error 2
+ Exit trap
+expected-stderr-pattern:
+ /syntax error: unexpected 'newline'/
+---
+name: test-stlt-1
+description:
+ Check that test also can handle string1 < string2 etc.
+stdin:
+ test 2005/10/08 '<' 2005/08/21 && echo ja || echo nein
+ test 2005/08/21 \< 2005/10/08 && echo ja || echo nein
+ test 2005/10/08 '>' 2005/08/21 && echo ja || echo nein
+ test 2005/08/21 \> 2005/10/08 && echo ja || echo nein
+expected-stdout:
+ nein
+ ja
+ ja
+ nein
+expected-stderr-pattern: !/unexpected op/
+---
+name: test-str-pattern
+description:
+ Check that [[ x = $y ]] can take extglobs, like ksh93
+stdin:
+ [[ -n $BASH_VERSION ]] && shopt -s extglob
+ function one {
+ n=$1 x=$2 y=$3 z=${4:-$3}
+ [[ $x = $y ]]; a=$?
+ [[ $x = "$y" ]]; b=$?
+ eval '[[ $x = '"$z"' ]]; c=$?'
+ eval '[[ $x = "'"$z"'" ]]; d=$?'
+ echo $n $a $b $c $d .
+ }
+ x='a\'
+ [[ $x = a\ ]]; echo 01 $? .
+ [[ $x = a\\ ]]; echo 02 $? .
+ one 03 'a\' 'a\' 'a\\'
+ one 04 'a\b' 'a\b'
+ one 05 'a\b' 'a\\b'
+ one 06 'foo' 'f+(o)'
+ one 07 'f+(o)' 'f+(o)'
+ one 08 'f+(o' 'f+(o' 'f+\(o'
+ one 09 foo 'f+(o' 'f+\(o'
+ one 10 abcde 'a\*e'
+ one 11 'a*e' 'a\*e'
+ one 12 'a\*e' 'a\*e'
+ echo extras:
+ x='f+(o'
+ z='f+(o'
+ eval '[[ $x = "'"$z"'" ]]; echo 14 $? "(08:4)" .'
+ x=foo
+ eval '[[ $x = "'"$z"'" ]]; echo 15 $? "(09:4)" .'
+expected-stdout:
+ 01 1 .
+ 02 0 .
+ 03 0 0 0 0 .
+ 04 1 0 1 0 .
+ 05 0 1 0 0 .
+ 06 0 1 0 1 .
+ 07 1 0 1 0 .
+ 08 0 0 0 1 .
+ 09 1 1 1 1 .
+ 10 1 1 1 1 .
+ 11 0 1 0 1 .
+ 12 1 0 1 0 .
+ extras:
+ 14 0 (08:4) .
+ 15 1 (09:4) .
+---
+name: test-precedence-1
+description:
+ Check a weird precedence case (and POSIX echo)
+stdin:
+ test \( -f = -f \)
+ rv=$?
+ echo $rv
+expected-stdout:
+ 0
+---
+name: test-option-1
+description:
+ Test the test -o operator
+stdin:
+ runtest() {
+ test -o $1; echo $?
+ [ -o $1 ]; echo $?
+ [[ -o $1 ]]; echo $?
+ }
+ if_test() {
+ test -o $1 -o -o !$1; echo $?
+ [ -o $1 -o -o !$1 ]; echo $?
+ [[ -o $1 || -o !$1 ]]; echo $?
+ test -o ?$1; echo $?
+ }
+ echo 0y $(if_test utf8-mode) =
+ echo 0n $(if_test utf8-hack) =
+ echo 1= $(runtest utf8-hack) =
+ echo 2= $(runtest !utf8-hack) =
+ echo 3= $(runtest ?utf8-hack) =
+ set +U
+ echo 1+ $(runtest utf8-mode) =
+ echo 2+ $(runtest !utf8-mode) =
+ echo 3+ $(runtest ?utf8-mode) =
+ set -U
+ echo 1- $(runtest utf8-mode) =
+ echo 2- $(runtest !utf8-mode) =
+ echo 3- $(runtest ?utf8-mode) =
+ echo = short flags =
+ echo 0y $(if_test -U) =
+ echo 0y $(if_test +U) =
+ echo 0n $(if_test -_) =
+ echo 0n $(if_test -U-) =
+ echo 1= $(runtest -_) =
+ echo 2= $(runtest !-_) =
+ echo 3= $(runtest ?-_) =
+ set +U
+ echo 1+ $(runtest -U) =
+ echo 2+ $(runtest !-U) =
+ echo 3+ $(runtest ?-U) =
+ echo 1+ $(runtest +U) =
+ echo 2+ $(runtest !+U) =
+ echo 3+ $(runtest ?+U) =
+ set -U
+ echo 1- $(runtest -U) =
+ echo 2- $(runtest !-U) =
+ echo 3- $(runtest ?-U) =
+ echo 1- $(runtest +U) =
+ echo 2- $(runtest !+U) =
+ echo 3- $(runtest ?+U) =
+expected-stdout:
+ 0y 0 0 0 0 =
+ 0n 1 1 1 1 =
+ 1= 1 1 1 =
+ 2= 1 1 1 =
+ 3= 1 1 1 =
+ 1+ 1 1 1 =
+ 2+ 0 0 0 =
+ 3+ 0 0 0 =
+ 1- 0 0 0 =
+ 2- 1 1 1 =
+ 3- 0 0 0 =
+ = short flags =
+ 0y 0 0 0 0 =
+ 0y 0 0 0 0 =
+ 0n 1 1 1 1 =
+ 0n 1 1 1 1 =
+ 1= 1 1 1 =
+ 2= 1 1 1 =
+ 3= 1 1 1 =
+ 1+ 1 1 1 =
+ 2+ 0 0 0 =
+ 3+ 0 0 0 =
+ 1+ 1 1 1 =
+ 2+ 0 0 0 =
+ 3+ 0 0 0 =
+ 1- 0 0 0 =
+ 2- 1 1 1 =
+ 3- 0 0 0 =
+ 1- 0 0 0 =
+ 2- 1 1 1 =
+ 3- 0 0 0 =
+---
+name: test-varset-1
+description:
+ Test the test -v operator
+stdin:
+ [[ -v a ]]
+ rv=$?; echo $((++i)) $rv
+ a=
+ [[ -v a ]]
+ rv=$?; echo $((++i)) $rv
+ unset a
+ [[ -v a ]]
+ rv=$?; echo $((++i)) $rv
+ a=x
+ [[ -v a ]]
+ rv=$?; echo $((++i)) $rv
+ nameref b=a
+ [[ -v b ]]
+ rv=$?; echo $((++i)) $rv
+ unset a
+ [[ -v b ]]
+ rv=$?; echo $((++i)) $rv
+ x[1]=y
+ [[ -v x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -v x[0] ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -v x[1] ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -v x[2] ]]
+ rv=$?; echo $((++i)) $rv
+expected-stdout:
+ 1 1
+ 2 0
+ 3 1
+ 4 0
+ 5 0
+ 6 1
+ 7 1
+ 8 1
+ 9 0
+ 10 1
+---
+name: test-varset-2
+description:
+ test -v works only on scalars
+stdin:
+ [[ -v x[*] ]]
+ echo ok
+expected-exit: e != 0
+expected-stderr-pattern:
+ /unexpected '\*'/
+---
+name: test-stnze-1
+description:
+ Check that the short form [ $x ] works
+stdin:
+ i=0
+ [ -n $x ]
+ rv=$?; echo $((++i)) $rv
+ [ $x ]
+ rv=$?; echo $((++i)) $rv
+ [ -n "$x" ]
+ rv=$?; echo $((++i)) $rv
+ [ "$x" ]
+ rv=$?; echo $((++i)) $rv
+ x=0
+ [ -n $x ]
+ rv=$?; echo $((++i)) $rv
+ [ $x ]
+ rv=$?; echo $((++i)) $rv
+ [ -n "$x" ]
+ rv=$?; echo $((++i)) $rv
+ [ "$x" ]
+ rv=$?; echo $((++i)) $rv
+ x='1 -a 1 = 2'
+ [ -n $x ]
+ rv=$?; echo $((++i)) $rv
+ [ $x ]
+ rv=$?; echo $((++i)) $rv
+ [ -n "$x" ]
+ rv=$?; echo $((++i)) $rv
+ [ "$x" ]
+ rv=$?; echo $((++i)) $rv
+expected-stdout:
+ 1 0
+ 2 1
+ 3 1
+ 4 1
+ 5 0
+ 6 0
+ 7 0
+ 8 0
+ 9 1
+ 10 1
+ 11 0
+ 12 0
+---
+name: test-stnze-2
+description:
+ Check that the short form [[ $x ]] works (ksh93 extension)
+stdin:
+ i=0
+ [[ -n $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -n "$x" ]]
+ rv=$?; echo $((++i)) $rv
+ [[ "$x" ]]
+ rv=$?; echo $((++i)) $rv
+ x=0
+ [[ -n $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -n "$x" ]]
+ rv=$?; echo $((++i)) $rv
+ [[ "$x" ]]
+ rv=$?; echo $((++i)) $rv
+ x='1 -a 1 = 2'
+ [[ -n $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ $x ]]
+ rv=$?; echo $((++i)) $rv
+ [[ -n "$x" ]]
+ rv=$?; echo $((++i)) $rv
+ [[ "$x" ]]
+ rv=$?; echo $((++i)) $rv
+expected-stdout:
+ 1 1
+ 2 1
+ 3 1
+ 4 1
+ 5 0
+ 6 0
+ 7 0
+ 8 0
+ 9 0
+ 10 0
+ 11 0
+ 12 0
+---
+name: test-numeq
+description:
+ Check numeric -eq works (R40d regression); spotted by Martijn Dekker
+stdin:
+ tst() {
+ eval "$2"
+ case $? in
+ (0) echo yepp 0 \#"$*" ;;
+ (1) echo nope 1 \#"$*" ;;
+ (2) echo terr 2 \#"$*" ;;
+ (*) echo wtf\? $? \#"$*" ;;
+ esac
+ }
+ tst 1 'test 2 -eq 2'
+ tst 2 'test 2 -eq 2a'
+ tst 3 'test 2 -eq 3'
+ tst 4 'test 2 -ne 2'
+ tst 5 'test 2 -ne 2a'
+ tst 6 'test 2 -ne 3'
+ tst 7 'test \! 2 -eq 2'
+ tst 8 'test \! 2 -eq 2a'
+ tst 9 'test \! 2 -eq 3'
+expected-stdout:
+ yepp 0 #1 test 2 -eq 2
+ terr 2 #2 test 2 -eq 2a
+ nope 1 #3 test 2 -eq 3
+ nope 1 #4 test 2 -ne 2
+ terr 2 #5 test 2 -ne 2a
+ yepp 0 #6 test 2 -ne 3
+ nope 1 #7 test \! 2 -eq 2
+ terr 2 #8 test \! 2 -eq 2a
+ yepp 0 #9 test \! 2 -eq 3
+expected-stderr-pattern:
+ /bad number/
+---
+name: mkshrc-1
+description:
+ Check that ~/.mkshrc works correctly.
+ Part 1: verify user environment is not read (internal)
+stdin:
+ echo x $FNORD
+expected-stdout:
+ x
+---
+name: mkshrc-2a
+description:
+ Check that ~/.mkshrc works correctly.
+ Part 2: verify mkshrc is not read (non-interactive shells)
+file-setup: file 644 ".mkshrc"
+ FNORD=42
+env-setup: !HOME=.!ENV=!
+stdin:
+ echo x $FNORD
+expected-stdout:
+ x
+---
+name: mkshrc-2b
+description:
+ Check that ~/.mkshrc works correctly.
+ 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:
+ echo x $FNORD
+expected-stdout:
+ x 42
+expected-stderr-pattern:
+ /(# )*/
+---
+name: mkshrc-3
+description:
+ Check that ~/.mkshrc works correctly.
+ Part 3: verify mkshrc can be turned off
+file-setup: file 644 ".mkshrc"
+ FNORD=42
+env-setup: !HOME=.!ENV=nonexistant!
+stdin:
+ echo x $FNORD
+expected-stdout:
+ x
+---
+name: sh-mode-1
+description:
+ Check that sh mode turns braceexpand off
+ and that that works correctly
+stdin:
+ set -o braceexpand
+ set +o sh
+ [[ -o sh ]] && echo sh
+ [[ -o !sh ]] && echo nosh
+ [[ -o braceexpand ]] && echo brex
+ [[ -o !braceexpand ]] && echo nobrex
+ echo {a,b,c}
+ set +o braceexpand
+ echo {a,b,c}
+ set -o braceexpand
+ echo {a,b,c}
+ set -o sh
+ echo {a,b,c}
+ [[ -o sh ]] && echo sh
+ [[ -o !sh ]] && echo nosh
+ [[ -o braceexpand ]] && echo brex
+ [[ -o !braceexpand ]] && echo nobrex
+ set -o braceexpand
+ echo {a,b,c}
+ [[ -o sh ]] && echo sh
+ [[ -o !sh ]] && echo nosh
+ [[ -o braceexpand ]] && echo brex
+ [[ -o !braceexpand ]] && echo nobrex
+ [[ $(exec -a -set "$__progname" -o) = *login+(' ')on* ]]; echo $?
+expected-stdout:
+ nosh
+ brex
+ a b c
+ {a,b,c}
+ a b c
+ {a,b,c}
+ sh
+ nobrex
+ a b c
+ sh
+ brex
+ 0
+---
+name: sh-mode-2a
+description:
+ Check that posix or sh mode is *not* automatically turned on
+category: !binsh
+stdin:
+ for shell in {,-}{,r}{,k,mk}sh {,-}{,R}{,K,MK}SH.EXE; do
+ ln -s "$__progname" ./$shell || cp "$__progname" ./$shell
+ print -- $shell $(./$shell +l -c '
+ [[ -o sh || -o posix ]] && echo sh
+ [[ -o !sh && -o !posix ]] && echo nosh
+ [[ -o restricted ]] && echo lim || echo ok
+ ')
+ done
+expected-stdout:
+ sh nosh ok
+ ksh nosh ok
+ mksh nosh ok
+ rsh nosh lim
+ rksh nosh lim
+ rmksh nosh lim
+ -sh nosh ok
+ -ksh nosh ok
+ -mksh nosh ok
+ -rsh nosh lim
+ -rksh nosh lim
+ -rmksh nosh lim
+ SH.EXE nosh ok
+ KSH.EXE nosh ok
+ MKSH.EXE nosh ok
+ RSH.EXE nosh lim
+ RKSH.EXE nosh lim
+ RMKSH.EXE nosh lim
+ -SH.EXE nosh ok
+ -KSH.EXE nosh ok
+ -MKSH.EXE nosh ok
+ -RSH.EXE nosh lim
+ -RKSH.EXE nosh lim
+ -RMKSH.EXE nosh lim
+---
+name: sh-mode-2b
+description:
+ Check that posix or sh mode *is* automatically turned on
+category: binsh
+stdin:
+ for shell in {,-}{,r}{,k,mk}sh {,-}{,R}{,K,MK}SH.EXE; do
+ ln -s "$__progname" ./$shell || cp "$__progname" ./$shell
+ print -- $shell $(./$shell +l -c '
+ [[ -o sh || -o posix ]] && echo sh
+ [[ -o !sh && -o !posix ]] && echo nosh
+ [[ -o restricted ]] && echo lim || echo ok
+ ')
+ done
+expected-stdout:
+ sh sh ok
+ ksh nosh ok
+ mksh nosh ok
+ rsh sh lim
+ rksh nosh lim
+ rmksh nosh lim
+ -sh sh ok
+ -ksh nosh ok
+ -mksh nosh ok
+ -rsh sh lim
+ -rksh nosh lim
+ -rmksh nosh lim
+ SH.EXE sh ok
+ KSH.EXE nosh ok
+ MKSH.EXE nosh ok
+ RSH.EXE sh lim
+ RKSH.EXE nosh lim
+ RMKSH.EXE nosh lim
+ -SH.EXE sh ok
+ -KSH.EXE nosh ok
+ -MKSH.EXE nosh ok
+ -RSH.EXE sh lim
+ -RKSH.EXE nosh lim
+ -RMKSH.EXE nosh lim
+---
+name: sh-options
+description:
+ Check that "set +o" DTRT per POSIX
+stdin:
+ t() {
+ [[ -o vi ]]; a=$?
+ [[ -o pipefail ]]; b=$?
+ echo $((++i)) $a $b .
+ }
+ set -e
+ set -o vi
+ set +o pipefail
+ set +e
+ t
+ x=$(set +o)
+ set +o vi
+ set -o pipefail
+ t
+ eval "$x"
+ t
+expected-stdout:
+ 1 0 1 .
+ 2 1 0 .
+ 3 0 1 .
+---
+name: pipeline-1
+description:
+ pdksh bug: last command of a pipeline is executed in a
+ subshell - make sure it still is, scripts depend on it
+file-setup: file 644 "abcx"
+file-setup: file 644 "abcy"
+stdin:
+ echo *
+ echo a | while read d; do
+ echo $d
+ echo $d*
+ echo *
+ set -o noglob
+ echo $d*
+ echo *
+ done
+ echo *
+expected-stdout:
+ abcx abcy
+ a
+ abcx abcy
+ abcx abcy
+ a*
+ *
+ abcx abcy
+---
+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'
+ "$__progname" -c 'i=300; (echo hi | cat) |& while read -p line; do echo "$((i++)) $line"; done'
+expected-stdout:
+ 100 hi
+ 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: pipeline-4
+description:
+ Check that "set -o pipefail" does what it's supposed to
+stdin:
+ echo 1 "$("$__progname" -c '(exit 12) | (exit 23) | (exit 42); echo $?')" .
+ echo 2 "$("$__progname" -c '! (exit 12) | (exit 23) | (exit 42); echo $?')" .
+ echo 3 "$("$__progname" -o pipefail -c '(exit 12) | (exit 23) | (exit 42); echo $?')" .
+ echo 4 "$("$__progname" -o pipefail -c '! (exit 12) | (exit 23) | (exit 42); echo $?')" .
+ echo 5 "$("$__progname" -c '(exit 23) | (exit 42) | :; echo $?')" .
+ echo 6 "$("$__progname" -c '! (exit 23) | (exit 42) | :; echo $?')" .
+ echo 7 "$("$__progname" -o pipefail -c '(exit 23) | (exit 42) | :; echo $?')" .
+ echo 8 "$("$__progname" -o pipefail -c '! (exit 23) | (exit 42) | :; echo $?')" .
+ echo 9 "$("$__progname" -o pipefail -c 'x=$( (exit 23) | (exit 42) | :); echo $?')" .
+expected-stdout:
+ 1 42 .
+ 2 0 .
+ 3 42 .
+ 4 0 .
+ 5 0 .
+ 6 1 .
+ 7 42 .
+ 8 0 .
+ 9 42 .
+---
+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"
+ PS1=X
+stdin:
+ cat hist.file
+expected-stdout-pattern:
+ /cat hist.file/
+expected-stderr-pattern:
+ /^X*$/
+---
+name: typeset-1
+description:
+ Check that typeset -g works correctly
+stdin:
+ set -A arrfoo 65
+ foo() {
+ typeset -g -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]} .
+ typeset -g -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-2
+description:
+ Check that typeset -p on arrays works correctly
+stdin:
+ set -A x -- a b c
+ echo =
+ typeset -p x
+ echo =
+ typeset -p x[1]
+expected-stdout:
+ =
+ set -A x
+ typeset x[0]=a
+ typeset x[1]=b
+ typeset x[2]=c
+ =
+ typeset x[1]=b
+---
+name: typeset-padding-1
+description:
+ Check if left/right justification works as per TFM
+stdin:
+ typeset -L10 ln=0hall0
+ typeset -R10 rn=0hall0
+ typeset -ZL10 lz=0hall0
+ typeset -ZR10 rz=0hall0
+ typeset -Z10 rx=" hallo "
+ echo "<$ln> <$rn> <$lz> <$rz> <$rx>"
+expected-stdout:
+ <0hall0 > < 0hall0> <hall0 > <00000hall0> <0000 hallo>
+---
+name: typeset-padding-2
+description:
+ Check if base-!10 integers are padded right
+stdin:
+ typeset -Uui16 -L9 ln=16#1
+ typeset -Uui16 -R9 rn=16#1
+ typeset -Uui16 -Z9 zn=16#1
+ typeset -L9 ls=16#1
+ typeset -R9 rs=16#1
+ typeset -Z9 zs=16#1
+ echo "<$ln> <$rn> <$zn> <$ls> <$rs> <$zs>"
+expected-stdout:
+ <16#1 > < 16#1> <16#000001> <16#1 > < 16#1> <0000016#1>
+---
+name: typeset-padding-3
+description:
+ Check for a regression in which UTF-8 wasn’t left-padded right
+stdin:
+ set -U
+ nl=$'\n'
+ typeset -L20 x='. ak'
+ typeset -R20 y='. ak'
+ print -r -- "<$x> (1$nl<12345678910 345678920$nl<$y> 1)"
+ typeset -L20 x='. aáşž'
+ typeset -R20 y='. aáşž'
+ print -r -- "<$x> (2$nl<12345678910 345678920$nl<$y> 2)"
+expected-stdout:
+ <. ak > (1
+ <12345678910 345678920
+ < . ak> 1)
+ <. aáşž > (2
+ <12345678910 345678920
+ < . aáşž> 2)
+---
+name: utf8bom-1
+description:
+ Check that the UTF-8 Byte Order Mark is ignored as the first
+ multibyte character of the shell input (with -c, from standard
+ input, as file, or as eval argument), but nowhere else
+# breaks on Mac OSX (HFS+ non-standard UTF-8 canonical decomposition)
+category: !os:darwin,!shell:ebcdic-yes
+stdin:
+ mkdir foo
+ print '#!/bin/sh\necho ohne' >foo/fnord
+ print '#!/bin/sh\necho mit' >foo/fnord
+ print 'fnord\nfnord\nfnord\nfnord' >foo/bar
+ print eval \''fnord\nfnord\nfnord\nfnord'\' >foo/zoo
+ set -A anzahl -- foo/*
+ echo got ${#anzahl[*]} files
+ chmod +x foo/*
+ export PATH=$(pwd)/foo$PATHSEP$PATH
+ "$__progname" -c 'fnord'
+ echo =
+ "$__progname" -c 'fnord; fnord; fnord; fnord'
+ echo =
+ "$__progname" foo/bar
+ echo =
+ "$__progname" <foo/bar
+ echo =
+ "$__progname" foo/zoo
+ echo =
+ "$__progname" -c 'echo : $(fnord)'
+ rm -rf foo
+expected-stdout:
+ got 4 files
+ ohne
+ =
+ ohne
+ ohne
+ mit
+ ohne
+ =
+ ohne
+ ohne
+ mit
+ ohne
+ =
+ ohne
+ ohne
+ mit
+ ohne
+ =
+ ohne
+ ohne
+ mit
+ ohne
+ =
+ : ohne
+---
+name: utf8bom-2
+description:
+ Check that we can execute BOM-shebangs (failures not fatal)
+ 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
+ XXX fails when LD_PRELOAD is set with -e and Perl chokes it (ASan)
+need-pass: no
+category: !os:cygwin,!os:midipix,!os:msys,!os:ultrix,!os:uwin-nt,!smksh
+env-setup: !FOO=BAR!
+stdin:
+ print '#!'"$__progname"'\nprint "1 a=$ENV{FOO}";' >t1
+ print '#!'"$__progname"'\nprint "2 a=$ENV{FOO}";' >t2
+ print '#!'"$__perlname"'\nprint "3 a=$ENV{FOO}\n";' >t3
+ print '#!'"$__perlname"'\nprint "4 a=$ENV{FOO}\n";' >t4
+ chmod +x t?
+ ./t1
+ ./t2
+ ./t3
+ ./t4
+expected-stdout:
+ 1 a=/nonexistant{FOO}
+ 2 a=/nonexistant{FOO}
+ 3 a=BAR
+ 4 a=BAR
+expected-stderr-pattern:
+ /(Unrecognized character .... ignored at \..t4 line 1)*/
+---
+name: utf8opt-1
+description:
+ Check that the utf8-mode flag is not set at non-interactive startup
+env-setup: !PS1=!PS2=!LC_CTYPE=@utflocale@!
+stdin:
+ if [[ $- = *U* ]]; then
+ echo is set
+ else
+ echo is not set
+ fi
+expected-stdout:
+ is not set
+---
+name: utf8opt-2
+description:
+ Check that the utf8-mode flag is set at interactive startup.
+ If your OS is old, try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
+need-pass: no
+category: !noutf8
+need-ctty: yes
+arguments: !-i!
+env-setup: !PS1=!PS2=!LC_CTYPE=@utflocale@!
+stdin:
+ if [[ $- = *U* ]]; then
+ echo is set
+ else
+ echo is not set
+ fi
+expected-stdout:
+ is set
+expected-stderr-pattern:
+ /(# )*/
+---
+name: utf8opt-3a
+description:
+ Ensure ±U on the command line is honoured
+ (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 done
+---
+name: utf8bug-1
+description:
+ Ensure trailing combining characters are not lost
+stdin:
+ set -U
+ a=a
+ b=$'\u0301'
+ x=$a$b
+ print -r -- "<e$x>"
+ x=$a
+ x+=$b
+ print -r -- "<e$x>"
+ b=$'\u0301'b
+ x=$a
+ x+=$b
+ print -r -- "<e$x>"
+expected-stdout:
+ <eaĚ>
+ <eaĚ>
+ <eaĚb>
+---
+name: aliases-1
+description:
+ Check if built-in shell aliases are okay
+stdin:
+ alias
+ typeset -f
+expected-stdout:
+ autoload='\\builtin typeset -fu'
+ functions='\\builtin typeset -f'
+ hash='\\builtin alias -t'
+ history='\\builtin fc -l'
+ integer='\\builtin typeset -i'
+ local='\\builtin typeset'
+ login='\\builtin exec login'
+ nameref='\\builtin typeset -n'
+ nohup='nohup '
+ r='\\builtin fc -e -'
+ type='\\builtin whence -v'
+---
+name: aliases-2b
+description:
+ Check if “set -o sh” does not influence built-in aliases
+arguments: !-o!sh!
+stdin:
+ alias
+ typeset -f
+expected-stdout:
+ autoload='\\builtin typeset -fu'
+ functions='\\builtin typeset -f'
+ hash='\\builtin alias -t'
+ history='\\builtin fc -l'
+ integer='\\builtin typeset -i'
+ local='\\builtin typeset'
+ login='\\builtin exec login'
+ nameref='\\builtin typeset -n'
+ nohup='nohup '
+ r='\\builtin fc -e -'
+ type='\\builtin whence -v'
+---
+name: aliases-3b
+description:
+ Check if running as sh does not influence built-in aliases
+stdin:
+ cp "$__progname" sh
+ ./sh -c 'alias; typeset -f'
+ rm -f sh
+expected-stdout:
+ autoload='\\builtin typeset -fu'
+ functions='\\builtin typeset -f'
+ hash='\\builtin alias -t'
+ history='\\builtin fc -l'
+ integer='\\builtin typeset -i'
+ local='\\builtin typeset'
+ login='\\builtin exec login'
+ nameref='\\builtin typeset -n'
+ nohup='nohup '
+ r='\\builtin fc -e -'
+ type='\\builtin whence -v'
+---
+name: aliases-cmdline
+description:
+ Check that aliases work from the command line (Debian #517009)
+ Note that due to the nature of the lexing process, defining
+ aliases in COMSUBs then immediately using them, and things
+ like 'alias foo=bar && foo', still fail.
+stdin:
+ "$__progname" -c $'alias a="echo OK"\na'
+expected-stdout:
+ OK
+---
+name: aliases-funcdef-1
+description:
+ Check if POSIX functions take precedences over aliases
+stdin:
+ alias foo='echo makro'
+ foo() {
+ echo funktion
+ }
+ foo
+expected-stdout:
+ makro
+---
+name: aliases-funcdef-2
+description:
+ Check if POSIX functions take precedences over aliases
+stdin:
+ alias foo='echo makro'
+ foo () {
+ echo funktion
+ }
+ foo
+expected-stdout:
+ makro
+---
+name: aliases-funcdef-3
+description:
+ Check if aliases take precedences over Korn functions
+stdin:
+ alias foo='echo makro'
+ function foo {
+ echo funktion
+ }
+ foo
+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='\\builtin typeset'
+ local='\\builtin typeset'
+---
+name: arrays-1
+description:
+ Check if Korn Shell arrays work as expected
+stdin:
+ v="c d"
+ set -A foo -- a \$v "$v" '$v' b
+ echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|"
+expected-stdout:
+ 5|a|$v|c d|$v|b|
+---
+name: arrays-2a
+description:
+ Check if bash-style arrays work as expected
+stdin:
+ v="c d"
+ foo=(a \$v "$v" '$v' b)
+ echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|"
+expected-stdout:
+ 5|a|$v|c d|$v|b|
+---
+name: arrays-2b
+description:
+ Check if bash-style arrays work as expected, with newlines
+stdin:
+ print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "$x|"; done' >pfp
+ chmod +x pfp
+ test -n "$ZSH_VERSION" && setopt KSH_ARRAYS
+ v="e f"
+ foo=(a
+ bc
+ d \$v "$v" '$v' g
+ )
+ ./pfp "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+ foo=(a\
+ bc
+ d \$v "$v" '$v' g
+ )
+ ./pfp "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+ foo=(a\
+ bc\\
+ d \$v "$v" '$v'
+ g)
+ ./pfp "${#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
+stdin:
+ set -A foo a b c
+ foo[4097]=d
+ foo[2147483637]=e
+ echo ${foo[*]}
+ foo[-1]=f
+ echo ${foo[4294967295]} g ${foo[*]}
+expected-stdout:
+ a b c d e
+ f g a b c d e f
+---
+name: arrays-4
+description:
+ Check if Korn Shell arrays with specified indices work as expected
+stdin:
+ v="c d"
+ set -A foo -- [1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b
+ echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|"
+ # we don't want this at all:
+ # 5|a|$v|c d||$v|b|
+ set -A arr "[5]=meh"
+ echo "<${arr[0]}><${arr[5]}>"
+expected-stdout:
+ 5|[1]=$v|[2]=c d|[4]=$v|[0]=a|[5]=b||
+ <[5]=meh><>
+---
+name: arrays-5
+description:
+ Check if bash-style arrays with specified indices work as expected
+ (taken out temporarily to fix arrays-4; see also arrays-9a comment)
+category: disabled
+stdin:
+ v="c d"
+ foo=([1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b)
+ echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|"
+ x=([128]=foo bar baz)
+ echo k= ${!x[*]} .
+ echo v= ${x[*]} .
+ # Check that we do not break this by globbing
+ :>b=blah
+ bleh=5
+ typeset -a arr
+ arr+=([bleh]=blah)
+ echo "<${arr[0]}><${arr[5]}>"
+expected-stdout:
+ 5|a|$v|c d||$v|b|
+ k= 128 129 130 .
+ v= foo bar baz .
+ <><blah>
+---
+name: arrays-6
+description:
+ Check if we can get the array keys (indices) for indexed arrays,
+ Korn shell style
+stdin:
+ of() {
+ i=0
+ for x in "$@"; do
+ echo -n "$((i++))<$x>"
+ done
+ echo
+ }
+ foo[1]=eins
+ set | grep '^foo'
+ echo =
+ foo[0]=zwei
+ foo[4]=drei
+ set | grep '^foo'
+ echo =
+ echo a $(of ${foo[*]}) = $(of ${bar[*]}) a
+ echo b $(of "${foo[*]}") = $(of "${bar[*]}") b
+ echo c $(of ${foo[@]}) = $(of ${bar[@]}) c
+ echo d $(of "${foo[@]}") = $(of "${bar[@]}") d
+ echo e $(of ${!foo[*]}) = $(of ${!bar[*]}) e
+ echo f $(of "${!foo[*]}") = $(of "${!bar[*]}") f
+ echo g $(of ${!foo[@]}) = $(of ${!bar[@]}) g
+ echo h $(of "${!foo[@]}") = $(of "${!bar[@]}") h
+expected-stdout:
+ foo[1]=eins
+ =
+ foo[0]=zwei
+ foo[1]=eins
+ foo[4]=drei
+ =
+ a 0<zwei>1<eins>2<drei> = a
+ b 0<zwei eins drei> = 0<> b
+ c 0<zwei>1<eins>2<drei> = c
+ d 0<zwei>1<eins>2<drei> = d
+ e 0<0>1<1>2<4> = e
+ f 0<0 1 4> = 0<> f
+ g 0<0>1<1>2<4> = g
+ h 0<0>1<1>2<4> = h
+---
+name: arrays-7
+description:
+ Check if we can get the array keys (indices) for indexed arrays,
+ Korn shell style, in some corner cases
+stdin:
+ echo !arz: ${!arz}
+ echo !arz[0]: ${!arz[0]}
+ echo !arz[1]: ${!arz[1]}
+ arz=foo
+ echo !arz: ${!arz}
+ echo !arz[0]: ${!arz[0]}
+ echo !arz[1]: ${!arz[1]}
+ unset arz
+ echo !arz: ${!arz}
+ echo !arz[0]: ${!arz[0]}
+ echo !arz[1]: ${!arz[1]}
+expected-stdout:
+ !arz: arz
+ !arz[0]: arz[0]
+ !arz[1]: arz[1]
+ !arz: arz
+ !arz[0]: arz[0]
+ !arz[1]: arz[1]
+ !arz: arz
+ !arz[0]: arz[0]
+ !arz[1]: arz[1]
+---
+name: arrays-8
+description:
+ Check some behavioural rules for arrays.
+stdin:
+ fna() {
+ set -A aa 9
+ }
+ fnb() {
+ typeset ab
+ set -A ab 9
+ }
+ fnc() {
+ typeset ac
+ set -A ac 91
+ unset ac
+ set -A ac 92
+ }
+ fnd() {
+ set +A ad 9
+ }
+ fne() {
+ unset ae
+ set +A ae 9
+ }
+ fnf() {
+ unset af[0]
+ set +A af 9
+ }
+ fng() {
+ unset ag[*]
+ set +A ag 9
+ }
+ set -A aa 1 2
+ set -A ab 1 2
+ set -A ac 1 2
+ set -A ad 1 2
+ set -A ae 1 2
+ set -A af 1 2
+ set -A ag 1 2
+ set -A ah 1 2
+ typeset -Z3 aa ab ac ad ae af ag
+ print 1a ${aa[*]} .
+ print 1b ${ab[*]} .
+ print 1c ${ac[*]} .
+ print 1d ${ad[*]} .
+ print 1e ${ae[*]} .
+ print 1f ${af[*]} .
+ print 1g ${ag[*]} .
+ print 1h ${ah[*]} .
+ fna
+ fnb
+ fnc
+ fnd
+ fne
+ fnf
+ fng
+ typeset -Z5 ah[*]
+ print 2a ${aa[*]} .
+ print 2b ${ab[*]} .
+ print 2c ${ac[*]} .
+ print 2d ${ad[*]} .
+ print 2e ${ae[*]} .
+ print 2f ${af[*]} .
+ print 2g ${ag[*]} .
+ print 2h ${ah[*]} .
+expected-stdout:
+ 1a 001 002 .
+ 1b 001 002 .
+ 1c 001 002 .
+ 1d 001 002 .
+ 1e 001 002 .
+ 1f 001 002 .
+ 1g 001 002 .
+ 1h 1 2 .
+ 2a 9 .
+ 2b 001 002 .
+ 2c 92 .
+ 2d 009 002 .
+ 2e 9 .
+ 2f 9 002 .
+ 2g 009 .
+ 2h 00001 00002 .
+---
+name: arrays-9a
+description:
+ Check that we can concatenate arrays
+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: arrassign-basic
+description:
+ Check basic whitespace conserving properties of wdarrassign
+stdin:
+ a=($(echo a b))
+ b=($(echo "a b"))
+ c=("$(echo "a b")")
+ d=("$(echo a b)")
+ a+=($(echo c d))
+ b+=($(echo "c d"))
+ c+=("$(echo "c d")")
+ d+=("$(echo c d)")
+ echo ".a:${a[0]}.${a[1]}.${a[2]}.${a[3]}:"
+ echo ".b:${b[0]}.${b[1]}.${b[2]}.${b[3]}:"
+ echo ".c:${c[0]}.${c[1]}.${c[2]}.${c[3]}:"
+ echo ".d:${d[0]}.${d[1]}.${d[2]}.${d[3]}:"
+expected-stdout:
+ .a:a.b.c.d:
+ .b:a.b.c.d:
+ .c:a b.c d..:
+ .d:a b.c d..:
+---
+name: arrassign-eol
+description:
+ Commands after array assignments are not permitted
+stdin:
+ foo=(a b) env
+expected-exit: e != 0
+expected-stderr-pattern:
+ /syntax error: unexpected 'env'/
+---
+name: arrassign-fnc-none
+description:
+ Check locality of array access inside a function
+stdin:
+ function fn {
+ x+=(f)
+ echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ }
+ function rfn {
+ if [[ -n $BASH_VERSION ]]; then
+ y=()
+ else
+ set -A y
+ fi
+ y+=(f)
+ echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ }
+ x=(m m)
+ y=(m m)
+ echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+expected-stdout:
+ .f0:m.m..:
+ .fn:m.m.f.:
+ .f1:m.m.f.:
+ .fn:m.m.f.f:
+ .f2:m.m.f.f:
+ .rf0:m.m..:
+ .rfn:f...:
+ .rf1:f...:
+ .rfn:f...:
+ .rf2:f...:
+---
+name: arrassign-fnc-local
+description:
+ Check locality of array access inside a function
+ with the bash/mksh/ksh93 local/typeset keyword
+ (note: ksh93 has no local; typeset works only in FKSH)
+stdin:
+ function fn {
+ typeset x
+ x+=(f)
+ echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ }
+ function rfn {
+ if [[ -n $BASH_VERSION ]]; then
+ y=()
+ else
+ set -A y
+ fi
+ typeset y
+ y+=(f)
+ echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ }
+ function fnr {
+ typeset z
+ if [[ -n $BASH_VERSION ]]; then
+ z=()
+ else
+ set -A z
+ fi
+ z+=(f)
+ echo ".fnr:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ }
+ x=(m m)
+ y=(m m)
+ z=(m m)
+ echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ echo ".f0r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ fnr
+ echo ".f1r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ fnr
+ echo ".f2r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+expected-stdout:
+ .f0:m.m..:
+ .fn:f...:
+ .f1:m.m..:
+ .fn:f...:
+ .f2:m.m..:
+ .rf0:m.m..:
+ .rfn:f...:
+ .rf1:...:
+ .rfn:f...:
+ .rf2:...:
+ .f0r:m.m..:
+ .fnr:f...:
+ .f1r:m.m..:
+ .fnr:f...:
+ .f2r:m.m..:
+---
+name: arrassign-fnc-global
+description:
+ Check locality of array access inside a function
+ with the bash4/mksh/yash/zsh typeset -g keyword
+stdin:
+ function fn {
+ typeset -g x
+ x+=(f)
+ echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ }
+ function rfn {
+ set -A y
+ typeset -g y
+ y+=(f)
+ echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ }
+ function fnr {
+ typeset -g z
+ set -A z
+ z+=(f)
+ echo ".fnr:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ }
+ x=(m m)
+ y=(m m)
+ z=(m m)
+ echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ fn
+ echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
+ echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ rfn
+ echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
+ echo ".f0r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ fnr
+ echo ".f1r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+ fnr
+ echo ".f2r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
+expected-stdout:
+ .f0:m.m..:
+ .fn:m.m.f.:
+ .f1:m.m.f.:
+ .fn:m.m.f.f:
+ .f2:m.m.f.f:
+ .rf0:m.m..:
+ .rfn:f...:
+ .rf1:f...:
+ .rfn:f...:
+ .rf2:f...:
+ .f0r:m.m..:
+ .fnr:f...:
+ .f1r:f...:
+ .fnr:f...:
+ .f2r:f...:
+---
+name: strassign-fnc-none
+description:
+ Check locality of string access inside a function
+stdin:
+ function fn {
+ x+=f
+ echo ".fn:$x:"
+ }
+ function rfn {
+ y=
+ y+=f
+ echo ".rfn:$y:"
+ }
+ x=m
+ y=m
+ echo ".f0:$x:"
+ fn
+ echo ".f1:$x:"
+ fn
+ echo ".f2:$x:"
+ echo ".rf0:$y:"
+ rfn
+ echo ".rf1:$y:"
+ rfn
+ echo ".rf2:$y:"
+expected-stdout:
+ .f0:m:
+ .fn:mf:
+ .f1:mf:
+ .fn:mff:
+ .f2:mff:
+ .rf0:m:
+ .rfn:f:
+ .rf1:f:
+ .rfn:f:
+ .rf2:f:
+---
+name: strassign-fnc-local
+description:
+ Check locality of string access inside a function
+ with the bash/mksh/ksh93 local/typeset keyword
+ (note: ksh93 has no local; typeset works only in FKSH)
+stdin:
+ function fn {
+ typeset x
+ x+=f
+ echo ".fn:$x:"
+ }
+ function rfn {
+ y=
+ typeset y
+ y+=f
+ echo ".rfn:$y:"
+ }
+ function fnr {
+ typeset z
+ z=
+ z+=f
+ echo ".fnr:$z:"
+ }
+ x=m
+ y=m
+ z=m
+ echo ".f0:$x:"
+ fn
+ echo ".f1:$x:"
+ fn
+ echo ".f2:$x:"
+ echo ".rf0:$y:"
+ rfn
+ echo ".rf1:$y:"
+ rfn
+ echo ".rf2:$y:"
+ echo ".f0r:$z:"
+ fnr
+ echo ".f1r:$z:"
+ fnr
+ echo ".f2r:$z:"
+expected-stdout:
+ .f0:m:
+ .fn:f:
+ .f1:m:
+ .fn:f:
+ .f2:m:
+ .rf0:m:
+ .rfn:f:
+ .rf1::
+ .rfn:f:
+ .rf2::
+ .f0r:m:
+ .fnr:f:
+ .f1r:m:
+ .fnr:f:
+ .f2r:m:
+---
+name: strassign-fnc-global
+description:
+ Check locality of string access inside a function
+ with the bash4/mksh/yash/zsh typeset -g keyword
+stdin:
+ function fn {
+ typeset -g x
+ x+=f
+ echo ".fn:$x:"
+ }
+ function rfn {
+ y=
+ typeset -g y
+ y+=f
+ echo ".rfn:$y:"
+ }
+ function fnr {
+ typeset -g z
+ z=
+ z+=f
+ echo ".fnr:$z:"
+ }
+ x=m
+ y=m
+ z=m
+ echo ".f0:$x:"
+ fn
+ echo ".f1:$x:"
+ fn
+ echo ".f2:$x:"
+ echo ".rf0:$y:"
+ rfn
+ echo ".rf1:$y:"
+ rfn
+ echo ".rf2:$y:"
+ echo ".f0r:$z:"
+ fnr
+ echo ".f1r:$z:"
+ fnr
+ echo ".f2r:$z:"
+expected-stdout:
+ .f0:m:
+ .fn:mf:
+ .f1:mf:
+ .fn:mff:
+ .f2:mff:
+ .rf0:m:
+ .rfn:f:
+ .rf1:f:
+ .rfn:f:
+ .rf2:f:
+ .f0r:m:
+ .fnr:f:
+ .f1r:f:
+ .fnr:f:
+ .f2r:f:
+---
+name: unset-fnc-local-ksh
+description:
+ Check that “unset” removes a previous “local”
+ (ksh93 syntax compatible version); apparently,
+ there are shells which fail this?
+stdin:
+ function f {
+ echo f0: $x
+ typeset x
+ echo f1: $x
+ x=fa
+ echo f2: $x
+ unset x
+ echo f3: $x
+ x=fb
+ echo f4: $x
+ }
+ x=o
+ echo before: $x
+ f
+ echo after: $x
+expected-stdout:
+ before: o
+ f0: o
+ f1:
+ f2: fa
+ f3: o
+ f4: fb
+ after: fb
+---
+name: unset-fnc-local-sh
+description:
+ Check that “unset” removes a previous “local”
+ (Debian Policy §10.4 sh version); apparently,
+ there are shells which fail this?
+stdin:
+ f() {
+ echo f0: $x
+ local x
+ echo f1: $x
+ x=fa
+ echo f2: $x
+ unset x
+ echo f3: $x
+ x=fb
+ echo f4: $x
+ }
+ x=o
+ echo before: $x
+ f
+ echo after: $x
+expected-stdout:
+ before: o
+ f0: o
+ f1:
+ f2: fa
+ f3: o
+ f4: fb
+ after: fb
+---
+name: varexpand-substr-1
+description:
+ Check if bash-style substring expansion works
+ when using positive numerics
+stdin:
+ x=abcdefghi
+ typeset -i y=123456789
+ typeset -i 16 z=123456789 # 16#75bcd15
+ echo a t${x:2:2} ${y:2:3} ${z:2:3} a
+ echo b ${x::3} ${y::3} ${z::3} b
+ echo c ${x:2:} ${y:2:} ${z:2:} c
+ echo d ${x:2} ${y:2} ${z:2} d
+ echo e ${x:2:6} ${y:2:6} ${z:2:7} e
+ echo f ${x:2:7} ${y:2:7} ${z:2:8} f
+ echo g ${x:2:8} ${y:2:8} ${z:2:9} g
+expected-stdout:
+ a tcd 345 #75 a
+ b abc 123 16# b
+ c c
+ d cdefghi 3456789 #75bcd15 d
+ e cdefgh 345678 #75bcd1 e
+ f cdefghi 3456789 #75bcd15 f
+ g cdefghi 3456789 #75bcd15 g
+---
+name: varexpand-substr-2
+description:
+ Check if bash-style substring expansion works
+ when using negative numerics or expressions
+stdin:
+ x=abcdefghi
+ typeset -i y=123456789
+ typeset -i 16 z=123456789 # 16#75bcd15
+ n=2
+ echo a ${x:$n:3} ${y:$n:3} ${z:$n:3} a
+ echo b ${x:(n):3} ${y:(n):3} ${z:(n):3} b
+ echo c ${x:(-2):1} ${y:(-2):1} ${z:(-2):1} c
+ echo d t${x: n:2} ${y: n:3} ${z: n:3} d
+expected-stdout:
+ a cde 345 #75 a
+ b cde 345 #75 b
+ c h 8 1 c
+ d tcd 345 #75 d
+---
+name: varexpand-substr-3
+description:
+ Match bash5
+stdin:
+ export x=abcdefghi n=2
+ "$__progname" -c 'echo v${x:(n)}x'
+ "$__progname" -c 'echo w${x: n}x'
+ "$__progname" -c 'echo x${x:n}x'
+ "$__progname" -c 'echo y${x:}x'
+ "$__progname" -c 'echo z${x}x'
+ "$__progname" -c 'x=abcdef;y=123;echo q${x:${y:2:1}:2}q'
+expected-stdout:
+ vcdefghix
+ wcdefghix
+ xcdefghix
+ zabcdefghix
+ qdeq
+expected-stderr-pattern:
+ /x:}.*bad substitution/
+---
+name: varexpand-substr-4
+description:
+ Check corner cases for substring expansion
+stdin:
+ x=abcdefghi
+ integer y=2
+ echo a ${x:(y == 1 ? 2 : 3):4} a
+expected-stdout:
+ a defg a
+---
+name: varexpand-substr-5A
+description:
+ Check that substring expansions work on characters
+stdin:
+ set +U
+ x=mäh
+ echo a ${x::1} ${x: -1} a
+ echo b ${x::3} ${x: -3} b
+ echo c ${x:1:2} ${x: -3:2} c
+ echo d ${#x} d
+expected-stdout:
+ a m h a
+ b mä äh b
+ c ä ä c
+ d 4 d
+---
+name: varexpand-substr-5W
+description:
+ Check that substring expansions work on characters
+stdin:
+ set -U
+ x=mäh
+ echo a ${x::1} ${x: -1} a
+ echo b ${x::2} ${x: -2} b
+ echo c ${x:1:1} ${x: -2:1} c
+ echo d ${#x} d
+expected-stdout:
+ a m h a
+ b mä äh b
+ c ä ä c
+ d 3 d
+---
+name: varexpand-substr-6
+description:
+ Check that string substitution works correctly
+stdin:
+ foo=1
+ bar=2
+ baz=qwertyuiop
+ echo a ${baz: foo: bar}
+ echo b ${baz: foo: $bar}
+ echo c ${baz: $foo: bar}
+ echo d ${baz: $foo: $bar}
+expected-stdout:
+ a we
+ b we
+ c we
+ d we
+---
+name: varexpand-special-hash
+description:
+ Check special ${var@x} expansion for x=hash
+category: !shell:ebcdic-yes
+stdin:
+ typeset -i8 foo=10
+ bar=baz
+ unset baz
+ print ${foo@#} ${bar@#} ${baz@#} .
+expected-stdout:
+ 9B15FBFB CFBDD32B 00000000 .
+---
+name: varexpand-special-hash-ebcdic
+description:
+ Check special ${var@x} expansion for x=hash
+category: !shell:ebcdic-no
+stdin:
+ typeset -i8 foo=10
+ bar=baz
+ unset baz
+ print ${foo@#} ${bar@#} ${baz@#} .
+expected-stdout:
+ 016AE33D 9769C4AF 00000000 .
+---
+name: varexpand-special-quote
+description:
+ Check special ${var@Q} expansion for quoted strings
+category: !shell:faux-ebcdic
+stdin:
+ set +U
+ i=x
+ j=a\ b
+ k=$'c
+ d\xA0''e€f'
+ print -r -- "<i=$i j=$j k=$k>"
+ s="u=${i@Q} v=${j@Q} w=${k@Q}"
+ print -r -- "s=\"$s\""
+ eval "$s"
+ typeset -p u v w
+expected-stdout:
+ <i=x j=a b k=c
+ d e€f>
+ s="u=x v='a b' w=$'c\nd\240e\u20ACf'"
+ typeset u=x
+ typeset v='a b'
+ typeset w=$'c\nd\240e\u20ACf'
+---
+name: varexpand-special-quote-faux-EBCDIC
+description:
+ Check special ${var@Q} expansion for quoted strings
+category: shell:faux-ebcdic
+stdin:
+ set +U
+ i=x
+ j=a\ b
+ k=$'c
+ d\xA0''e€f'
+ print -r -- "<i=$i j=$j k=$k>"
+ s="u=${i@Q} v=${j@Q} w=${k@Q}"
+ print -r -- "s=\"$s\""
+ eval "$s"
+ typeset -p u v w
+expected-stdout:
+ <i=x j=a b k=c
+ d e€f>
+ s="u=x v='a b' w=$'c\nd e\u20ACf'"
+ typeset u=x
+ typeset v='a b'
+ typeset w=$'c\nd e\u20ACf'
+---
+name: varexpand-null-1
+description:
+ Ensure empty strings expand emptily
+stdin:
+ print s ${a} . ${b} S
+ print t ${a#?} . ${b%?} T
+ print r ${a=} . ${b/c/d} R
+ print q
+ print s "${a}" . "${b}" S
+ print t "${a#?}" . "${b%?}" T
+ print r "${a=}" . "${b/c/d}" R
+expected-stdout:
+ s . S
+ t . T
+ r . R
+ q
+ s . S
+ t . T
+ r . R
+---
+name: varexpand-null-2
+description:
+ Ensure empty strings, when quoted, are expanded as empty strings
+stdin:
+ print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "<$x> "; done' >pfs
+ chmod +x pfs
+ ./pfs 1 "${a}" 2 "${a#?}" + "${b%?}" 3 "${a=}" + "${b/c/d}"
+ echo .
+expected-stdout:
+ <1> <> <2> <> <+> <> <3> <> <+> <> .
+---
+name: varexpand-null-3
+description:
+ Ensure concatenating behaviour matches other shells
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ showargs 0 ""$@
+ x=; showargs 1 "$x"$@
+ set A; showargs 2 "${@:+}"
+ n() { echo "$#"; }
+ unset e
+ set -- a b
+ n """$@"
+ n "$@"
+ n "$@"""
+ n "$e""$@"
+ n "$@"
+ n "$@""$e"
+ set --
+ n """$@"
+ n "$@"
+ n "$@"""
+ n "$e""$@"
+ n "$@"
+ n "$@""$e"
+expected-stdout:
+ <0> <> .
+ <1> <> .
+ <2> <> .
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 0
+ 1
+ 1
+ 0
+ 1
+---
+name: varexpand-funny-chars
+description:
+ Check some characters
+ XXX \uEF80 is asymmetric, possibly buggy so we don’t check this
+stdin:
+ x=$'<\x00>'; typeset -p x
+ x=$'<\x01>'; typeset -p x
+ x=$'<\u0000>'; typeset -p x
+ x=$'<\u0001>'; typeset -p x
+expected-stdout:
+ typeset x='<'
+ typeset x=$'<\001>'
+ typeset x='<'
+ typeset x=$'<\001>'
+---
+name: print-funny-chars
+description:
+ Check print builtin's capability to output designated characters
+stdin:
+ {
+ print '<\0144\0344\xDB\u00DB\u20AC\uDB\x40>'
+ print '<\x00>'
+ print '<\x01>'
+ print '<\u0000>'
+ print '<\u0001>'
+ } | {
+ # 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 -r -- "$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 -r -- "$dasc|"
+ }
+expected-stdout:
+ 00000000 3C 64 E4 DB C3 9B E2 82 - AC C3 9B 40 3E 0A 3C 00 |<d.........@>.<.|
+ 00000010 3E 0A 3C 01 3E 0A 3C 00 - 3E 0A 3C 01 3E 0A |>.<.>.<.>.<.>.|
+---
+name: print-bksl-c
+description:
+ Check print builtin's \c escape
+stdin:
+ print '\ca'; print b
+expected-stdout:
+ ab
+---
+name: print-cr
+description:
+ Check that CR+LF is not collapsed into LF as some MSYS shells wrongly do
+stdin:
+ echo '#!'"$__progname" >foo
+ cat >>foo <<-'EOF'
+ print -n -- '220-blau.mirbsd.org ESMTP ready at Thu, 25 Jul 2013 15:57:57 GMT\r\n220->> Bitte keine Werbung einwerfen! <<\r\r\n220 Who do you wanna pretend to be today'
+ print \?
+ EOF
+ chmod +x foo
+ echo "[$(./foo)]"
+ ./foo | while IFS= read -r line; do
+ print -r -- "{$line}"
+ done
+expected-stdout:
+ [220-blau.mirbsd.org ESMTP ready at Thu, 25 Jul 2013 15:57:57 GMT
+ 220->> Bitte keine Werbung einwerfen! <<
+ 220 Who do you wanna pretend to be today? ]
+ {220-blau.mirbsd.org ESMTP ready at Thu, 25 Jul 2013 15:57:57 GMT }
+ {220->> Bitte keine Werbung einwerfen! << }
+ {220 Who do you wanna pretend to be today? }
+---
+name: print-crlf
+description:
+ Check that CR+LF is shown and read as-is
+category: shell:textmode-no
+stdin:
+ cat >foo <<-'EOF'
+ x='bar
+ ' #
+ echo .${#x} #
+ if test x"$KSH_VERSION" = x""; then #
+ printf '<%s>' "$x" #
+ else #
+ print -nr -- "<$x>" #
+ fi #
+ EOF
+ echo "[$("$__progname" foo)]"
+ "$__progname" foo | while IFS= read -r line; do
+ print -r -- "{$line}"
+ done
+expected-stdout:
+ [.5
+ <bar
+ >]
+ {.5}
+ {<bar }
+---
+name: print-crlf-textmode
+description:
+ Check that CR+LF is treated as newline
+category: shell:textmode-yes
+stdin:
+ cat >foo <<-'EOF'
+ x='bar
+ ' #
+ echo .${#x} #
+ if test x"$KSH_VERSION" = x""; then #
+ printf '<%s>' "$x" #
+ else #
+ print -nr -- "<$x>" #
+ fi #
+ EOF
+ echo "[$("$__progname" foo)]"
+ "$__progname" foo | while IFS= read -r line; do
+ print -r -- "{$line}"
+ done
+expected-stdout:
+ [.4
+ <bar
+ >]
+ {.4}
+ {<bar}
+---
+name: print-lf
+description:
+ Check that LF-only is shown and read as-is
+stdin:
+ cat >foo <<-'EOF'
+ x='bar
+ ' #
+ echo .${#x} #
+ if test x"$KSH_VERSION" = x""; then #
+ printf '<%s>' "$x" #
+ else #
+ print -nr -- "<$x>" #
+ fi #
+ EOF
+ echo "[$("$__progname" foo)]"
+ "$__progname" foo | while IFS= read -r line; do
+ print -r -- "{$line}"
+ done
+expected-stdout:
+ [.4
+ <bar
+ >]
+ {.4}
+ {<bar}
+---
+name: print-nul-chars
+description:
+ Check handling of NUL characters for print and COMSUB
+stdin:
+ x=$(print '<\0>')
+ print $(($(print '<\0>' | wc -c))) $(($(print "$x" | wc -c))) \
+ ${#x} "$x" '<\0>'
+expected-stdout-pattern:
+ /^4 3 2 <> <\0>$/
+---
+name: print-array
+description:
+ Check that print -A works as expected
+stdin:
+ print -An 0x20AC 0xC3 0xBC 8#101
+ set -U
+ print -A 0x20AC 0xC3 0xBC 8#102
+expected-stdout:
+ ¬ĂĽA€ĂÂĽB
+---
+name: print-escapes
+description:
+ Check backslash expansion by the print builtin
+stdin:
+ print '\ \!\"\#\$\%\&'\\\''\(\)\*\+\,\-\.\/\0\1\2\3\4\5\6\7\8' \
+ '\9\:\;\<\=\>\?\@\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 \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=2147483647
+ typeset -i1 wc=0x0A
+ dasc=
+ nl=${wc#1#}
+ while IFS= read -r line; do
+ line=$line$nl
+ while [[ -n $line ]]; do
+ hv=1#${line::1}
+ if (( (pos & 15) == 0 )); then
+ (( pos )) && print -r -- "$dasc|"
+ print -n "${pos#16#} "
+ dasc=' |'
+ fi
+ print -n "${hv#16#} "
+ if (( (hv < 32) || (hv > 126) )); then
+ dasc=$dasc.
+ else
+ dasc=$dasc${line::1}
+ fi
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ line=${line:1}
+ done
+ done
+ while (( pos & 15 )); do
+ print -n ' '
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ done
+ (( hv == 2147483647 )) || print -r -- "$dasc|"
+ }
+expected-stdout:
+ 00000000 5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27 |\ \!\"\#\$\%\&\'|
+ 00000010 5C 28 5C 29 5C 2A 5C 2B - 5C 2C 5C 2D 5C 2E 5C 2F |\(\)\*\+\,\-\.\/|
+ 00000020 5C 31 5C 32 5C 33 5C 34 - 5C 35 5C 36 5C 37 5C 38 |\1\2\3\4\5\6\7\8|
+ 00000030 20 5C 39 5C 3A 5C 3B 5C - 3C 5C 3D 5C 3E 5C 3F 5C | \9\:\;\<\=\>\?\|
+ 00000040 40 5C 41 5C 42 5C 43 5C - 44 1B 5C 46 5C 47 5C 48 |@\A\B\C\D.\F\G\H|
+ 00000050 5C 49 5C 4A 5C 4B 5C 4C - 5C 4D 5C 4E 5C 4F 5C 50 |\I\J\K\L\M\N\O\P|
+ 00000060 5C 51 5C 52 5C 53 5C 54 - 20 5C 55 5C 56 5C 57 5C |\Q\R\S\T \U\V\W\|
+ 00000070 58 5C 59 5C 5A 5C 5B 5C - 5C 5D 5C 5E 5C 5F 5C 60 |X\Y\Z\[\\]\^\_\`|
+ 00000080 07 08 20 20 5C 64 1B 0C - 5C 67 5C 68 5C 69 5C 6A |.. \d..\g\h\i\j|
+ 00000090 5C 6B 5C 6C 5C 6D 0A 5C - 6F 5C 70 20 5C 71 0D 5C |\k\l\m.\o\p \q.\|
+ 000000A0 73 09 5C 75 0B 5C 77 5C - 78 5C 79 5C 7A 5C 7B 5C |s.\u.\w\x\y\z\{\|
+ 000000B0 7C 5C 7D 5C 7E 20 E2 82 - AC 64 20 EF BF BD 20 12 ||\}\~ ...d ... .|
+ 000000C0 33 20 78 20 53 20 53 34 - 0A |3 x S 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
+stdin:
+ print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn
+ chmod +x pfn
+ ./pfn $'\ \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/ \1\2\3\4\5\6' \
+ $'a\0b' $'a\01b' $'\7\8\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I' \
+ $'\J\K\L\M\N\O\P\Q\R\S\T\U1\V\W\X\Y\Z\[\\\]\^\_\`\a\b\d\e' \
+ $'\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u1\v\w\x1\y\z\{\|\}\~ $x' \
+ $'\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=2147483647
+ typeset -i1 wc=0x0A
+ dasc=
+ nl=${wc#1#}
+ while IFS= read -r line; do
+ line=$line$nl
+ while [[ -n $line ]]; do
+ hv=1#${line::1}
+ if (( (pos & 15) == 0 )); then
+ (( pos )) && print -r -- "$dasc|"
+ print -n "${pos#16#} "
+ dasc=' |'
+ fi
+ print -n "${hv#16#} "
+ if (( (hv < 32) || (hv > 126) )); then
+ dasc=$dasc.
+ else
+ dasc=$dasc${line::1}
+ fi
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ line=${line:1}
+ done
+ done
+ while (( pos & 15 )); do
+ print -n ' '
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ done
+ (( hv == 2147483647 )) || print -r -- "$dasc|"
+ }
+expected-stdout:
+ 00000000 20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F | !"#$%&'()*+,-./|
+ 00000010 20 01 02 03 04 05 06 0A - 61 0A 61 01 62 0A 07 38 | .......a.a.b..8|
+ 00000020 39 3A 3B 3C 3D 3E 3F 40 - 41 42 43 44 1B 46 47 48 |9:;<=>?@ABCD.FGH|
+ 00000030 49 0A 4A 4B 4C 4D 4E 4F - 50 51 52 53 54 01 56 57 |I.JKLMNOPQRST.VW|
+ 00000040 58 59 5A 5B 5C 5D 5E 5F - 60 07 08 64 1B 0A 0C 67 |XYZ[\]^_`..d...g|
+ 00000050 68 69 6A 6B 6C 6D 0A 6F - 70 71 0D 73 09 01 0B 77 |hijklm.opq.s...w|
+ 00000060 01 79 7A 7B 7C 7D 7E 20 - 24 78 0A E2 82 AC 64 0A |.yz{|}~ $x....d.|
+ 00000070 EF BF BD 0A C4 A3 0A 66 - 6E 0A 13 34 0A 9C 0A 9C |.......fn..4....|
+ 00000080 35 0A 01 0A 01 0A 7F 0A - 82 80 A6 0A 61 0A 62 0A |5...........a.b.|
+---
+name: dollar-quotes-in-heredocs-strings
+description:
+ They are, however, not parsed in here documents, here strings
+ (outside of string delimiters) or regular strings, but in
+ parameter substitutions.
+stdin:
+ cat <<EOF
+ dollar = strchr(s, '$'); /* ' */
+ foo " bar \" baz
+ EOF
+ cat <<$'a\tb'
+ a\tb
+ a b
+ cat <<<"dollar = strchr(s, '$'); /* ' */"
+ cat <<<'dollar = strchr(s, '\''$'\''); /* '\'' */'
+ x="dollar = strchr(s, '$'); /* ' */"
+ cat <<<"$x"
+ cat <<<$'a\E[0m\tb'
+ unset nl; print -r -- "x${nl:=$'\n'}y"
+ echo "1 foo\"bar"
+ # cf & HEREDOC
+ cat <<EOF
+ 2 foo\"bar
+ EOF
+ # probably never reached for here strings?
+ cat <<<"3 foo\"bar"
+ cat <<<"4 foo\\\"bar"
+ cat <<<'5 foo\"bar'
+ # old scripts use this (e.g. ncurses)
+ echo "^$"
+ # make sure this works, outside of quotes
+ cat <<<'7'$'\t''.'
+expected-stdout:
+ dollar = strchr(s, '$'); /* ' */
+ foo " bar \" baz
+ a\tb
+ dollar = strchr(s, '$'); /* ' */
+ dollar = strchr(s, '$'); /* ' */
+ dollar = strchr(s, '$'); /* ' */
+ a b
+ x
+ y
+ 1 foo"bar
+ 2 foo\"bar
+ 3 foo"bar
+ 4 foo\"bar
+ 5 foo\"bar
+ ^$
+ 7 .
+---
+name: dot-needs-argument
+description:
+ check Debian #415167 solution: '.' without arguments should fail
+stdin:
+ "$__progname" -c .
+ "$__progname" -c source
+expected-exit: e != 0
+expected-stderr-pattern:
+ /\.: missing argument.*\n.*source: missing argument/
+---
+name: dot-errorlevel
+description:
+ Ensure dot resets $?
+stdin:
+ :>dotfile
+ (exit 42)
+ . ./dotfile
+ echo 1 $? .
+expected-stdout:
+ 1 0 .
+---
+name: alias-function-no-conflict
+description:
+ make aliases not conflict with function definitions
+stdin:
+ # POSIX function can be defined, but alias overrides it
+ alias foo='echo bar'
+ foo
+ foo() {
+ echo baz
+ }
+ foo
+ unset -f foo
+ foo 2>/dev/null || echo rab
+ # alias overrides ksh function
+ alias korn='echo bar'
+ korn
+ function korn {
+ echo baz
+ }
+ korn
+ # alias temporarily overrides POSIX function
+ bla() {
+ echo bfn
+ }
+ bla
+ alias bla='echo bal'
+ bla
+ unalias bla
+ bla
+expected-stdout:
+ bar
+ bar
+ bar
+ bar
+ bar
+ bfn
+ bal
+ bfn
+---
+name: bash-function-parens
+description:
+ ensure the keyword function is ignored when preceding
+ POSIX style function declarations (bashism)
+stdin:
+ mk() {
+ echo '#!'"$__progname"
+ echo "$1 {"
+ echo ' echo "bar='\''$0'\'\"
+ echo '}'
+ print -r -- "${2:-foo}"
+ }
+ mk 'function foo' >f-korn
+ mk 'foo ()' >f-dash
+ mk 'function foo ()' >f-bash
+ print '#!'"$__progname"'\nprint -r -- "${0%/f-argh}"' >f-argh
+ chmod +x f-*
+ u=$(./f-argh)
+ x="korn: $(./f-korn)"; echo "${x/@("$u")/.}"
+ x="dash: $(./f-dash)"; echo "${x/@("$u")/.}"
+ x="bash: $(./f-bash)"; echo "${x/@("$u")/.}"
+expected-stdout:
+ korn: bar='foo'
+ dash: bar='./f-dash'
+ bash: bar='./f-bash'
+---
+name: integer-base-one-1
+description:
+ check if the use of fake integer base 1 works
+stdin:
+ set -U
+ typeset -Uui16 i0=1#ď i1=1#€
+ typeset -i1 o0a=64
+ typeset -i1 o1a=0x263A
+ typeset -Uui1 o0b=0x7E
+ typeset -Uui1 o1b=0xFDD0
+ integer px=0xCAFE 'p0=1# ' p1=1#… pl=1#f
+ echo "in <$i0> <$i1>"
+ echo "out <${o0a#1#}|${o0b#1#}> <${o1a#1#}|${o1b#1#}>"
+ typeset -Uui1 i0 i1
+ echo "pass <$px> <$p0> <$p1> <$pl> <${i0#1#}|${i1#1#}>"
+ typeset -Uui16 tv1=1#~ tv2=1# tv3=1#€ tv4=1# tv5=1#Ŕ tv6=1#Á tv7=1#  tv8=1#€
+ echo "specX <${tv1#16#}> <${tv2#16#}> <${tv3#16#}> <${tv4#16#}> <${tv5#16#}> <${tv6#16#}> <${tv7#16#}> <${tv8#16#}>"
+ typeset -i1 tv1 tv2 tv3 tv4 tv5 tv6 tv7 tv8
+ echo "specW <${tv1#1#}> <${tv2#1#}> <${tv3#1#}> <${tv4#1#}> <${tv5#1#}> <${tv6#1#}> <${tv7#1#}> <${tv8#1#}>"
+ typeset -i1 xs1=0xEF7F xs2=0xEF80 xs3=0xFDD0
+ echo "specU <${xs1#1#}> <${xs2#1#}> <${xs3#1#}>"
+expected-stdout:
+ in <16#EFEF> <16#20AC>
+ out <@|~> <âş|ď·>
+ pass <16#cafe> <1# > <1#…> <1#f> <ď|€>
+ specX <7E> <7F> <EF80> <EF81> <EFC0> <EFC1> <A0> <80>
+ specW <~> <> <€> <> <Ŕ> <Á> < > <€>
+ specU <> <€> <ď·>
+---
+name: integer-base-one-2a
+description:
+ check if the use of fake integer base 1 stops at correct characters
+stdin:
+ set -U
+ integer x=1#foo
+ echo /$x/
+expected-stderr-pattern:
+ /1#foo: unexpected 'oo'/
+expected-exit: e != 0
+---
+name: integer-base-one-2b
+description:
+ check if the use of fake integer base 1 stops at correct characters
+stdin:
+ set -U
+ integer x=1#Ŕ€
+ echo /$x/
+expected-stderr-pattern:
+ /1#Ŕ€: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2c1
+description:
+ check if the use of fake integer base 1 stops at correct characters
+stdin:
+ set -U
+ integer x=1#…
+ echo /$x/
+expected-stdout:
+ /1#…/
+---
+name: integer-base-one-2c2
+description:
+ check if the use of fake integer base 1 stops at correct characters
+stdin:
+ set +U
+ integer x=1#…
+ echo /$x/
+expected-stderr-pattern:
+ /1#…: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d1
+description:
+ check if the use of fake integer base 1 handles octets okay
+stdin:
+ set -U
+ typeset -i16 x=1#˙
+ echo /$x/ # invalid utf-8
+expected-stdout:
+ /16#efff/
+---
+name: integer-base-one-2d2
+description:
+ check if the use of fake integer base 1 handles octets
+stdin:
+ set -U
+ typeset -i16 x=1#Â
+ echo /$x/ # invalid 2-byte
+expected-stdout:
+ /16#efc2/
+---
+name: integer-base-one-2d3
+description:
+ check if the use of fake integer base 1 handles octets
+stdin:
+ set -U
+ typeset -i16 x=1#ď
+ echo /$x/ # invalid 2-byte
+expected-stdout:
+ /16#efef/
+---
+name: integer-base-one-2d4
+description:
+ check if the use of fake integer base 1 stops at invalid input
+stdin:
+ set -U
+ typeset -i16 x=1#ďżŔ
+ echo /$x/ # invalid 3-byte
+expected-stderr-pattern:
+ /1#ďżŔ: unexpected 'ż'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d5
+description:
+ check if the use of fake integer base 1 stops at invalid input
+stdin:
+ set -U
+ typeset -i16 x=1#Ŕ€
+ echo /$x/ # non-minimalistic
+expected-stderr-pattern:
+ /1#Ŕ€: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d6
+description:
+ check if the use of fake integer base 1 stops at invalid input
+stdin:
+ set -U
+ typeset -i16 x=1#ŕ€€
+ echo /$x/ # non-minimalistic
+expected-stderr-pattern:
+ /1#ŕ€€: unexpected '€'/
+expected-exit: e != 0
+---
+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ă“ă‚“ă«ăˇăŻďĽ'
+ 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-3As
+ typeset -Uui16 -Z11 pos=0
+ typeset -Uui16 -Z5 hv=2147483647
+ typeset -i1 wc=0x0A
+ dasc=
+ nl=${wc#1#}
+ while IFS= read -r line; do
+ line=$line$nl
+ while [[ -n $line ]]; do
+ hv=1#${line::1}
+ if (( (pos & 15) == 0 )); then
+ (( pos )) && print -r -- "$dasc|"
+ print -n "${pos#16#} "
+ dasc=' |'
+ fi
+ print -n "${hv#16#} "
+ if (( (hv < 32) || (hv > 126) )); then
+ dasc=$dasc.
+ else
+ dasc=$dasc${line::1}
+ fi
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ line=${line:1}
+ done
+ done
+ while (( pos & 15 )); do
+ print -n ' '
+ (( (pos++ & 15) == 7 )) && print -n -- '- '
+ done
+ (( hv == 2147483647 )) || print -r -- "$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 7A 0A - |.z.|
+---
+name: integer-base-one-3Ws
+description:
+ some sample code for hexdumping UCS-2
+ not NUL safe; input lines must be NL terminated
+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-3Ws
+ typeset -Uui16 -Z11 pos=0
+ typeset -Uui16 -Z7 hv
+ typeset -i1 wc=0x0A
+ typeset -i lpos
+ dasc=
+ nl=${wc#1#}
+ while IFS= read -r line; do
+ line=$line$nl
+ lpos=0
+ while (( lpos < ${#line} )); do
+ wc=1#${line:(lpos++):1}
+ if (( (wc < 32) || \
+ ((wc > 126) && (wc < 160)) )); then
+ dch=.
+ elif (( (wc & 0xFF80) == 0xEF80 )); then
+ dch=ďż˝
+ else
+ dch=${wc#1#}
+ fi
+ if (( (pos & 7) == 7 )); then
+ dasc=$dasc$dch
+ dch=
+ elif (( (pos & 7) == 0 )); then
+ (( pos )) && print -r -- "$dasc|"
+ print -n "${pos#16#} "
+ dasc=' |'
+ fi
+ let hv=wc
+ print -n "${hv#16#} "
+ (( (pos++ & 7) == 3 )) && \
+ print -n -- '- '
+ dasc=$dasc$dch
+ done
+ done
+ while (( pos & 7 )); do
+ print -n ' '
+ (( (pos++ & 7) == 3 )) && print -n -- '- '
+ done
+ (( hv == 2147483647 )) || print -r -- "$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 -r -- "$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 -r -- "$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 UCS-2; 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 -r -- "$dasc|"
+ print -n "${pos#16#} "
+ dasc=' |'
+ fi
+ print -n "${hv#16#} "
+ (( (pos++ & 7) == 3 )) && \
+ print -n -- '- '
+ dasc=$dasc$dch
+ done
+ fi
+ while (( pos & 7 )); do
+ print -n ' '
+ (( (pos++ & 7) == 3 )) && print -n -- '- '
+ done
+ (( hv == 2147483647 )) || print -r -- "$dasc|"
+ }
+expected-stdout:
+ 00000000 0048 0065 006C 006C - 006F 002C 0020 0057 |Hello, W|
+ 00000008 006F 0072 006C 0064 - 0021 005C 000A 3053 |orld!\.ă“|
+ 00000010 3093 306B 3061 306F - FF01 000A 0001 0002 |ă‚“ă«ăˇăŻďĽ...|
+ 00000018 0003 0004 0005 0006 - 0007 0008 0009 000A |........|
+ 00000020 000B 000C 000D 000E - 000F 0010 0011 0012 |........|
+ 00000028 0013 0014 0015 0016 - 0017 0018 0019 001A |........|
+ 00000030 001B 001C 001D 001E - 001F 0020 0021 0022 |..... !"|
+ 00000038 0023 0024 0025 0026 - 0027 0028 0029 002A |#$%&'()*|
+ 00000040 002B 002C 002D 002E - 002F 0030 0031 0032 |+,-./012|
+ 00000048 0033 0034 0035 0036 - 0037 0038 0039 003A |3456789:|
+ 00000050 003B 003C 003D 003E - 003F 0040 0041 0042 |;<=>?@AB|
+ 00000058 0043 0044 0045 0046 - 0047 0048 0049 004A |CDEFGHIJ|
+ 00000060 004B 004C 004D 004E - 004F 0050 0051 0052 |KLMNOPQR|
+ 00000068 0053 0054 0055 0056 - 0057 0058 0059 005A |STUVWXYZ|
+ 00000070 005B 005C 005D 005E - 005F 0060 0061 0062 |[\]^_`ab|
+ 00000078 0063 0064 0065 0066 - 0067 0068 0069 006A |cdefghij|
+ 00000080 006B 006C 006D 006E - 006F 0070 0071 0072 |klmnopqr|
+ 00000088 0073 0074 0075 0076 - 0077 0078 0079 007A |stuvwxyz|
+ 00000090 007B 007C 007D 007E - 007F 0080 0081 0082 |{|}~....|
+ 00000098 0083 0084 0085 0086 - 0087 0088 0089 008A |........|
+ 000000A0 008B 008C 008D 008E - 008F 0090 0091 0092 |........|
+ 000000A8 0093 0094 0095 0096 - 0097 0098 0099 009A |........|
+ 000000B0 009B 009C 009D 009E - 009F 00A0 00A1 00A2 |..... ¡¢|
+ 000000B8 00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA |£¤¥¦§¨©ª|
+ 000000C0 00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2 |«¬­®¯°±²|
+ 000000C8 00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA |³´µ¶·¸¹º|
+ 000000D0 00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2 |»¼½¾¿ÀĂĂ‚|
+ 000000D8 00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA |ĂÄÅÆÇĂÉÊ|
+ 000000E0 00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2 |Ă‹ĂŚĂŤĂŽĂŹĂĂ‘Ă’|
+ 000000E8 00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA |ÓÔÕÖ×ĂÙÚ|
+ 000000F0 00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2 |ÛÜÝÞßàáâ|
+ 000000F8 00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA |ãäåæçèéê|
+ 00000100 00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2 |ëìíîïðñò|
+ 00000108 00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA |óôõö÷øùú|
+ 00000110 00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A |ûüýþÿ.�.|
+ 00000118 EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80 |�.���.��|
+ 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���|
+ 00000128 EFBE EFEF EFBF EFBF - 000A 0000 007A 000A |����..z.|
+---
+name: integer-base-one-4
+description:
+ Check if ksh93-style base-one integers work
+category: !smksh
+stdin:
+ set -U
+ echo 1 $(('a'))
+ (echo 2f $(('aa'))) 2>&1 | sed "s/^[^']*'/2p '/"
+ echo 3 $(('…'))
+ x="'a'"
+ echo "4 <$x>"
+ echo 5 $(($x))
+ echo 6 $((x))
+expected-stdout:
+ 1 97
+ 2p 'aa': multi-character character constant
+ 3 8230
+ 4 <'a'>
+ 5 97
+ 6 97
+---
+name: integer-base-one-5A
+description:
+ Check to see that we’re NUL and UCS safe
+category: !shell:ebcdic-yes
+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-5E
+description:
+ Check to see that we’re NUL and UCS safe
+category: !shell:ebcdic-no
+stdin:
+ set +U
+ print 'a\0b\xfdz' >x
+ read -a y <x
+ set -U
+ typeset -Uui16 y
+ print ${y[*]} .
+expected-stdout:
+ 16#81 16#0 16#82 16#FD 16#A9 .
+---
+name: integer-base-one-5W
+description:
+ Check to see that we’re NUL and UCS 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 that ulimit as used in dot.mksh works or is stubbed
+stdin:
+ ulimit -c 0
+---
+name: ulimit-2
+description:
+ Check if we can use a specific syntax idiom for ulimit
+ XXX Haiku works, but only for -n and -V
+category: !os:haiku,!os:syllable
+stdin:
+ if ! x=$(ulimit -d) || [[ $x = unknown ]]; then
+ #echo expected to fail on this OS
+ echo okay
+ else
+ ulimit -dS $x && echo okay
+ fi
+expected-stdout:
+ okay
+---
+name: ulimit-3
+description:
+ Check that there are no duplicate limits (if this fails,
+ immediately contact with system information the developers)
+stdin:
+ [[ -z $(set | grep ^opt) ]]; mis=$?
+ set | grep ^opt | sed 's/^/unexpectedly set in environment: /'
+ opta='<used for showing all limits>'
+ optH='<used to set hard limits>'
+ optS='<used to set soft limits>'
+ ulimit -a >tmpf
+ set -o noglob
+ while IFS= read -r line; do
+ x=${line:1:1}
+ if [[ -z $x || ${#x}/${%x} != 1/1 ]]; then
+ print -r -- "weird line: $line"
+ (( mis |= 1 ))
+ continue
+ fi
+ set -- $line
+ nameref v=opt$x
+ if [[ -n $v ]]; then
+ print -r -- "duplicate -$x \"$2\" already seen as \"$v\""
+ (( mis |= 2 ))
+ fi
+ v=$2
+ done <tmpf
+ if (( mis & 2 )); then
+ echo failed
+ elif (( mis & 1 )); then
+ echo inconclusive
+ else
+ echo done
+ fi
+expected-stdout:
+ done
+---
+name: redir-1
+description:
+ Check some of the most basic invariants of I/O redirection
+stdin:
+ i=0
+ function d {
+ print o$i.
+ print -u2 e$((i++)).
+ }
+ d >a 2>b
+ echo =1=
+ cat a
+ echo =2=
+ cat b
+ echo =3=
+ d 2>&1 >c
+ echo =4=
+ cat c
+ echo =5=
+expected-stdout:
+ =1=
+ o0.
+ =2=
+ e0.
+ =3=
+ e1.
+ =4=
+ o1.
+ =5=
+---
+name: bashiop-1
+description:
+ Check if GNU bash-like I/O redirection works
+ Part 1: this is also supported by GNU bash
+stdin:
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout &>foo
+ echo ===
+ cat foo
+expected-stdout:
+ tri
+ ===
+ ras
+ dwa
+---
+name: bashiop-2a
+description:
+ Check if GNU bash-like I/O redirection works
+ Part 2: this is *not* supported by GNU bash
+stdin:
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout 3&>foo
+ echo ===
+ cat foo
+expected-stdout:
+ ras
+ ===
+ dwa
+ tri
+---
+name: bashiop-2b
+description:
+ Check if GNU bash-like I/O redirection works
+ Part 2: this is *not* supported by GNU bash
+stdin:
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout 3>foo &>&3
+ echo ===
+ cat foo
+expected-stdout:
+ ===
+ ras
+ dwa
+ tri
+---
+name: bashiop-2c
+description:
+ Check if GNU bash-like I/O redirection works
+ Part 2: this is supported by GNU bash 4 only
+stdin:
+ echo mir >foo
+ set -o noclobber
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout &>>foo
+ echo ===
+ cat foo
+expected-stdout:
+ tri
+ ===
+ mir
+ ras
+ dwa
+---
+name: bashiop-3a
+description:
+ Check if GNU bash-like I/O redirection fails correctly
+ Part 1: this is also supported by GNU bash
+stdin:
+ echo mir >foo
+ set -o noclobber
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout &>foo
+ echo ===
+ cat foo
+expected-stdout:
+ ===
+ mir
+expected-stderr-pattern: /.*: can't (create|overwrite) .*/
+---
+name: bashiop-3b
+description:
+ Check if GNU bash-like I/O redirection fails correctly
+ Part 2: this is *not* supported by GNU bash
+stdin:
+ echo mir >foo
+ set -o noclobber
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ threeout &>|foo
+ echo ===
+ cat foo
+expected-stdout:
+ tri
+ ===
+ ras
+ dwa
+---
+name: bashiop-4
+description:
+ Check if GNU bash-like I/O redirection works
+ Part 4: this is also supported by GNU bash,
+ but failed in some mksh versions
+stdin:
+ exec 3>&1
+ function threeout {
+ echo ras
+ echo dwa >&2
+ echo tri >&3
+ }
+ function blubb {
+ [[ -e bar ]] && threeout "$bf" &>foo
+ }
+ blubb
+ echo -n >bar
+ blubb
+ echo ===
+ cat foo
+expected-stdout:
+ tri
+ ===
+ ras
+ dwa
+---
+name: bashiop-5
+description:
+ Check if GNU bash-like I/O redirection is only supported
+ in !POSIX !sh mode as it breaks existing scripts' syntax
+stdin:
+ :>x; echo 1 "$("$__progname" -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
+ :>x; echo 2 "$("$__progname" -o posix -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
+ :>x; echo 3 "$("$__progname" -o sh -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
+expected-stdout:
+ 1 = foo echo bar .
+ 2 = bar .
+ 3 = bar .
+---
+name: oksh-eval
+description:
+ Check expansions.
+stdin:
+ a=
+ for n in ${a#*=}; do echo 1hu ${n} .; done
+ for n in "${a#*=}"; do echo 1hq ${n} .; done
+ for n in ${a##*=}; do echo 2hu ${n} .; done
+ for n in "${a##*=}"; do echo 2hq ${n} .; done
+ for n in ${a%=*}; do echo 1pu ${n} .; done
+ for n in "${a%=*}"; do echo 1pq ${n} .; done
+ for n in ${a%%=*}; do echo 2pu ${n} .; done
+ for n in "${a%%=*}"; do echo 2pq ${n} .; done
+expected-stdout:
+ 1hq .
+ 2hq .
+ 1pq .
+ 2pq .
+---
+name: oksh-and-list-error-1
+description:
+ Test exit status of rightmost element in 2 element && list in -e mode
+stdin:
+ true && false
+ echo "should not print"
+arguments: !-e!
+expected-exit: e != 0
+---
+name: oksh-and-list-error-2
+description:
+ Test exit status of rightmost element in 3 element && list in -e mode
+stdin:
+ true && true && false
+ echo "should not print"
+arguments: !-e!
+expected-exit: e != 0
+---
+name: oksh-or-list-error-1
+description:
+ Test exit status of || list in -e mode
+stdin:
+ false || false
+ echo "should not print"
+arguments: !-e!
+expected-exit: e != 0
+---
+name: oksh-longline-crash
+description:
+ This used to cause a core dump
+stdin:
+ ulimit -c 0
+ deplibs="-lz -lpng /usr/local/lib/libjpeg.la -ltiff -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -ltiff -ljpeg -lz -lpng -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk_pixbuf.la -lz -lpng /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -lz -lz /usr/local/lib/libxml.la -lz -lz -lz /usr/local/lib/libxml.la -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lglib -lgmodule /usr/local/lib/libgdk.la /usr/local/lib/libgtk.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade.la -lz -lz -lz /usr/local/lib/libxml.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile /usr/local/lib/libesd.la -lm -lz /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lz /usr/local/lib/libgdk_imlib.la /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lz -lungif -lz -ljpeg -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade-gnome.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib"
+ specialdeplibs="-lgnomeui -lart_lgpl -lgdk_imlib -ltiff -ljpeg -lungif -lpng -lz -lSM -lICE -lgtk -lgdk -lgmodule -lintl -lXext -lX11 -lgnome -lgnomesupport -lesd -laudiofile -lm -lglib"
+ for deplib in $deplibs; do
+ case $deplib in
+ -L*)
+ new_libs="$deplib $new_libs"
+ ;;
+ *)
+ case " $specialdeplibs " in
+ *" $deplib "*)
+ new_libs="$deplib $new_libs";;
+ esac
+ ;;
+ esac
+ done
+---
+name: oksh-seterror-1
+description:
+ The -e flag should be ignored when executing a compound list
+ followed by an if statement.
+stdin:
+ if true; then false && false; fi
+ true
+arguments: !-e!
+expected-exit: e == 0
+---
+name: oksh-seterror-2
+description:
+ The -e flag should be ignored when executing a compound list
+ followed by an if statement.
+stdin:
+ if true; then if true; then false && false; fi; fi
+ true
+arguments: !-e!
+expected-exit: e == 0
+---
+name: oksh-seterror-3
+description:
+ The -e flag should be ignored when executing a compound list
+ followed by an elif statement.
+stdin:
+ if true; then :; elif true; then false && false; fi
+arguments: !-e!
+expected-exit: e == 0
+---
+name: oksh-seterror-4
+description:
+ The -e flag should be ignored when executing a pipeline
+ beginning with '!'
+stdin:
+ for i in 1 2 3
+ do
+ false && false
+ true || false
+ done
+arguments: !-e!
+expected-exit: e == 0
+---
+name: oksh-seterror-5
+description:
+ The -e flag should be ignored when executing a pipeline
+ beginning with '!'
+stdin:
+ ! true | false
+ true
+arguments: !-e!
+expected-exit: e == 0
+---
+name: oksh-seterror-6
+description:
+ When trapping ERR and EXIT, both traps should run in -e mode
+ when an error occurs.
+stdin:
+ trap 'echo EXIT' EXIT
+ trap 'echo ERR' ERR
+ set -e
+ false
+ echo DONE
+ exit 0
+arguments: !-e!
+expected-exit: e != 0
+expected-stdout:
+ ERR
+ EXIT
+---
+name: oksh-seterror-7
+description:
+ The -e flag within a command substitution should be honored
+stdin:
+ echo $( set -e; false; echo foo )
+arguments: !-e!
+expected-stdout:
+
+---
+name: oksh-input-comsub
+description:
+ A command substitution using input redirection should exit with
+ failure if the input file does not exist.
+stdin:
+ var=$(< non-existent)
+expected-exit: e != 0
+expected-stderr-pattern: /non-existent/
+---
+name: oksh-empty-for-list
+description:
+ A for list which expands to zero items should not execute the body.
+stdin:
+ set foo bar baz ; for out in ; do echo $out ; done
+---
+name: oksh-varfunction-mod1
+description:
+ (Inspired by PR 2450 on OpenBSD.) Calling
+ FOO=bar f
+ where f is a ksh style function, should not set FOO in the current
+ env. If f is a Bourne style function, (new) also not. Furthermore,
+ the function should receive a correct value of FOO. However, differing
+ from oksh, setting FOO in the function itself must change the value in
+ setting FOO in the function itself should not change the value in
+ global environment.
+stdin:
+ print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \
+ 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \
+ done >env; chmod +x env; PATH=.$PATHSEP$PATH
+ function k {
+ if [ x$FOO != xbar ]; then
+ echo 1
+ return 1
+ fi
+ x=$(env | grep FOO)
+ if [ "x$x" != "xFOO=bar" ]; then
+ echo 2
+ return 1;
+ fi
+ FOO=foo
+ return 0
+ }
+ b () {
+ if [ x$FOO != xbar ]; then
+ echo 3
+ return 1
+ fi
+ x=$(env | grep FOO)
+ if [ "x$x" != "xFOO=bar" ]; then
+ echo 4
+ return 1;
+ fi
+ FOO=foo
+ return 0
+ }
+ FOO=bar k
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+ if [ x$FOO != x ]; then
+ exit 1
+ fi
+ FOO=bar b
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+ if [ x$FOO != x ]; then
+ exit 1
+ fi
+ FOO=barbar
+ FOO=bar k
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+ if [ x$FOO != xbarbar ]; then
+ exit 1
+ fi
+ FOO=bar b
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+ if [ x$FOO != xbarbar ]; then
+ exit 1
+ fi
+---
+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
+ XXX fails on some old Perl installations
+need-pass: no
+stdin:
+ cat >cld <<-EOF
+ #!$__perlname
+ open(my \$fh, ">&", 9) or die "E: open \$!";
+ syswrite(\$fh, "Fowl\\n", 5) or die "E: write \$!";
+ EOF
+ chmod +x cld
+ exec 9>&1
+ ./cld
+expected-exit: e != 0
+expected-stderr-pattern:
+ /E: open /
+---
+name: fd-cloexec-2
+description:
+ Verify that file descriptors > 2 are not private for POSIX shells
+ See Debian Bug #154540, Closes: #499139
+ XXX fails on some old Perl installations
+need-pass: no
+stdin:
+ cat >cld <<-EOF
+ #!$__perlname
+ open(my \$fh, ">&", 9) or die "E: open \$!";
+ syswrite(\$fh, "Fowl\\n", 5) or die "E: write \$!";
+ EOF
+ chmod +x cld
+ test -n "$POSH_VERSION" || set -o posix
+ exec 9>&1
+ ./cld
+expected-stdout:
+ Fowl
+---
+name: fd-cloexec-3
+description:
+ Another check for close-on-exec
+stdin:
+ print '#!'"$__progname" >ts
+ cat >>ts <<'EOF'
+ s=ERR
+ read -rN-1 -u$1 s 2>/dev/null; e=$?
+ print -r -- "($1, $((!e)), $s)"
+ EOF
+ chmod +x ts
+ print foo >tx
+ runtest() {
+ s=$1; shift
+ print -r -- $("$__progname" "$@" -c "$s") "$@" .
+ }
+ runtest 'exec 3<tx; ./ts 3 3<&3; ./ts 3'
+ runtest 'exec 3<tx; ./ts 3 3<&3; ./ts 3' -o posix
+ runtest 'exec 3<tx; ./ts 3 3<&3; ./ts 3' -o sh
+ runtest 'exec 3<tx; ./ts 4 4<&3; ./ts 4 4<&3'
+ runtest 'exec 3<tx; ./ts 3 3<&3; ./ts 3 3<&3'
+expected-stdout:
+ (3, 1, foo) (3, 0, ERR) .
+ (3, 1, foo) (3, 1, ) -o posix .
+ (3, 1, foo) (3, 1, ) -o sh .
+ (4, 1, foo) (4, 1, ) .
+ (3, 1, foo) (3, 1, ) .
+---
+name: comsub-1a
+description:
+ 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 1 $(case 1 in (1) echo yes;; (2) echo no;; esac) .
+ echo 2 $(case 1 in 1) echo yes;; 2) echo no;; esac) .
+ TEST=1234; echo 3 ${TEST: $(case 1 in (1) echo 1;; (*) echo 2;; esac)} .
+ TEST=5678; echo 4 ${TEST: $(case 1 in 1) echo 1;; *) echo 2;; esac)} .
+ a=($(case 1 in (1) echo 1;; (*) echo 2;; esac)); echo 5 ${a[0]} .
+ a=($(case 1 in 1) echo 1;; *) echo 2;; esac)); echo 6 ${a[0]} .
+expected-stdout:
+ 1 yes .
+ 2 yes .
+ 3 234 .
+ 4 678 .
+ 5 1 .
+ 6 1 .
+---
+name: comsub-1b
+description:
+ COMSUB are now parsed recursively, so this works
+ Fails on: pdksh bash2 bash3 bash4 zsh
+ Passes on: ksh93 mksh(20110313+)
+stdin:
+ echo 1 $(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10)) .
+ echo 2 $(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20)) .
+ (( a = $(case 1 in (1) echo 1;; (*) echo 2;; esac) )); echo 3 $a .
+ (( a = $(case 1 in 1) echo 1;; *) echo 2;; esac) )); echo 4 $a .
+ a=($(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))); echo 5 ${a[0]} .
+ a=($(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))); echo 6 ${a[0]} .
+expected-stdout:
+ 1 11 .
+ 2 21 .
+ 3 1 .
+ 4 1 .
+ 5 11 .
+ 6 21 .
+---
+name: comsub-2
+description:
+ RedHat BZ#496791 – another case of missing recursion
+ in parsing COMSUB expressions
+ Fails on: pdksh bash2 bash3Âą bash4Âą zsh
+ Passes on: ksh93 mksh(20110305+)
+ â‘  bash[34] seem to choke on comment ending with backslash-newline
+stdin:
+ # a comment with " ' \
+ x=$(
+ echo yes
+ # a comment with " ' \
+ )
+ echo $x
+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:
+ print '#!'"$__progname"'\necho 1234' >id; chmod +x id; PATH=.$PATHSEP$PATH
+ 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 u) echo x ;;& *) echo $1 ;; esac; }
+ typeset -f x
+expected-stdout:
+ x() {
+ case $1 in
+ (u)
+ \echo x
+ ;|
+ (*)
+ \echo $1
+ ;;
+ esac
+ }
+---
+name: comsub-5
+description:
+ Check COMSUB works with aliases (does not expand them twice)
+ and reentrancy safety
+stdin:
+ print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn
+ chmod +x pfn
+ alias echo='echo a'
+ foo() {
+ echo moo
+ ./pfn "$(echo foo)"
+ }
+ ./pfn "$(echo b)"
+ typeset -f foo >x
+ cat x
+ foo
+ . ./x
+ typeset -f foo
+ foo
+expected-stdout:
+ a b
+ foo() {
+ \echo a moo
+ ./pfn "$(\echo a foo )"
+ }
+ a moo
+ a foo
+ foo() {
+ \echo a moo
+ ./pfn "$(\echo a foo )"
+ }
+ a moo
+ a foo
+---
+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
+ time for i in {1,2,3} ; do 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_FUNSUB_VALSUB
+ echo $(true) $((1+ 2)) ${ :;} ${| REPLY=x;}
+ #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
+ #wdarrassign
+ case x in
+ x) a+=b; c+=(d e)
+ esac
+ #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() {
+ time for i in {1,2,3} ; do echo $i ; done
+ }
+ inline_TFOR_TTIME() {
+ time for i in {1,2,3}
+ do
+ \echo $i
+ done
+ }
+ function comsub_TFOR_TTIME { x=$(
+ time for i in {1,2,3} ; do echo $i ; done
+ ); }
+ function comsub_TFOR_TTIME {
+ x=$(time for i in {1,2,3} ; do \echo $i ; done )
+ }
+ function reread_TFOR_TTIME { x=$((
+ time for i in {1,2,3} ; do echo $i ; done
+ )|tr u x); }
+ function reread_TFOR_TTIME {
+ x=$( ( time for i in {1,2,3} ; do \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 {
+ \\builtin 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 { \\builtin 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 { \\builtin 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 {
+ \\builtin 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 { \\builtin 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 { \\builtin 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_FUNSUB_VALSUB() {
+ echo $(true) $((1+ 2)) ${ :;} ${| REPLY=x;}
+ }
+ inline_COMSUB_EXPRSUB_FUNSUB_VALSUB() {
+ \echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;}
+ }
+ function comsub_COMSUB_EXPRSUB_FUNSUB_VALSUB { x=$(
+ echo $(true) $((1+ 2)) ${ :;} ${| REPLY=x;}
+ ); }
+ function comsub_COMSUB_EXPRSUB_FUNSUB_VALSUB {
+ x=$(\echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;} )
+ }
+ function reread_COMSUB_EXPRSUB_FUNSUB_VALSUB { x=$((
+ echo $(true) $((1+ 2)) ${ :;} ${| REPLY=x;}
+ )|tr u x); }
+ function reread_COMSUB_EXPRSUB_FUNSUB_VALSUB {
+ x=$( ( \echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;} ) | \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 )
+ }
+ inline_wdarrassign() {
+ case x in
+ x) a+=b; c+=(d e)
+ esac
+ }
+ inline_wdarrassign() {
+ case x in
+ (x)
+ a+=b
+ \\builtin set -A c+ -- d e
+ ;;
+ esac
+ }
+ function comsub_wdarrassign { x=$(
+ case x in
+ x) a+=b; c+=(d e)
+ esac
+ ); }
+ function comsub_wdarrassign {
+ x=$(case x in (x) a+=b ; \\builtin set -A c+ -- d e ;; esac )
+ }
+ function reread_wdarrassign { x=$((
+ case x in
+ x) a+=b; c+=(d e)
+ esac
+ )|tr u x); }
+ function reread_wdarrassign {
+ x=$( ( case x in (x) a+=b ; \\builtin set -A c+ -- d e ;; esac ) | \tr u x )
+ }
+---
+name: comsub-torture-io
+description:
+ Check the tree dump functions work correctly with I/O redirection
+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" >&3
+ #TPAREN_TPIPE_TLIST
+ (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3
+ #TAND_TOR
+ cmd >&3 && >&3 echo ja || echo >&3 nein
+ #TSELECT
+ select file in *; do echo "<$file>" ; break >&3 ; done >&3
+ #TFOR_TTIME
+ for i in {1,2,3} ; do time >&3 echo $i ; done >&3
+ #TCASE
+ case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3
+ #TIF_TBANG_TDBRACKET_TELIF
+ if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3
+ #TWHILE
+ i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
+ #TUNTIL
+ i=10; until (( !--i )) >&3 ; do echo $i; done >&3
+ #TCOPROC
+ cat * >&3 |& >&3 ls
+ #TFUNCT_TBRACE_TASYNC
+ function korn { echo eins; echo >&3 zwei ; }
+ bourne () { logger * >&3 & }
+ #COMSUB_EXPRSUB
+ echo $(true >&3) $((1+ 2))
+ #0
+ EOD
+expected-stdout:
+ inline_TCOM() {
+ vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3
+ }
+ inline_TCOM() {
+ vara=1 varb="2 3" \cmd arg1 $arg2 "$arg3 4" >&3
+ }
+ function comsub_TCOM { x=$(
+ vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3
+ ); }
+ function comsub_TCOM {
+ x=$(vara=1 varb="2 3" \cmd arg1 $arg2 "$arg3 4" >&3 )
+ }
+ function reread_TCOM { x=$((
+ vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3
+ )|tr u x); }
+ function reread_TCOM {
+ x=$( ( vara=1 varb="2 3" \cmd arg1 $arg2 "$arg3 4" >&3 ) | \tr u x )
+ }
+ inline_TPAREN_TPIPE_TLIST() {
+ (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3
+ }
+ inline_TPAREN_TPIPE_TLIST() {
+ ( \echo $foo | \tr -dc 0-9 >&3
+ \echo >&3 ) >&3
+ }
+ function comsub_TPAREN_TPIPE_TLIST { x=$(
+ (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3
+ ); }
+ function comsub_TPAREN_TPIPE_TLIST {
+ x=$( ( \echo $foo | \tr -dc 0-9 >&3 ; \echo >&3 ) >&3 )
+ }
+ function reread_TPAREN_TPIPE_TLIST { x=$((
+ (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3
+ )|tr u x); }
+ function reread_TPAREN_TPIPE_TLIST {
+ x=$( ( ( \echo $foo | \tr -dc 0-9 >&3 ; \echo >&3 ) >&3 ) | \tr u x )
+ }
+ inline_TAND_TOR() {
+ cmd >&3 && >&3 echo ja || echo >&3 nein
+ }
+ inline_TAND_TOR() {
+ \cmd >&3 && \echo ja >&3 || \echo nein >&3
+ }
+ function comsub_TAND_TOR { x=$(
+ cmd >&3 && >&3 echo ja || echo >&3 nein
+ ); }
+ function comsub_TAND_TOR {
+ x=$(\cmd >&3 && \echo ja >&3 || \echo nein >&3 )
+ }
+ function reread_TAND_TOR { x=$((
+ cmd >&3 && >&3 echo ja || echo >&3 nein
+ )|tr u x); }
+ function reread_TAND_TOR {
+ x=$( ( \cmd >&3 && \echo ja >&3 || \echo nein >&3 ) | \tr u x )
+ }
+ inline_TSELECT() {
+ select file in *; do echo "<$file>" ; break >&3 ; done >&3
+ }
+ inline_TSELECT() {
+ select file in *
+ do
+ \echo "<$file>"
+ \break >&3
+ done >&3
+ }
+ function comsub_TSELECT { x=$(
+ select file in *; do echo "<$file>" ; break >&3 ; done >&3
+ ); }
+ function comsub_TSELECT {
+ x=$(select file in * ; do \echo "<$file>" ; \break >&3 ; done >&3 )
+ }
+ function reread_TSELECT { x=$((
+ select file in *; do echo "<$file>" ; break >&3 ; done >&3
+ )|tr u x); }
+ function reread_TSELECT {
+ x=$( ( select file in * ; do \echo "<$file>" ; \break >&3 ; done >&3 ) | \tr u x )
+ }
+ inline_TFOR_TTIME() {
+ for i in {1,2,3} ; do time >&3 echo $i ; done >&3
+ }
+ inline_TFOR_TTIME() {
+ for i in {1,2,3}
+ do
+ time \echo $i >&3
+ done >&3
+ }
+ function comsub_TFOR_TTIME { x=$(
+ for i in {1,2,3} ; do time >&3 echo $i ; done >&3
+ ); }
+ function comsub_TFOR_TTIME {
+ x=$(for i in {1,2,3} ; do time \echo $i >&3 ; done >&3 )
+ }
+ function reread_TFOR_TTIME { x=$((
+ for i in {1,2,3} ; do time >&3 echo $i ; done >&3
+ )|tr u x); }
+ function reread_TFOR_TTIME {
+ x=$( ( for i in {1,2,3} ; do time \echo $i >&3 ; done >&3 ) | \tr u x )
+ }
+ inline_TCASE() {
+ case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3
+ }
+ inline_TCASE() {
+ case $foo in
+ (1)
+ \echo eins >&3
+ ;&
+ (2)
+ \echo zwei >&3
+ ;|
+ (*)
+ \echo kann net bis drei zählen >&3
+ ;;
+ esac >&3
+ }
+ function comsub_TCASE { x=$(
+ case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3
+ ); }
+ function comsub_TCASE {
+ x=$(case $foo in (1) \echo eins >&3 ;& (2) \echo zwei >&3 ;| (*) \echo kann net bis drei zählen >&3 ;; esac >&3 )
+ }
+ function reread_TCASE { x=$((
+ case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3
+ )|tr u x); }
+ function reread_TCASE {
+ x=$( ( case $foo in (1) \echo eins >&3 ;& (2) \echo zwei >&3 ;| (*) \echo kann net bis drei zählen >&3 ;; esac >&3 ) | \tr u x )
+ }
+ inline_TIF_TBANG_TDBRACKET_TELIF() {
+ if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3
+ }
+ inline_TIF_TBANG_TDBRACKET_TELIF() {
+ if ! [[ 1 = 1 ]] >&3
+ then
+ \echo eins
+ elif [[ 1 = 2 ]] >&3
+ then
+ \echo zwei
+ else
+ \echo drei
+ fi >&3
+ }
+ function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$(
+ if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3
+ ); }
+ function comsub_TIF_TBANG_TDBRACKET_TELIF {
+ x=$(if ! [[ 1 = 1 ]] >&3 ; then \echo eins ; elif [[ 1 = 2 ]] >&3 ; then \echo zwei ; else \echo drei ; fi >&3 )
+ }
+ function reread_TIF_TBANG_TDBRACKET_TELIF { x=$((
+ if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3
+ )|tr u x); }
+ function reread_TIF_TBANG_TDBRACKET_TELIF {
+ x=$( ( if ! [[ 1 = 1 ]] >&3 ; then \echo eins ; elif [[ 1 = 2 ]] >&3 ; then \echo zwei ; else \echo drei ; fi >&3 ) | \tr u x )
+ }
+ inline_TWHILE() {
+ i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
+ }
+ inline_TWHILE() {
+ i=1
+ while {
+ \\builtin let " i < 10 "
+ } >&3
+ do
+ \echo $i
+ \let ++i
+ done >&3
+ }
+ function comsub_TWHILE { x=$(
+ i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
+ ); }
+ function comsub_TWHILE {
+ x=$(i=1 ; while { \\builtin let " i < 10 " ; } >&3 ; do \echo $i ; \let ++i ; done >&3 )
+ }
+ function reread_TWHILE { x=$((
+ i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
+ )|tr u x); }
+ function reread_TWHILE {
+ x=$( ( i=1 ; while { \\builtin let " i < 10 " ; } >&3 ; do \echo $i ; \let ++i ; done >&3 ) | \tr u x )
+ }
+ inline_TUNTIL() {
+ i=10; until (( !--i )) >&3 ; do echo $i; done >&3
+ }
+ inline_TUNTIL() {
+ i=10
+ until {
+ \\builtin let " !--i "
+ } >&3
+ do
+ \echo $i
+ done >&3
+ }
+ function comsub_TUNTIL { x=$(
+ i=10; until (( !--i )) >&3 ; do echo $i; done >&3
+ ); }
+ function comsub_TUNTIL {
+ x=$(i=10 ; until { \\builtin let " !--i " ; } >&3 ; do \echo $i ; done >&3 )
+ }
+ function reread_TUNTIL { x=$((
+ i=10; until (( !--i )) >&3 ; do echo $i; done >&3
+ )|tr u x); }
+ function reread_TUNTIL {
+ x=$( ( i=10 ; until { \\builtin let " !--i " ; } >&3 ; do \echo $i ; done >&3 ) | \tr u x )
+ }
+ inline_TCOPROC() {
+ cat * >&3 |& >&3 ls
+ }
+ inline_TCOPROC() {
+ \cat * >&3 |&
+ \ls >&3
+ }
+ function comsub_TCOPROC { x=$(
+ cat * >&3 |& >&3 ls
+ ); }
+ function comsub_TCOPROC {
+ x=$(\cat * >&3 |& \ls >&3 )
+ }
+ function reread_TCOPROC { x=$((
+ cat * >&3 |& >&3 ls
+ )|tr u x); }
+ function reread_TCOPROC {
+ x=$( ( \cat * >&3 |& \ls >&3 ) | \tr u x )
+ }
+ inline_TFUNCT_TBRACE_TASYNC() {
+ function korn { echo eins; echo >&3 zwei ; }
+ bourne () { logger * >&3 & }
+ }
+ inline_TFUNCT_TBRACE_TASYNC() {
+ function korn {
+ \echo eins
+ \echo zwei >&3
+ }
+ bourne() {
+ \logger * >&3 &
+ }
+ }
+ function comsub_TFUNCT_TBRACE_TASYNC { x=$(
+ function korn { echo eins; echo >&3 zwei ; }
+ bourne () { logger * >&3 & }
+ ); }
+ function comsub_TFUNCT_TBRACE_TASYNC {
+ x=$(function korn { \echo eins ; \echo zwei >&3 ; } ; bourne() { \logger * >&3 & } )
+ }
+ function reread_TFUNCT_TBRACE_TASYNC { x=$((
+ function korn { echo eins; echo >&3 zwei ; }
+ bourne () { logger * >&3 & }
+ )|tr u x); }
+ function reread_TFUNCT_TBRACE_TASYNC {
+ x=$( ( function korn { \echo eins ; \echo zwei >&3 ; } ; bourne() { \logger * >&3 & } ) | \tr u x )
+ }
+ inline_COMSUB_EXPRSUB() {
+ echo $(true >&3) $((1+ 2))
+ }
+ inline_COMSUB_EXPRSUB() {
+ \echo $(\true >&3 ) $((1+ 2))
+ }
+ function comsub_COMSUB_EXPRSUB { x=$(
+ echo $(true >&3) $((1+ 2))
+ ); }
+ function comsub_COMSUB_EXPRSUB {
+ x=$(\echo $(\true >&3 ) $((1+ 2)) )
+ }
+ function reread_COMSUB_EXPRSUB { x=$((
+ echo $(true >&3) $((1+ 2))
+ )|tr u x); }
+ function reread_COMSUB_EXPRSUB {
+ x=$( ( \echo $(\true >&3 ) $((1+ 2)) ) | \tr u x )
+ }
+---
+name: funsub-1
+description:
+ Check that non-subenvironment command substitution works
+stdin:
+ set -e
+ foo=bar
+ echo "ob $foo ."
+ echo "${
+ echo "ib $foo :"
+ foo=baz
+ echo "ia $foo :"
+ false
+ }" .
+ echo "oa $foo ."
+expected-stdout:
+ ob bar .
+ ib bar :
+ ia baz : .
+ oa baz .
+---
+name: funsub-2
+description:
+ You can now reliably use local and return in funsubs
+ (not exit though)
+stdin:
+ x=q; e=1; x=${ echo a; e=2; echo x$e;}; echo 1:y$x,$e,$?.
+ x=q; e=1; x=${ echo a; typeset e=2; echo x$e;}; echo 2:y$x,$e,$?.
+ x=q; e=1; x=${ echo a; typeset e=2; return 3; echo x$e;}; echo 3:y$x,$e,$?.
+expected-stdout:
+ 1:ya x2,2,0.
+ 2:ya x2,1,0.
+ 3:ya,1,3.
+---
+name: valsub-1
+description:
+ Check that "value substitutions" work as advertised
+stdin:
+ x=1
+ y=2
+ z=3
+ REPLY=4
+ echo "before: x<$x> y<$y> z<$z> R<$REPLY>"
+ x=${|
+ local y
+ echo "start: x<$x> y<$y> z<$z> R<$REPLY>"
+ x=5
+ y=6
+ z=7
+ REPLY=8
+ echo "end: x<$x> y<$y> z<$z> R<$REPLY>"
+ }
+ echo "after: x<$x> y<$y> z<$z> R<$REPLY>"
+ # ensure trailing newlines are kept
+ t=${|REPLY=$'foo\n\n';}
+ typeset -p t
+ echo -n this used to segfault
+ echo ${|true;}$(true).
+expected-stdout:
+ before: x<1> y<2> z<3> R<4>
+ start: x<1> y<> z<3> R<>
+ end: x<5> y<6> z<7> R<8>
+ after: x<8> y<2> z<7> R<4>
+ typeset t=$'foo\n\n'
+ this used to segfault.
+---
+name: event-subst-3
+description:
+ Check that '!' substitution in noninteractive mode is ignored
+file-setup: file 755 "falsetto"
+ #! /bin/sh
+ echo molto bene
+ exit 42
+file-setup: file 755 "!false"
+ #! /bin/sh
+ echo si
+stdin:
+ export PATH=.$PATHSEP$PATH
+ falsetto
+ echo yeap
+ !false
+ echo meow
+ ! false
+ echo = $?
+ if
+ ! false; then echo foo; else echo bar; fi
+expected-stdout:
+ molto bene
+ yeap
+ si
+ meow
+ = 0
+ foo
+---
+name: event-subst-0
+description:
+ Check that '!' substitution in interactive mode is ignored
+need-ctty: yes
+arguments: !-i!
+file-setup: file 755 "falsetto"
+ #! /bin/sh
+ echo molto bene
+ exit 42
+file-setup: file 755 "!false"
+ #! /bin/sh
+ echo si
+stdin:
+ export PATH=.$PATHSEP$PATH
+ falsetto
+ echo yeap
+ !false
+ echo meow
+ ! false
+ echo = $?
+ if
+ ! false; then echo foo; else echo bar; fi
+expected-stdout:
+ molto bene
+ yeap
+ si
+ meow
+ = 0
+ foo
+expected-stderr-pattern:
+ /.*/
+---
+name: nounset-1
+description:
+ Check that "set -u" matches (future) SUSv4 requirement
+stdin:
+ (set -u
+ try() {
+ local v
+ eval v=\$$1
+ if [[ -n $v ]]; then
+ echo $1=nz
+ else
+ echo $1=zf
+ fi
+ }
+ x=y
+ (echo $x)
+ echo =1
+ (echo $y)
+ echo =2
+ (try x)
+ echo =3
+ (try y)
+ echo =4
+ (try 0)
+ echo =5
+ (try 2)
+ echo =6
+ (try)
+ echo =7
+ (echo at=$@)
+ echo =8
+ (echo asterisk=$*)
+ echo =9
+ (echo $?)
+ echo =10
+ (echo $!)
+ echo =11
+ (echo $-)
+ echo =12
+ #(echo $_)
+ #echo =13
+ (echo $#)
+ echo =14
+ (mypid=$$; try mypid)
+ echo =15
+ ) 2>&1 | sed -e 's/^[A-Za-z]://' -e 's/^[^]]*]//' -e 's/^[^:]*: *//'
+ exit ${PIPESTATUS[0]}
+expected-stdout:
+ y
+ =1
+ y: parameter not set
+ =2
+ x=nz
+ =3
+ y: parameter not set
+ =4
+ 0=nz
+ =5
+ 2: parameter not set
+ =6
+ 1: parameter not set
+ =7
+ at=
+ =8
+ asterisk=
+ =9
+ 0
+ =10
+ !: parameter not set
+ =11
+ ush
+ =12
+ 0
+ =14
+ mypid=nz
+ =15
+---
+name: nameref-1
+description:
+ Testsuite for nameref (bound variables)
+stdin:
+ bar=global
+ typeset -n ir2=bar
+ typeset -n ind=ir2
+ echo !ind: ${!ind}
+ echo ind: $ind
+ echo !ir2: ${!ir2}
+ echo ir2: $ir2
+ typeset +n ind
+ echo !ind: ${!ind}
+ echo ind: $ind
+ typeset -n ir2=ind
+ echo !ir2: ${!ir2}
+ echo ir2: $ir2
+ set|grep ^ir2|sed 's/^/s1: /'
+ typeset|grep ' ir2'|sed -e 's/^/s2: /' -e 's/nameref/typeset -n/'
+ set -A blub -- e1 e2 e3
+ typeset -n ind=blub
+ typeset -n ir2=blub[2]
+ echo !ind[1]: ${!ind[1]}
+ echo !ir2: $!ir2
+ echo ind[1]: ${ind[1]}
+ echo ir2: $ir2
+expected-stdout:
+ !ind: bar
+ ind: global
+ !ir2: bar
+ ir2: global
+ !ind: ind
+ ind: ir2
+ !ir2: ind
+ ir2: ir2
+ s1: ir2=ind
+ s2: typeset -n ir2
+ !ind[1]: blub[1]
+ !ir2: ir2
+ ind[1]: e2
+ ir2: e3
+---
+name: nameref-2da
+description:
+ Testsuite for nameref (bound variables)
+ Functions, argument given directly, after local
+stdin:
+ function foo {
+ typeset bar=lokal baz=auch
+ typeset -n v=bar
+ echo entering
+ echo !v: ${!v}
+ echo !bar: ${!bar}
+ echo !baz: ${!baz}
+ echo bar: $bar
+ echo v: $v
+ v=123
+ echo bar: $bar
+ echo v: $v
+ echo exiting
+ }
+ bar=global
+ echo bar: $bar
+ foo bar
+ echo bar: $bar
+expected-stdout:
+ bar: global
+ entering
+ !v: bar
+ !bar: bar
+ !baz: baz
+ bar: lokal
+ v: lokal
+ bar: 123
+ v: 123
+ exiting
+ bar: global
+---
+name: nameref-3
+description:
+ Advanced testsuite for bound variables (ksh93 fails this)
+stdin:
+ typeset -n foo=bar[i]
+ set -A bar -- b c a
+ for i in 0 1 2 3; do
+ print $i $foo .
+ done
+expected-stdout:
+ 0 b .
+ 1 c .
+ 2 a .
+ 3 .
+---
+name: nameref-4
+description:
+ Ensure we don't run in an infinite loop
+time-limit: 3
+stdin:
+ baz() {
+ typeset -n foo=fnord fnord=foo
+ foo[0]=bar
+ }
+ set -A foo bad
+ echo sind $foo .
+ baz
+ echo blah $foo .
+expected-stdout:
+ sind bad .
+ blah bad .
+expected-stderr-pattern:
+ /fnord: expression recurses on parameter/
+---
+name: better-parens-1a
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ if ( (echo fubar)|tr u x); then
+ echo ja
+ else
+ echo nein
+ fi
+expected-stdout:
+ fxbar
+ ja
+---
+name: better-parens-1b
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ 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
+---
+name: better-parens-2a
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ if ((echo fubar)|tr u x); then
+ echo ja
+ else
+ echo nein
+ fi
+expected-stdout:
+ fxbar
+ ja
+---
+name: better-parens-2b
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ 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
+---
+name: better-parens-3a
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ if ( (echo fubar)|(tr u x)); then
+ echo ja
+ else
+ echo nein
+ fi
+expected-stdout:
+ fxbar
+ ja
+---
+name: better-parens-3b
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ 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
+---
+name: better-parens-4a
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ if ((echo fubar)|(tr u x)); then
+ echo ja
+ else
+ echo nein
+ fi
+expected-stdout:
+ fxbar
+ ja
+---
+name: better-parens-4b
+description:
+ Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+ 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
+---
+name: better-parens-5
+description:
+ Another corner case
+stdin:
+ ( (echo 'fo o$bar' "baz\$bla\"" m\$eh) | tr a A)
+ ((echo 'fo o$bar' "baz\$bla\"" m\$eh) | tr a A)
+expected-stdout:
+ fo o$bAr bAz$blA" m$eh
+ fo o$bAr bAz$blA" m$eh
+---
+name: echo-test-1
+description:
+ Test what the echo builtin does (mksh)
+category: !shell:ebcdic-yes
+stdin:
+ echo -n 'foo\x40bar'
+ echo -e '\tbaz'
+expected-stdout:
+ foo@bar baz
+---
+name: echo-test-1-ebcdic
+description:
+ Test what the echo builtin does (mksh)
+category: !shell:ebcdic-no
+stdin:
+ echo -n 'foo\x7Cbar'
+ echo -e '\tbaz'
+expected-stdout:
+ foo@bar baz
+---
+name: echo-test-2
+description:
+ Test what the echo builtin does (POSIX)
+ Note: this follows Debian Policy 10.4 which mandates
+ that -n shall be treated as an option, not XSI which
+ mandates it shall be treated as string but escapes
+ shall be expanded.
+stdin:
+ test -n "$POSH_VERSION" || set -o posix
+ echo -n 'foo\x40bar'
+ echo -e '\tbaz'
+expected-stdout:
+ foo\x40bar-e \tbaz
+---
+name: echo-test-3-mnbsd
+description:
+ Test what the echo builtin does, and test a compatibility flag.
+category: mnbsdash
+stdin:
+ "$__progname" -c 'echo -n 1=\\x40$1; echo -e \\x2E' -- foo bar
+ "$__progname" -o posix -c 'echo -n 2=\\x40$1; echo -e \\x2E' -- foo bar
+ "$__progname" -o sh -c 'echo -n 3=\\x40$1; echo -e \\x2E' -- foo bar
+expected-stdout:
+ 1=@foo.
+ 2=\x40foo-e \x2E
+ 3=\x40bar.
+---
+name: echo-test-3-normal
+description:
+ Test what the echo builtin does, and test a compatibility flag.
+category: !mnbsdash,!shell:ebcdic-yes
+stdin:
+ "$__progname" -c 'echo -n 1=\\x40$1; echo -e \\x2E' -- foo bar
+ "$__progname" -o posix -c 'echo -n 2=\\x40$1; echo -e \\x2E' -- foo bar
+ "$__progname" -o sh -c 'echo -n 3=\\x40$1; echo -e \\x2E' -- foo bar
+expected-stdout:
+ 1=@foo.
+ 2=\x40foo-e \x2E
+ 3=\x40foo-e \x2E
+---
+name: echo-test-3-ebcdic
+description:
+ Test what the echo builtin does, and test a compatibility flag.
+category: !mnbsdash,!shell:ebcdic-no
+stdin:
+ "$__progname" -c 'echo -n 1=\\x7C$1; echo -e \\x4B' -- foo bar
+ "$__progname" -o posix -c 'echo -n 2=\\x7C$1; echo -e \\x4B' -- foo bar
+ "$__progname" -o sh -c 'echo -n 3=\\x7C$1; echo -e \\x4B' -- foo bar
+expected-stdout:
+ 1=@foo.
+ 2=\x7Cfoo-e \x4B
+ 3=\x7Cfoo-e \x4B
+---
+name: utilities-getopts-1
+description:
+ getopts sets OPTIND correctly for unparsed option
+stdin:
+ set -- -a -a -x
+ while getopts :a optc; do
+ echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc."
+ done
+ echo done
+expected-stdout:
+ OPTARG=, OPTIND=2, optc=a.
+ OPTARG=, OPTIND=3, optc=a.
+ OPTARG=x, OPTIND=4, optc=?.
+ done
+---
+name: utilities-getopts-2
+description:
+ Check OPTARG
+stdin:
+ set -- -a Mary -x
+ while getopts a: optc; do
+ echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc."
+ done
+ echo done
+expected-stdout:
+ OPTARG=Mary, OPTIND=3, optc=a.
+ OPTARG=, OPTIND=4, optc=?.
+ done
+expected-stderr-pattern: /.*-x.*option/
+---
+name: utilities-getopts-3
+description:
+ Check unsetting OPTARG
+stdin:
+ set -- -x arg -y
+ getopts x:y opt && echo "${OPTARG-unset}"
+ getopts x:y opt && echo "${OPTARG-unset}"
+expected-stdout:
+ arg
+ unset
+---
+name: wcswidth-1
+description:
+ Check the new wcswidth feature
+stdin:
+ s=何
+ set +U
+ print octets: ${#s} .
+ print 8-bit width: ${%s} .
+ set -U
+ print characters: ${#s} .
+ print columns: ${%s} .
+ s=ďż˝
+ set +U
+ print octets: ${#s} .
+ print 8-bit width: ${%s} .
+ set -U
+ print characters: ${#s} .
+ print columns: ${%s} .
+expected-stdout:
+ octets: 3 .
+ 8-bit width: -1 .
+ characters: 1 .
+ columns: 2 .
+ octets: 3 .
+ 8-bit width: 3 .
+ characters: 1 .
+ columns: 1 .
+---
+name: wcswidth-2
+description:
+ Check some corner cases
+stdin:
+ print % $% .
+ set -U
+ x='a b'
+ print c ${%x} .
+ set +U
+ x='a b'
+ print d ${%x} .
+expected-stdout:
+ % $% .
+ c -1 .
+ d -1 .
+---
+name: wcswidth-3
+description:
+ Check some corner cases
+stdin:
+ print ${%} .
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4a
+description:
+ Check some corner cases
+stdin:
+ print ${%*} .
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4b
+description:
+ Check some corner cases
+stdin:
+ print ${%@} .
+expected-stderr-pattern:
+ /bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4c
+description:
+ Check some corner cases
+stdin:
+ :
+ print ${%?} .
+expected-stdout:
+ 1 .
+---
+name: realpath-1
+description:
+ Check proper return values for realpath
+category: os:mirbsd
+stdin:
+ wd=$(realpath .)
+ mkdir dir
+ :>file
+ :>dir/file
+ ln -s dir lndir
+ ln -s file lnfile
+ ln -s nix lnnix
+ ln -s . lnself
+ i=0
+ chk() {
+ typeset x y
+ x=$(realpath "$wd/$1" 2>&1); y=$?
+ print $((++i)) "?$1" =${x##*$wd/} !$y
+ }
+ chk dir
+ chk dir/
+ chk dir/file
+ chk dir/nix
+ chk file
+ chk file/
+ chk file/file
+ chk file/nix
+ chk nix
+ chk nix/
+ chk nix/file
+ chk nix/nix
+ chk lndir
+ chk lndir/
+ chk lndir/file
+ chk lndir/nix
+ chk lnfile
+ chk lnfile/
+ chk lnfile/file
+ chk lnfile/nix
+ chk lnnix
+ chk lnnix/
+ chk lnnix/file
+ chk lnnix/nix
+ chk 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
+ rm lnself
+expected-stdout:
+ 1 ?dir =dir !0
+ 2 ?dir/ =dir !0
+ 3 ?dir/file =dir/file !0
+ 4 ?dir/nix =dir/nix !0
+ 5 ?file =file !0
+ 6 ?file/ =file/: Not a directory !20
+ 7 ?file/file =file/file: Not a directory !20
+ 8 ?file/nix =file/nix: Not a directory !20
+ 9 ?nix =nix !0
+ 10 ?nix/ =nix !0
+ 11 ?nix/file =nix/file: No such file or directory !2
+ 12 ?nix/nix =nix/nix: No such file or directory !2
+ 13 ?lndir =dir !0
+ 14 ?lndir/ =dir !0
+ 15 ?lndir/file =dir/file !0
+ 16 ?lndir/nix =dir/nix !0
+ 17 ?lnfile =file !0
+ 18 ?lnfile/ =lnfile/: Not a directory !20
+ 19 ?lnfile/file =lnfile/file: Not a directory !20
+ 20 ?lnfile/nix =lnfile/nix: Not a directory !20
+ 21 ?lnnix =nix !0
+ 22 ?lnnix/ =nix !0
+ 23 ?lnnix/file =lnnix/file: No such file or directory !2
+ 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=FGJRSX
+ "$__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
+---
+name: case-braces
+description:
+ Check that case end tokens are not mixed up (Debian #220272)
+stdin:
+ i=0
+ for value in 'x' '}' 'esac'; do
+ print -n "$((++i))($value)bourne "
+ case $value in
+ }) echo brace ;;
+ *) echo no ;;
+ esac
+ print -n "$((++i))($value)korn "
+ case $value {
+ esac) echo esac ;;
+ *) echo no ;;
+ }
+ done
+expected-stdout:
+ 1(x)bourne no
+ 2(x)korn no
+ 3(})bourne brace
+ 4(})korn no
+ 5(esac)bourne no
+ 6(esac)korn esac
+---
+name: command-shift
+description:
+ Check that 'command shift' works
+stdin:
+ function snc {
+ echo "before 0='$0' 1='$1' 2='$2'"
+ shift
+ echo "after 0='$0' 1='$1' 2='$2'"
+ }
+ function swc {
+ echo "before 0='$0' 1='$1' 2='$2'"
+ command shift
+ echo "after 0='$0' 1='$1' 2='$2'"
+ }
+ echo = without command
+ snc 一 二
+ echo = with command
+ swc 一 二
+ echo = done
+expected-stdout:
+ = without command
+ before 0='snc' 1='一' 2='二'
+ after 0='snc' 1='二' 2=''
+ = with command
+ before 0='swc' 1='一' 2='二'
+ after 0='swc' 1='二' 2=''
+ = done
+---
+name: command-set
+description:
+ Same but with set
+stdin:
+ showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+ showargs 1 "$@"
+ set -- foo bar baz
+ showargs 2 "$@"
+ command set -- miau 'meow nyao'
+ showargs 3 "$@"
+expected-stdout:
+ <1> .
+ <2> <foo> <bar> <baz> .
+ <3> <miau> <meow nyao> .
+---
+name: command-readonly
+description:
+ These should not exit on error when prefixed
+stdin:
+ exec 2>/dev/null
+ "$__progname" -c 'readonly v; export v=foo || echo ok'
+ echo ef=$?
+ "$__progname" -c 'readonly v; command export v=foo || echo ok'
+ echo en=$?
+ "$__progname" -c 'readonly v; readonly v=foo || echo ok'
+ echo rf=$?
+ "$__progname" -c 'readonly v; command readonly v=foo || echo ok'
+ echo rn=$?
+expected-stdout:
+ ef=2
+ ok
+ en=0
+ rf=2
+ ok
+ rn=0
+---
+name: command-dot-regression
+description:
+ Check a regression in fixing the above does not appear
+stdin:
+ cat >test.mksh <<\EOF
+ set -- one two
+ shift
+ for s_arg in "$#" "$@"; do echo -n "<$s_arg> "; done; echo .
+ EOF
+ "$__progname" -c '. ./test.mksh' dummy oh dear this is not good
+ echo =
+ "$__progname" -c 'command . ./test.mksh' dummy oh dear this is not good
+expected-stdout:
+ <1> <two> .
+ =
+ <1> <two> .
+---
+name: command-pvV-posix-priorities
+description:
+ For POSIX compatibility, command -v should find aliases and reserved
+ words, and command -p[vV] should find aliases, reserved words, and
+ builtins over external commands.
+stdin:
+ PATH=/bin:/usr/bin
+ alias foo="bar baz"
+ alias '[ab]=:'
+ bar() { :; }
+ for word in 'if' 'foo' 'bar' 'set' 'true' '[ab]'; do
+ command -v "$word"
+ command -pv "$word"
+ command -V "$word"
+ command -pV "$word"
+ done
+ # extra checks
+ alias '[ab]'
+ whence '[ab]'
+expected-stdout:
+ if
+ if
+ if is a reserved word
+ if is a reserved word
+ alias foo='bar baz'
+ alias foo='bar baz'
+ foo is an alias for 'bar baz'
+ foo is an alias for 'bar baz'
+ bar
+ bar
+ bar is a function
+ bar is a function
+ set
+ set
+ set is a special shell builtin
+ set is a special shell builtin
+ true
+ true
+ true is a shell builtin
+ true is a shell builtin
+ alias '[ab]'=:
+ alias '[ab]'=:
+ '[ab]' is an alias for :
+ '[ab]' is an alias for :
+ '[ab]'=:
+ :
+---
+name: whence-preserve-tradition
+description:
+ This regression test is to ensure that the POSIX compatibility
+ changes for 'command' (see previous test) do not affect traditional
+ 'whence' behaviour.
+category: os:mirbsd
+stdin:
+ PATH=/bin:/usr/bin
+ alias foo="bar baz"
+ bar() { :; }
+ for word in 'if' 'foo' 'bar' 'set' 'true'; do
+ whence "$word"
+ whence -p "$word"
+ whence -v "$word"
+ whence -pv "$word"
+ done
+expected-stdout:
+ if
+ if is a reserved word
+ if not found
+ 'bar baz'
+ foo is an alias for 'bar baz'
+ foo not found
+ bar
+ bar is a function
+ bar not found
+ set
+ set is a special shell builtin
+ set not found
+ true
+ /bin/true
+ true is a shell builtin
+ true is a tracked alias for /bin/true
+---
+name: duffs-device
+description:
+ Check that the compiler did not optimise-break them
+ (lex.c has got a similar one in SHEREDELIM)
+category: !shell:faux-ebcdic,!shell:ebcdic-yes
+stdin:
+ set +U
+ s=
+ typeset -i1 i=0
+ while (( ++i < 256 )); do
+ s+=${i#1#}
+ done
+ s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.'
+ typeset -p s
+expected-stdout:
+ typeset s=$'\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\E\034\035\036\037 !"#$%&\047()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\u00A0\u20AC\uFFFD\357\277\276\357\277\277\360\220\200\200.'
+---
+name: duffs-device-ebcdic
+description:
+ Check that the compiler did not optimise-break them
+category: !shell:ebcdic-no
+stdin:
+ set +U
+ s=
+ typeset -i1 i=0
+ while (( ++i < 256 )); do
+ s+=${i#1#}
+ done
+ #s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.' #XXX
+ typeset -p s
+expected-stdout:
+ typeset s=$'\001\002\003\004\t\006\007\010\011\012\v\f\r\016\017\020\021\022\023\024\n\b\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\E\050\051\052\053\054\055\056\a\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077  âäŕáăĺçń˘.<(+|&éęëčíîďěß!$*);^-/ÂÄŔÁĂĹÇѦ,%_>?řÉĘËČÍÎĎĚ`:#@\175="Řabcdefghi«»đýţ±°jklmnopqrŞşć¸Ć¤µ~stuvwxyzˇżĐ[Ţ®¬ŁĄ·©§¶Ľ˝ľÝ¨Ż]´×{ABCDEFGHI­ôöňóő}JKLMNOPQRąűüůú˙\\÷STUVWXYZ˛ÔÖŇÓŐ0123456789łŰÜŮÚ\377'
+---
+name: duffs-device-faux-EBCDIC
+description:
+ Check that the compiler did not optimise-break them
+category: shell:faux-ebcdic
+stdin:
+ set +U
+ s=
+ typeset -i1 i=0
+ while (( ++i < 256 )); do
+ s+=${i#1#}
+ done
+ s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.'
+ typeset -p s
+expected-stdout:
+ typeset s=$'\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\E\034\035\036\037 !"#$%&\047()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237 ˇ˘Ł¤Ą¦§¨©Ş«¬­®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙\u00A0\u20AC\uFFFDďżľďżżđ\220\200\200.'
+---
+name: stateptr-underflow
+description:
+ This check overflows an Xrestpos stored in a short in R40
+category: fastbox
+stdin:
+ 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
+ }
+
+ i=-1
+ s=
+ while (( ++i < 12120 )); do
+ s+=a
+ done
+ Lb64decode $s >/dev/null
+---
+name: xtrace-1
+description:
+ Check that "set -x" doesn't redirect too quickly
+stdin:
+ print '#!'"$__progname" >bash
+ cat >>bash <<'EOF'
+ echo 'GNU bash, version 2.05b.0(1)-release (i386-ecce-mirbsd10)
+ Copyright (C) 2002 Free Software Foundation, Inc.'
+ EOF
+ chmod +x bash
+ "$__progname" -xc 'foo=$(./bash --version 2>&1 | sed q); echo "=$foo="'
+expected-stdout:
+ =GNU bash, version 2.05b.0(1)-release (i386-ecce-mirbsd10)=
+expected-stderr-pattern:
+ /.*/
+---
+name: xtrace-2
+description:
+ Check that "set -x" is off during PS4 expansion
+stdin:
+ f() {
+ print -n "(f1:$-)"
+ set -x
+ print -n "(f2:$-)"
+ }
+ PS4='[(p:$-)$(f)] '
+ print "(o0:$-)"
+ set -x -o inherit-xtrace
+ print "(o1:$-)"
+ set +x
+ print "(o2:$-)"
+expected-stdout:
+ (o0:sh)
+ (o1:shx)
+ (o2:sh)
+expected-stderr:
+ [(p:sh)(f1:sh)(f2:sh)] print '(o1:shx)'
+ [(p:sh)(f1:sh)(f2:sh)] set +x
+---
+name: fksh-flags
+description:
+ Check that FKSH functions have their own shell flags
+category: shell:legacy-no
+stdin:
+ [[ $KSH_VERSION = Version* ]] && set +B
+ function foo {
+ set +f
+ set -e
+ echo 2 "${-/s}" .
+ }
+ set -fh
+ echo 1 "${-/s}" .
+ foo
+ echo 3 "${-/s}" .
+expected-stdout:
+ 1 fh .
+ 2 eh .
+ 3 fh .
+---
+name: fksh-flags-legacy
+description:
+ Check that even FKSH functions share the shell flags
+category: shell:legacy-yes
+stdin:
+ [[ $KSH_VERSION = Version* ]] && set +B
+ foo() {
+ set +f
+ set -e
+ echo 2 "${-/s}" .
+ }
+ set -fh
+ echo 1 "${-/s}" .
+ foo
+ echo 3 "${-/s}" .
+expected-stdout:
+ 1 fh .
+ 2 eh .
+ 3 eh .
+---
+name: fsh-flags
+description:
+ Check that !FKSH functions share the shell flags
+stdin:
+ [[ $KSH_VERSION = Version* ]] && set +B
+ foo() {
+ set +f
+ set -e
+ echo 2 "${-/s}" .
+ }
+ set -fh
+ echo 1 "${-/s}" .
+ foo
+ echo 3 "${-/s}" .
+expected-stdout:
+ 1 fh .
+ 2 eh .
+ 3 eh .
+---
diff --git a/shells/mksh/files/dot.mkshrc b/shells/mksh/files/dot.mkshrc
new file mode 100644
index 00000000000..5d112cc0319
--- /dev/null
+++ b/shells/mksh/files/dot.mkshrc
@@ -0,0 +1,675 @@
+# $Id: dot.mkshrc,v 1.1 2020/07/06 10:11:34 jperkin Exp $
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.128 2020/04/13 18:39:03 tg Exp $
+#-
+# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
+# 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+# 2020
+# mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells
+
+# catch non-mksh, non-lksh, trying to run this file
+case ${KSH_VERSION:-} in
+*LEGACY\ KSH*|*MIRBSD\ KSH*) ;;
+*) \return 0 ;;
+esac
+
+# give MidnightBSD's laffer1 a bit of csh feeling
+function setenv {
+ if (( $# )); then
+ \\builtin eval '\\builtin export "$1"="${2:-}"'
+ else
+ \\builtin typeset -x
+ fi
+}
+
+# pager (not control character safe)
+smores() (
+ \\builtin set +m
+ \\builtin cat "$@" |&
+ \\builtin trap "rv=\$?; \\\\builtin kill $! >/dev/null 2>&1; \\\\builtin exit \$rv" EXIT
+ while IFS= \\builtin read -pr line; do
+ llen=${%line}
+ (( llen == -1 )) && llen=${#line}
+ (( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
+ if (( (curlin += llen) >= LINES )); then
+ \\builtin print -nr -- $'\e[7m--more--\e[0m'
+ \\builtin read -u1 || \\builtin exit $?
+ [[ $REPLY = [Qq]* ]] && \\builtin exit 0
+ curlin=$llen
+ fi
+ \\builtin print -r -- "$line"
+ done
+)
+
+# customise your favourite editor here; the first one found is used
+for EDITOR in "${EDITOR:-}" jupp jstar mcedit ed vi; do
+ EDITOR=$(\\builtin whence -p "$EDITOR") || EDITOR=
+ [[ -n $EDITOR && -x $EDITOR ]] && break
+ EDITOR=
+done
+
+\\builtin alias ls=ls l='ls -F' la='l -a' ll='l -l' lo='l -alo'
+\: "${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit -c 0; id -un \
+ 2>/dev/null)}${HOSTNAME:=$(\\builtin ulimit -c 0; hostname 2>/dev/null)}"
+[[ $HOSTNAME = ?(?(ip6-)localhost?(6)) ]] && HOSTNAME=nil; \\builtin unalias ls
+\\builtin export EDITOR HOSTNAME TERM USER="${USER:-?}"
+
+# minimal support for lksh users
+if [[ $KSH_VERSION = *LEGACY\ KSH* ]]; then
+ PS1='$USER@${HOSTNAME%%.*}:$PWD>'
+ \\builtin return 0
+fi
+
+# mksh-specific from here
+\: "${MKSH:=$(\\builtin whence -p mksh)}${MKSH:=/bin/mksh}"
+\\builtin export MKSH
+
+# prompts
+PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='$'; PS1=$'\001\r''${|
+ \\builtin typeset e=$?
+
+ (( e )) && REPLY+="$e|"
+ REPLY+=${USER}@${HOSTNAME%%.*}:
+
+ \\builtin typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
+ d=${d%/}; \\builtin typeset m=${%d} n p=...; (( m > 0 )) || m=${#d}
+ (( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || p=
+ REPLY+=$p$d
+
+ \\builtin return $e
+} '"$PS1 "
+
+# utilities
+\\builtin alias doch='sudo mksh -c "$(\\builtin fc -ln -1)"'
+\\builtin command -v rot13 >/dev/null || \\builtin alias rot13='tr \
+ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
+ nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
+if \\builtin command -v hd >/dev/null; then
+ \:
+elif \\builtin command -v hexdump >/dev/null; then
+ function hd {
+ hexdump -e '"%08.8_ax " 8/1 "%02X " " - " 8/1 "%02X "' \
+ -e '" |" "%_p"' -e '"|\n"' "$@"
+ }
+else
+ function hd {
+ \\builtin cat "$@" | hd_mksh
+ }
+fi
+
+# NUL-safe and EBCDIC-safe hexdump (from stdin)
+function hd_mksh {
+ \\builtin typeset -Uui16 -Z11 pos=0
+ \\builtin typeset -Uui16 -Z5 hv=2147483647
+ \\builtin typeset dasc dn line i
+ \\builtin set +U
+
+ while \\builtin read -arn 512 line; do
+ \\builtin typeset -i1 'line[*]'
+ i=0
+ while (( i < ${#line[*]} )); do
+ dn=
+ (( (hv = line[i++]) != 0 )) && dn=${line[i-1]#1#}
+ if (( (pos & 15) == 0 )); then
+ (( pos )) && \
+ \\builtin print -r -- "$dasc|"
+ \\builtin print -nr "${pos#16#} "
+ dasc=' |'
+ fi
+ \\builtin print -nr "${hv#16#} "
+ if [[ $dn = [[:print:]] ]]; then
+ dasc+=$dn
+ else
+ dasc+=.
+ fi
+ (( (pos++ & 15) == 7 )) && \
+ \\builtin print -nr -- '- '
+ done
+ done
+ while (( pos & 15 )); do
+ \\builtin print -nr ' '
+ (( (pos++ & 15) == 7 )) && \
+ \\builtin print -nr -- '- '
+ done
+ (( hv == 2147483647 )) || \\builtin print -r -- "$dasc|"
+}
+
+function which {
+ \\builtin typeset p x c
+ \\builtin typeset -i a=0 rv=2 e
+ \\builtin set +e
+ \\builtin set -o noglob
+
+ while \\builtin getopts "a" x; do
+ case $x {
+ (a) a=1 ;;
+ (+a) a=0 ;;
+ (*) \\builtin print -ru2 'Usage: which [-a] name [...]'
+ \\builtin return 255 ;;
+ }
+ done
+ \\builtin shift $((OPTIND - 1))
+
+ # vvvvvvvvvvvvvvvvvvvv should be def_path
+ p=${PATH-/usr/bin$PATHSEP/bin}
+ # ^ no colon!
+
+ # trailing PATHSEP vs field splitting
+ [[ $p = *"$PATHSEP" ]] && p+=.
+
+ IFS=$PATHSEP
+ \\builtin set -A p -- ${p:-.}
+ IFS=$' \t\n'
+
+ for x in "$@"; do
+ if (( !a )) || [[ $x = */* ]]; then
+ \\builtin whence -p -- "$x"
+ e=$?
+ else
+ e=1
+ for c in "${p[@]}"; do
+ PATH=${c:-.} \\builtin whence -p -- "$x" && e=0
+ done
+ fi
+ (( rv = (e == 0) ? (rv & ~2) : (rv == 2 ? 2 : 1) ))
+ done
+ \\builtin return $rv
+}
+
+# Berkeley C shell compatible dirs, popd, and pushd functions
+# Z shell compatible chpwd() hook, used to update DIRSTACK[0]
+DIRSTACKBASE=$(\\builtin realpath ~/. 2>/dev/null || \
+ \\builtin print -nr -- "${HOME:-/}")
+\\builtin set -A DIRSTACK
+function chpwd {
+ DIRSTACK[0]=$(\\builtin realpath . 2>/dev/null || \
+ \\builtin print -nr -- "$PWD")
+ [[ $DIRSTACKBASE = ?(*/) ]] || \
+ DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/\~}
+ \:
+}
+\chpwd .
+cd() {
+ \\builtin cd "$@" || \\builtin return $?
+ \chpwd "$@"
+}
+function cd_csh {
+ \\builtin typeset d t=${1/#\~/$DIRSTACKBASE}
+
+ if ! d=$(\\builtin cd "$t" 2>&1); then
+ \\builtin print -ru2 "${1}: ${d##*cd: $t: }."
+ \\builtin return 1
+ fi
+ \cd "$t"
+}
+function dirs {
+ \\builtin typeset d dwidth
+ \\builtin typeset -i fl=0 fv=0 fn=0 cpos=0
+
+ while \\builtin getopts ":lvn" d; do
+ case $d {
+ (l) fl=1 ;;
+ (v) fv=1 ;;
+ (n) fn=1 ;;
+ (*) \\builtin print -ru2 'Usage: dirs [-lvn].'
+ \\builtin return 1 ;;
+ }
+ done
+ \\builtin shift $((OPTIND - 1))
+ if (( $# > 0 )); then
+ \\builtin print -ru2 'Usage: dirs [-lvn].'
+ \\builtin return 1
+ fi
+ if (( fv )); then
+ fv=0
+ while (( fv < ${#DIRSTACK[*]} )); do
+ d=${DIRSTACK[fv]}
+ (( fl )) && d=${d/#\~/$DIRSTACKBASE}
+ \\builtin print -r -- "$fv $d"
+ (( ++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
+ \\builtin print
+ (( cpos = dwidth + 1 ))
+ fi
+ \\builtin print -nr -- "$d "
+ (( ++fv ))
+ done
+ \\builtin print
+ fi
+ \\builtin return 0
+}
+function popd {
+ \\builtin typeset d fa
+ \\builtin typeset -i n=1
+
+ while \\builtin getopts ":0123456789lvn" d; do
+ case $d {
+ (l|v|n) fa+=" -$d" ;;
+ (+*) n=2
+ \\builtin break ;;
+ (*) \\builtin print -ru2 'Usage: popd [-lvn] [+<n>].'
+ \\builtin return 1 ;;
+ }
+ done
+ \\builtin shift $((OPTIND - n))
+ n=0
+ if (( $# > 1 )); then
+ \\builtin print -ru2 popd: Too many arguments.
+ \\builtin return 1
+ elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+ if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+ \\builtin print -ru2 popd: Directory stack not that deep.
+ \\builtin return 1
+ fi
+ elif [[ -n $1 ]]; then
+ \\builtin print -ru2 popd: Bad directory.
+ \\builtin return 1
+ fi
+ if (( ${#DIRSTACK[*]} < 2 )); then
+ \\builtin print -ru2 popd: Directory stack empty.
+ \\builtin return 1
+ fi
+ \\builtin unset DIRSTACK[n]
+ \\builtin set -A DIRSTACK -- "${DIRSTACK[@]}"
+ \cd_csh "${DIRSTACK[0]}" || \\builtin return 1
+ \dirs $fa
+}
+function pushd {
+ \\builtin typeset d fa
+ \\builtin typeset -i n=1
+
+ while \\builtin getopts ":0123456789lvn" d; do
+ case $d {
+ (l|v|n) fa+=" -$d" ;;
+ (+*) n=2
+ \\builtin break ;;
+ (*) \\builtin print -ru2 'Usage: pushd [-lvn] [<dir>|+<n>].'
+ \\builtin return 1 ;;
+ }
+ done
+ \\builtin shift $((OPTIND - n))
+ if (( $# == 0 )); then
+ if (( ${#DIRSTACK[*]} < 2 )); then
+ \\builtin print -ru2 pushd: No other directory.
+ \\builtin return 1
+ fi
+ d=${DIRSTACK[1]}
+ DIRSTACK[1]=${DIRSTACK[0]}
+ \cd_csh "$d" || \\builtin return 1
+ elif (( $# > 1 )); then
+ \\builtin print -ru2 pushd: Too many arguments.
+ \\builtin return 1
+ elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+ if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+ \\builtin print -ru2 pushd: Directory stack not that deep.
+ \\builtin return 1
+ fi
+ while (( n-- )); do
+ d=${DIRSTACK[0]}
+ \\builtin unset DIRSTACK[0]
+ \\builtin set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
+ done
+ \cd_csh "${DIRSTACK[0]}" || \\builtin return 1
+ else
+ \\builtin set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
+ \cd_csh "$1" || \\builtin return 1
+ fi
+ \dirs $fa
+}
+
+# base64 encoder and decoder, RFC compliant, NUL safe, not EBCDIC safe
+function Lb64decode {
+ \\builtin set +U
+ \\builtin typeset c s="$*" t
+ [[ -n $s ]] || { s=$(\\builtin cat; \\builtin print x); s=${s%x}; }
+ \\builtin typeset -i i=0 j=0 n=${#s} p=0 v x
+ \\builtin typeset -i16 o
+
+ while (( i < n )); do
+ c=${s:(i++):1}
+ case $c {
+ (=) \\builtin break ;;
+ ([A-Z]) (( v = 1#$c - 65 )) ;;
+ ([a-z]) (( v = 1#$c - 71 )) ;;
+ ([0-9]) (( v = 1#$c + 4 )) ;;
+ (+) v=62 ;;
+ (/) v=63 ;;
+ (*) \\builtin continue ;;
+ }
+ (( x = (x << 6) | v ))
+ case $((p++)) {
+ (0) \\builtin continue ;;
+ (1) (( o = (x >> 4) & 255 )) ;;
+ (2) (( o = (x >> 2) & 255 )) ;;
+ (3) (( o = x & 255 ))
+ p=0
+ ;;
+ }
+ t+=\\x${o#16#}
+ (( ++j & 4095 )) && \\builtin continue
+ \\builtin print -n $t
+ t=
+ done
+ \\builtin print -n $t
+}
+function Lb64encode {
+ \\builtin set +U
+ \\builtin typeset c s t table
+ \\builtin set -A table -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
+ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
+ if (( $# )); then
+ \\builtin read -raN-1 s <<<"$*"
+ \\builtin unset s[${#s[*]}-1]
+ else
+ \\builtin read -raN-1 s
+ fi
+ \\builtin typeset -i i=0 n=${#s[*]} v
+
+ while (( i < n )); do
+ (( v = s[i++] << 16 ))
+ (( v |= s[i++] << 8 ))
+ (( v |= s[i++] ))
+ t+=${table[v >> 18]}${table[v >> 12 & 63]}
+ c=${table[v >> 6 & 63]}
+ if (( i <= n )); then
+ t+=$c${table[v & 63]}
+ elif (( i == n + 1 )); then
+ t+=$c=
+ else
+ t+===
+ fi
+ if (( ${#t} == 76 || i >= n )); then
+ \\builtin print -r $t
+ t=
+ fi
+ done
+}
+
+# Better Avalanche for the Jenkins Hash
+\\builtin typeset -Z11 -Uui16 Lbafh_v
+function Lbafh_init {
+ Lbafh_v=0
+}
+function Lbafh_add {
+ \\builtin set +U
+ \\builtin typeset s
+ if (( $# )); then
+ \\builtin read -raN-1 s <<<"$*"
+ \\builtin unset s[${#s[*]}-1]
+ else
+ \\builtin read -raN-1 s
+ fi
+ \\builtin typeset -i i=0 n=${#s[*]}
+
+ while (( i < n )); do
+ ((# Lbafh_v = (Lbafh_v + s[i++] + 1) * 1025 ))
+ ((# Lbafh_v ^= Lbafh_v >> 6 ))
+ done
+}
+function Lbafh_finish {
+ \\builtin typeset -Ui t
+
+ ((# t = (((Lbafh_v >> 7) & 0x01010101) * 0x1B) ^ \
+ ((Lbafh_v << 1) & 0xFEFEFEFE) ))
+ ((# Lbafh_v = t ^ (t ^> 8) ^ (Lbafh_v ^> 8) ^ \
+ (Lbafh_v ^> 16) ^ (Lbafh_v ^> 24) ))
+ \:
+}
+
+# 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 {
+ \\builtin set -o noglob
+ \\builtin cat "$@" | while \\builtin read _line; do
+ _line=${_line%%#*}
+ [[ -n $_line ]] && \\builtin print -r -- $_line
+ done
+}
+
+# toggle built-in aliases and utilities, and aliases and functions from mkshrc
+function enable {
+ \\builtin typeset doprnt=0 mode=1 x y z rv=0
+ \\builtin typeset b_alias i_alias i_func nalias=0 nfunc=0 i_all
+ \\builtin set -A b_alias
+ \\builtin set -A i_alias
+ \\builtin set -A i_func
+
+ # accumulate mksh built-in aliases, in ASCIIbetical order
+ i_alias[nalias]=autoload; b_alias[nalias++]='\\builtin typeset -fu'
+ i_alias[nalias]=functions; b_alias[nalias++]='\\builtin typeset -f'
+ i_alias[nalias]=hash; b_alias[nalias++]='\\builtin alias -t'
+ i_alias[nalias]=history; b_alias[nalias++]='\\builtin fc -l'
+ i_alias[nalias]=integer; b_alias[nalias++]='\\builtin typeset -i'
+ i_alias[nalias]=local; b_alias[nalias++]='\\builtin typeset'
+ i_alias[nalias]=login; b_alias[nalias++]='\\builtin exec login'
+ i_alias[nalias]=nameref; b_alias[nalias++]='\\builtin typeset -n'
+ i_alias[nalias]=nohup; b_alias[nalias++]='nohup '
+ i_alias[nalias]=r; b_alias[nalias++]='\\builtin fc -e -'
+ i_alias[nalias]=type; b_alias[nalias++]='\\builtin whence -v'
+
+ # accumulate mksh built-in utilities, in definition order, even ifndef
+ i_func[nfunc++]=.
+ i_func[nfunc++]=:
+ i_func[nfunc++]='['
+ i_func[nfunc++]=alias
+ i_func[nfunc++]=break
+ # \\builtin cannot, by design, be overridden
+ i_func[nfunc++]=builtin
+ i_func[nfunc++]=cat
+ i_func[nfunc++]=cd
+ i_func[nfunc++]=chdir
+ i_func[nfunc++]=command
+ i_func[nfunc++]=continue
+ i_func[nfunc++]=echo
+ i_func[nfunc++]=eval
+ i_func[nfunc++]=exec
+ i_func[nfunc++]=exit
+ i_func[nfunc++]=export
+ i_func[nfunc++]=false
+ i_func[nfunc++]=fc
+ i_func[nfunc++]=getopts
+ i_func[nfunc++]=jobs
+ i_func[nfunc++]=kill
+ i_func[nfunc++]=let
+ i_func[nfunc++]=print
+ i_func[nfunc++]=pwd
+ i_func[nfunc++]=read
+ i_func[nfunc++]=readonly
+ i_func[nfunc++]=realpath
+ i_func[nfunc++]=rename
+ i_func[nfunc++]=return
+ i_func[nfunc++]=set
+ i_func[nfunc++]=shift
+ i_func[nfunc++]=source
+ i_func[nfunc++]=suspend
+ i_func[nfunc++]=test
+ i_func[nfunc++]=times
+ i_func[nfunc++]=trap
+ i_func[nfunc++]=true
+ i_func[nfunc++]=typeset
+ i_func[nfunc++]=ulimit
+ i_func[nfunc++]=umask
+ i_func[nfunc++]=unalias
+ i_func[nfunc++]=unset
+ i_func[nfunc++]=wait
+ i_func[nfunc++]=whence
+ i_func[nfunc++]=bg
+ i_func[nfunc++]=fg
+ i_func[nfunc++]=bind
+ i_func[nfunc++]=mknod
+ i_func[nfunc++]=printf
+ i_func[nfunc++]=sleep
+ i_func[nfunc++]=domainname
+ i_func[nfunc++]=extproc
+
+ # accumulate aliases from dot.mkshrc, in definition order
+ i_alias[nalias]=l; b_alias[nalias++]='ls -F'
+ i_alias[nalias]=la; b_alias[nalias++]='l -a'
+ i_alias[nalias]=ll; b_alias[nalias++]='l -l'
+ i_alias[nalias]=lo; b_alias[nalias++]='l -alo'
+ i_alias[nalias]=doch; b_alias[nalias++]='sudo mksh -c "$(\\builtin fc -ln -1)"'
+ i_alias[nalias]=rot13; b_alias[nalias++]='tr abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
+ i_alias[nalias]=cls; b_alias[nalias++]='\\builtin print -n \\ec'
+
+ # accumulate functions from dot.mkshrc, in definition order
+ i_func[nfunc++]=setenv
+ i_func[nfunc++]=smores
+ i_func[nfunc++]=hd
+ i_func[nfunc++]=hd_mksh
+ i_func[nfunc++]=which
+ i_func[nfunc++]=chpwd
+ i_func[nfunc++]=cd
+ i_func[nfunc++]=cd_csh
+ i_func[nfunc++]=dirs
+ i_func[nfunc++]=popd
+ i_func[nfunc++]=pushd
+ i_func[nfunc++]=Lb64decode
+ i_func[nfunc++]=Lb64encode
+ i_func[nfunc++]=Lbafh_init
+ i_func[nfunc++]=Lbafh_add
+ i_func[nfunc++]=Lbafh_finish
+ i_func[nfunc++]=Lstripcom
+ i_func[nfunc++]=enable
+
+ # collect all identifiers, sorted ASCIIbetically
+ \\builtin set -sA i_all -- "${i_alias[@]}" "${i_func[@]}"
+
+ # handle options, we don't do dynamic loading
+ while \\builtin getopts "adf:nps" x; do
+ case $x {
+ (a)
+ mode=-1
+ ;;
+ (d)
+ # deliberately causing an error, like bash-static
+ ;|
+ (f)
+ \\builtin print -ru2 enable: dynamic loading not available
+ \\builtin return 2
+ ;;
+ (n)
+ mode=0
+ ;;
+ (p)
+ doprnt=1
+ ;;
+ (s)
+ \\builtin set -sA i_all -- . : break continue eval \
+ exec exit export readonly return set shift times \
+ trap unset
+ ;;
+ (*)
+ \\builtin print -ru2 enable: usage: \
+ "enable [-adnps] [-f filename] [name ...]"
+ return 2
+ ;;
+ }
+ done
+ \\builtin shift $((OPTIND - 1))
+
+ # display builtins enabled/disabled/all/special?
+ if (( doprnt || ($# == 0) )); then
+ for x in "${i_all[@]}"; do
+ y=$(\\builtin alias "$x") || y=
+ [[ $y = "$x='\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)'" ]]; z=$?
+ case $mode:$z {
+ (-1:0|0:0)
+ \\builtin print -r -- "enable -n $x"
+ ;;
+ (-1:1|1:1)
+ \\builtin print -r -- "enable $x"
+ ;;
+ }
+ done
+ \\builtin return 0
+ fi
+
+ for x in "$@"; do
+ z=0
+ for y in "${i_alias[@]}" "${i_func[@]}"; do
+ [[ $x = "$y" ]] || \\builtin continue
+ z=1
+ \\builtin break
+ done
+ if (( !z )); then
+ \\builtin print -ru2 enable: "$x": not a shell builtin
+ rv=1
+ \\builtin continue
+ fi
+ if (( !mode )); then
+ # disable this
+ \\builtin alias "$x=\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)"
+ else
+ # find out if this is an alias or not, first
+ z=0
+ y=-1
+ while (( ++y < nalias )); do
+ [[ $x = "${i_alias[y]}" ]] || \\builtin continue
+ z=1
+ \\builtin break
+ done
+ if (( z )); then
+ # re-enable the original alias body
+ \\builtin alias "$x=${b_alias[y]}"
+ else
+ # re-enable the original utility/function
+ \\builtin unalias "$x"
+ fi
+ fi
+ done
+ \\builtin return $rv
+}
+
+\: place customisations below this line
+
+# some defaults / samples which you are supposed to adjust to your
+# liking; by default we add ~/.etc/bin and ~/bin (whichever exist)
+# to $PATH, set $SHELL to mksh, set some defaults for man and less
+# and show a few more possible things for users to begin moving in
+
+for p in ~/.etc/bin ~/bin; do
+ [[ -d $p/. ]] || \\builtin continue
+ [[ $PATHSEP$PATH$PATHSEP = *"$PATHSEP$p$PATHSEP"* ]] || \
+ PATH=$p$PATHSEP$PATH
+done
+
+\\builtin export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
+\\builtin alias cls='\\builtin print -n \\ec'
+
+#\\builtin unset LC_ADDRESS LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+# LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+# LC_TELEPHONE LC_TIME LANGUAGE LANG LC_ALL
+#p=en_GB.UTF-8
+#\\builtin export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+#\\builtin export LANG=C.UTF-8 LC_CTYPE=C.UTF-8
+#\\builtin export LC_ALL=C.UTF-8
+#\\builtin set -U
+#[[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} = *[Uu][Tt][Ff]?(-)8* ]] || \\builtin set +U
+
+\\builtin unset p
+
+\: place customisations above this line
diff --git a/shells/mksh/files/edit.c b/shells/mksh/files/edit.c
new file mode 100644
index 00000000000..8c1c2c8e50e
--- /dev/null
+++ b/shells/mksh/files/edit.c
@@ -0,0 +1,5726 @@
+/* $OpenBSD: edit.c,v 1.41 2015/09/01 13:12:31 tedu Exp $ */
+/* $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $ */
+/* $OpenBSD: emacs.c,v 1.52 2015/09/10 22:48:58 nicm Exp $ */
+/* $OpenBSD: vi.c,v 1.30 2015/09/10 22:48:58 nicm Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+#ifndef MKSH_NO_CMDLINE_EDITING
+
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.351 2020/04/15 20:16:19 tg Exp $");
+
+/*
+ * in later versions we might use libtermcap for this, but since external
+ * dependencies are problematic, this has not yet been decided on; another
+ * good string is KSH_ESC_STRING "c" except on hardware terminals like the
+ * DEC VT420 which do a full power cycle then...
+ */
+#ifndef MKSH_CLS_STRING
+#define MKSH_CLS_STRING KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J"
+#endif
+
+/* tty driver characters we are interested in */
+#define EDCHAR_DISABLED 0xFFFFU
+#define EDCHAR_INITIAL 0xFFFEU
+static struct {
+ unsigned short erase;
+ unsigned short kill;
+ unsigned short werase;
+ unsigned short intr;
+ unsigned short quit;
+ unsigned short eof;
+} edchars;
+
+#define isched(x,e) ((unsigned short)(unsigned char)(x) == (e))
+#define isedchar(x) (!((x) & ~0xFF))
+#ifndef _POSIX_VDISABLE
+#define toedchar(x) ((unsigned short)(unsigned char)(x))
+#else
+#define toedchar(x) (((_POSIX_VDISABLE != -1) && ((x) == _POSIX_VDISABLE)) ? \
+ ((unsigned short)EDCHAR_DISABLED) : \
+ ((unsigned short)(unsigned char)(x)))
+#endif
+
+/* 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_IS_COMMAND BIT(3) /* return flag: is command */
+#define XCF_IS_NOSPACE BIT(4) /* return flag: do not append a space */
+
+static char editmode;
+static int xx_cols; /* for Emacs mode */
+static int modified; /* buffer has been "modified" */
+static char *holdbufp; /* place to hold last edit buffer */
+
+/* 0=dumb 1=tmux (for now) */
+static uint8_t x_term_mode;
+
+static void x_adjust(void);
+static int x_getc(void);
+static void x_putcf(int);
+static void x_modified(void);
+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 ***);
+static size_t x_longest_prefix(int, char * const *);
+static void x_glob_hlp_add_qchar(char *);
+static char *x_glob_hlp_tilde_and_rem_qchar(char *, bool);
+static size_t 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));
+static int x_emacs(char *);
+static void x_init_prompt(bool);
+#if !MKSH_S_NOVI
+static int x_vi(char *);
+#endif
+static void x_intr(int, int) MKSH_A_NORETURN;
+
+#define x_flush() shf_flush(shl_out)
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define x_putc(c) x_putcf(c)
+#else
+#define x_putc(c) shf_putc((c), shl_out)
+#endif
+
+static int path_order_cmp(const void *, const void *);
+static void glob_table(const char *, XPtrV *, struct table *);
+static void glob_path(int, const char *, XPtrV *, const char *);
+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 *);
+
+/* +++ generic editing functions +++ */
+
+/*
+ * read an edited command line
+ */
+int
+x_read(char *buf)
+{
+ int i;
+
+ x_mode(true);
+ modified = 1;
+ if (Flag(FEMACS) || Flag(FGMACS))
+ i = x_emacs(buf);
+#if !MKSH_S_NOVI
+ else if (Flag(FVI))
+ i = x_vi(buf);
+#endif
+ else
+ /* internal error */
+ i = -1;
+ editmode = 0;
+ x_mode(false);
+ return (i);
+}
+
+/* tty I/O */
+
+static int
+x_getc(void)
+{
+#ifdef __OS2__
+ return (_read_kbd(0, 1, 0));
+#else
+ char c;
+ ssize_t n;
+
+ while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
+ if (trap) {
+ x_mode(false);
+ runtraps(0);
+#ifdef SIGWINCH
+ if (got_winch) {
+ change_winsz();
+ if (x_cols != xx_cols && editmode == 1) {
+ /* redraw line in Emacs mode */
+ xx_cols = x_cols;
+ x_init_prompt(false);
+ x_adjust();
+ }
+ }
+#endif
+ x_mode(true);
+ }
+ return ((n == 1) ? (int)(unsigned char)c : -1);
+#endif
+}
+
+static void
+x_putcf(int c)
+{
+ shf_putc_i(c, shl_out);
+}
+
+/*********************************
+ * Misc common code for vi/emacs *
+ *********************************/
+
+/*-
+ * Handle the commenting/uncommenting of a line.
+ * Returns:
+ * 1 if a carriage return is indicated (comment added)
+ * 0 if no return (comment removed)
+ * -1 if there is an error (not enough room for comment chars)
+ * If successful, *lenp contains the new length. Note: cursor should be
+ * moved to the start of the line after (un)commenting.
+ */
+static int
+x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp)
+{
+ ssize_t i, j, len = *lenp;
+
+ if (len == 0)
+ /* somewhat arbitrary - it's what AT&T ksh does */
+ return (1);
+
+ /* Already commented? */
+ if (buf[0] == '#') {
+ bool saw_nl = false;
+
+ for (j = 0, i = 1; i < len; i++) {
+ if (!saw_nl || buf[i] != '#')
+ buf[j++] = buf[i];
+ saw_nl = buf[i] == '\n';
+ }
+ *lenp = j;
+ return (0);
+ } else {
+ int n = 1;
+
+ /* See if there's room for the #s - 1 per \n */
+ for (i = 0; i < len; i++)
+ if (buf[i] == '\n')
+ n++;
+ if (len + n >= bsize)
+ return (-1);
+ /* Now add them... */
+ for (i = len, j = len + n; --i >= 0; ) {
+ if (buf[i] == '\n')
+ buf[--j] = '#';
+ buf[--j] = buf[i];
+ }
+ buf[0] = '#';
+ *lenp += n;
+ return (1);
+ }
+}
+
+/****************************************************
+ * Common file/command completion code for vi/emacs *
+ ****************************************************/
+
+static void
+x_print_expansions(int nwords, char * const *words, bool is_command)
+{
+ bool use_copy = false;
+ size_t prefix_len;
+ XPtrV l = { NULL, 0, 0 };
+ struct columnise_opts co;
+
+ /*
+ * Check if all matches are in the same directory (in this
+ * case, we want to omit the directory name)
+ */
+ if (!is_command &&
+ (prefix_len = x_longest_prefix(nwords, words)) > 0) {
+ int i;
+
+ /* Special case for 1 match (prefix is whole word) */
+ if (nwords == 1)
+ prefix_len = x_basename(words[0], NULL);
+ /* Any (non-trailing) slashes in non-common word suffixes? */
+ for (i = 0; i < nwords; i++)
+ if (x_basename(words[i] + prefix_len, NULL) >
+ prefix_len)
+ break;
+ /* All in same directory? */
+ if (i == nwords) {
+ while (prefix_len > 0 &&
+ !mksh_cdirsep(words[0][prefix_len - 1]))
+ prefix_len--;
+ use_copy = true;
+ XPinit(l, nwords + 1);
+ for (i = 0; i < nwords; i++)
+ XPput(l, words[i] + prefix_len);
+ XPput(l, NULL);
+ }
+ }
+ /*
+ * Enumerate expansions
+ */
+ x_putc('\r');
+ x_putc('\n');
+ co.shf = shl_out;
+ co.linesep = '\n';
+ co.do_last = true;
+ co.prefcol = false;
+ pr_list(&co, use_copy ? (char **)XPptrv(l) : words);
+
+ if (use_copy)
+ /* not x_free_words() */
+ XPfree(l);
+}
+
+/*
+ * Convert backslash-escaped string to QCHAR-escaped
+ * string useful for globbing; loses QCHAR unless it
+ * can squeeze in, eg. by previous loss of backslash
+ */
+static void
+x_glob_hlp_add_qchar(char *cp)
+{
+ char ch, *dp = cp;
+ bool escaping = false;
+
+ while ((ch = *cp++)) {
+ if (ch == '\\' && !escaping) {
+ escaping = true;
+ continue;
+ }
+ if (escaping || (ch == QCHAR && (cp - dp) > 1)) {
+ /*
+ * empirically made list of chars to escape
+ * for globbing as well as QCHAR itself
+ */
+ switch (ord(ch)) {
+ case QCHAR:
+ case ORD('$'):
+ case ORD('*'):
+ case ORD('?'):
+ case ORD('['):
+ case ORD('\\'):
+ case ORD('`'):
+ *dp++ = QCHAR;
+ break;
+ }
+ escaping = false;
+ }
+ *dp++ = ch;
+ }
+ *dp = '\0';
+}
+
+/*
+ * Run tilde expansion on argument string, return the result
+ * after unescaping; if the flag is set, the original string
+ * is freed if changed and assumed backslash-escaped, if not
+ * it is assumed QCHAR-escaped
+ */
+static char *
+x_glob_hlp_tilde_and_rem_qchar(char *s, bool magic_flag)
+{
+ char ch, *cp, *dp;
+
+ /*
+ * On the string, check whether we have a tilde expansion,
+ * and if so, discern "~foo/bar" and "~/baz" from "~blah";
+ * if we have a directory part (the former), try to expand
+ */
+ if (*s == '~' && (cp = /* not sdirsep */ strchr(s, '/')) != NULL) {
+ /* ok, so split into "~foo"/"bar" or "~"/"baz" */
+ *cp++ = 0;
+ /* try to expand the tilde */
+ if (!(dp = do_tilde(s + 1))) {
+ /* nope, revert damage */
+ *--cp = '/';
+ } else {
+ /* ok, expand and replace */
+ strpathx(cp, dp, cp, 1);
+ if (magic_flag)
+ afree(s, ATEMP);
+ s = cp;
+ }
+ }
+
+ /* ... convert it from backslash-escaped via QCHAR-escaped... */
+ if (magic_flag)
+ x_glob_hlp_add_qchar(s);
+ /* ... to unescaped, for comparison with the matches */
+ cp = dp = s;
+
+ while ((ch = *cp++)) {
+ if (ch == QCHAR && !(ch = *cp++))
+ break;
+ *dp++ = ch;
+ }
+ *dp = '\0';
+
+ return (s);
+}
+
+/**
+ * Do file globbing:
+ * - does expansion, checks for no match, etc.
+ * - sets *wordsp to array of matching strings
+ * - returns number of matching strings
+ */
+static int
+x_file_glob(int *flagsp, char *toglob, char ***wordsp)
+{
+ char **words, *cp;
+ int nwords;
+ XPtrV w;
+ struct source *s, *sold;
+
+ /* remove all escaping backward slashes */
+ x_glob_hlp_add_qchar(toglob);
+
+ /*
+ * Convert "foo*" (toglob) to an array of strings (words)
+ */
+ sold = source;
+ s = pushs(SWSTR, ATEMP);
+ s->start = s->str = toglob;
+ source = s;
+ if (yylex(ONEWORD | LQCHAR) != LWORD) {
+ source = sold;
+ internal_warningf(Tfg_badsubst);
+ return (0);
+ }
+ source = sold;
+ afree(s, ATEMP);
+ XPinit(w, 32);
+ cp = yylval.cp;
+ while (*cp == CHAR || *cp == QCHAR)
+ cp += 2;
+ nwords = DOGLOB | DOTILDE | DOMARKDIRS;
+ if (*cp != EOS) {
+ /* probably a $FOO expansion */
+ *flagsp |= XCF_IS_NOSPACE;
+ /* this always results in at most one match */
+ nwords = 0;
+ }
+ expand(yylval.cp, &w, nwords);
+ XPput(w, NULL);
+ words = (char **)XPclose(w);
+
+ for (nwords = 0; words[nwords]; nwords++)
+ ;
+ if (nwords == 1) {
+ struct stat statb;
+
+ /* Expand any tilde and drop all QCHAR for comparison */
+ toglob = x_glob_hlp_tilde_and_rem_qchar(toglob, false);
+
+ /*
+ * 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
+ * string (e.g., "$FOO" when there is no FOO, etc).
+ */
+ if ((strcmp(words[0], toglob) == 0 &&
+ stat(words[0], &statb) < 0) ||
+ words[0][0] == '\0') {
+ x_free_words(nwords, words);
+ words = NULL;
+ nwords = 0;
+ }
+ }
+
+ if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
+ x_free_words(nwords, words);
+
+ return (nwords);
+}
+
+/* Data structure used in x_command_glob() */
+struct path_order_info {
+ char *word;
+ size_t base;
+ size_t path_order;
+};
+
+/* Compare routine used in x_command_glob() */
+static int
+path_order_cmp(const void *aa, const void *bb)
+{
+ const struct path_order_info *a = (const struct path_order_info *)aa;
+ const struct path_order_info *b = (const struct path_order_info *)bb;
+ int t;
+
+ if ((t = ascstrcmp(a->word + a->base, b->word + b->base)))
+ return (t);
+ if (a->path_order > b->path_order)
+ return (1);
+ if (a->path_order < b->path_order)
+ return (-1);
+ return (0);
+}
+
+static int
+x_command_glob(int flags, char *toglob, char ***wordsp)
+{
+ char *pat, *fpath;
+ size_t nwords;
+ XPtrV w;
+ struct block *l;
+
+ /* Convert "foo*" (toglob) to a pattern for future use */
+ pat = evalstr(toglob, DOPAT | DOTILDE);
+
+ XPinit(w, 32);
+
+ glob_table(pat, &w, &keywords);
+ glob_table(pat, &w, &aliases);
+ glob_table(pat, &w, &builtins);
+ for (l = e->loc; l; l = l->next)
+ glob_table(pat, &w, &l->funs);
+
+ glob_path(flags, pat, &w, path);
+ if ((fpath = str_val(global(TFPATH))) != null)
+ glob_path(flags, pat, &w, fpath);
+
+ nwords = XPsize(w);
+
+ if (!nwords) {
+ *wordsp = NULL;
+ XPfree(w);
+ return (0);
+ }
+ /* Sort entries */
+ if (flags & XCF_FULLPATH) {
+ /* Sort by basename, then path order */
+ struct path_order_info *info, *last_info = NULL;
+ char **words = (char **)XPptrv(w);
+ size_t i, path_order = 0;
+
+ info = (struct path_order_info *)
+ 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);
+ if (!last_info || info[i].base != last_info->base ||
+ strncmp(words[i], last_info->word, info[i].base) != 0) {
+ last_info = &info[i];
+ path_order++;
+ }
+ info[i].path_order = path_order;
+ }
+ qsort(info, nwords, sizeof(struct path_order_info),
+ path_order_cmp);
+ for (i = 0; i < nwords; i++)
+ words[i] = info[i].word;
+ afree(info, ATEMP);
+ } else {
+ /* Sort and remove duplicate entries */
+ char **words = (char **)XPptrv(w);
+ size_t i, j;
+
+ qsort(words, nwords, sizeof(void *), ascpstrcmp);
+ for (i = j = 0; i < nwords - 1; i++) {
+ if (strcmp(words[i], words[i + 1]))
+ words[j++] = words[i];
+ else
+ afree(words[i], ATEMP);
+ }
+ words[j++] = words[i];
+ w.len = nwords = j;
+ }
+
+ XPput(w, NULL);
+ *wordsp = (char **)XPclose(w);
+
+ return (nwords);
+}
+
+#define IS_WORDC(c) (!ctype(c, C_EDNWC))
+
+static int
+x_locate_word(const char *buf, int buflen, int pos, int *startp,
+ bool *is_commandp)
+{
+ int start, end;
+
+ /* Bad call? Probably should report error */
+ if (pos < 0 || pos > buflen) {
+ *startp = pos;
+ *is_commandp = false;
+ return (0);
+ }
+ /* The case where pos == buflen happens to take care of itself... */
+
+ start = pos;
+ /*
+ * 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])) ||
+ (start > 1 && buf[start - 2] == '\\'); start--)
+ ;
+ /* Go forwards to end of word */
+ for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
+ if (buf[end] == '\\' && (end + 1) < buflen)
+ end++;
+ }
+
+ if (is_commandp) {
+ bool iscmd;
+ int p = start - 1;
+
+ /* Figure out if this is a command */
+ while (p >= 0 && ctype(buf[p], C_SPACE))
+ p--;
+ iscmd = p < 0 || ctype(buf[p], C_EDCMD);
+ if (iscmd) {
+ /*
+ * If command has a /, path, etc. is not searched;
+ * only current directory is searched which is just
+ * like file globbing.
+ */
+ for (p = start; p < end; p++)
+ if (mksh_cdirsep(buf[p]))
+ break;
+ iscmd = p == end;
+ }
+ *is_commandp = iscmd;
+ }
+ *startp = start;
+
+ return (end - start);
+}
+
+static int
+x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
+ int *endp, char ***wordsp)
+{
+ int len, nwords = 0;
+ char **words = NULL;
+ bool is_command;
+
+ len = x_locate_word(buf, buflen, pos, startp, &is_command);
+ if (!((*flagsp) & XCF_COMMAND))
+ is_command = false;
+ /*
+ * 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);
+
+ if (len >= 0) {
+ char *toglob, *s;
+
+ /*
+ * 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 an extglob, then it is globbed based
+ * on that value (i.e., without the appended '*'). Same
+ * for parameter substitutions (as in “cat $HOME/.ss↹”)
+ * without appending a trailing space (LP: #710539), as
+ * well as for “~foo” (but not “~foo/”).
+ */
+ for (s = toglob; *s; s++) {
+ if (*s == '\\' && s[1])
+ s++;
+ else if (ctype(*s, C_QUEST | C_DOLAR) ||
+ ord(*s) == ORD('*') || ord(*s) == ORD('[') ||
+ /* ?() *() +() @() !() but two already checked */
+ (ord(s[1]) == ORD('(' /*)*/) &&
+ (ord(*s) == ORD('+') || ord(*s) == ORD('@') ||
+ ord(*s) == ORD('!')))) {
+ /*
+ * just expand based on the extglob
+ * or parameter
+ */
+ goto dont_add_glob;
+ }
+ }
+
+ if (*toglob == '~' && /* not vdirsep */ !vstrchr(toglob, '/')) {
+ /* neither for '~foo' (but '~foo/bar') */
+ *flagsp |= XCF_IS_NOSPACE;
+ goto dont_add_glob;
+ }
+
+ /* append a glob */
+ toglob[len] = '*';
+ toglob[len + 1] = '\0';
+ dont_add_glob:
+ /*
+ * 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_command)
+ *flagsp |= XCF_IS_COMMAND;
+ *wordsp = words;
+ *endp = *startp + len;
+
+ return (nwords);
+}
+
+/*
+ * Find longest common prefix
+ */
+static size_t
+x_longest_prefix(int nwords, char * const * words)
+{
+ int i;
+ size_t j, prefix_len;
+ char *p;
+
+ if (nwords <= 0)
+ return (0);
+
+ prefix_len = strlen(words[0]);
+ for (i = 1; i < nwords; i++)
+ for (j = 0, p = words[i]; j < prefix_len; j++)
+ if (p[j] != words[0][j]) {
+ prefix_len = j;
+ break;
+ }
+ /* false for nwords==1 as 0 = words[0][prefix_len] then */
+ if (UTFMODE && prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) == 0x80)
+ while (prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) != 0xC0)
+ --prefix_len;
+ return (prefix_len);
+}
+
+static void
+x_free_words(int nwords, char **words)
+{
+ while (nwords)
+ afree(words[--nwords], ATEMP);
+ afree(words, ATEMP);
+}
+
+/*-
+ * 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
+ * /etc 1
+ * /etc/ 1
+ * /etc// 1
+ * /etc/fo 5
+ * foo 0
+ * /// 2
+ * 0
+ */
+static size_t
+x_basename(const char *s, const char *se)
+{
+ const char *p;
+
+ if (se == NULL)
+ se = strnul(s);
+ if (s == se)
+ return (0);
+
+ /* skip trailing directory separators */
+ p = se - 1;
+ while (p > s && mksh_cdirsep(*p))
+ --p;
+ /* drop last component */
+ while (p > s && !mksh_cdirsep(*p))
+ --p;
+ if (mksh_cdirsep(*p) && p + 1 < se)
+ ++p;
+
+ return (p - s);
+}
+
+/*
+ * Apply pattern matching to a table: all table entries that match a pattern
+ * are added to wp.
+ */
+static void
+glob_table(const char *pat, XPtrV *wp, struct table *tp)
+{
+ struct tstate ts;
+ struct tbl *te;
+
+ ktwalk(&ts, tp);
+ while ((te = ktnext(&ts)))
+ if (gmatchx(te->name, pat, false)) {
+ char *cp;
+
+ strdupx(cp, te->name, ATEMP);
+ XPput(*wp, cp);
+ }
+}
+
+static void
+glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
+{
+ const char *sp = lpath, *p;
+ char *xp, **words;
+ size_t pathlen, patlen, oldsize, newsize, i, j;
+ XString xs;
+
+ patlen = strlen(pat);
+ checkoktoadd(patlen, 129 + X_EXTRA);
+ ++patlen;
+ Xinit(xs, xp, patlen + 128, ATEMP);
+ while (sp) {
+ xp = Xstring(xs, xp);
+ if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
+ p = strnul(sp);
+ pathlen = p - sp;
+ if (pathlen) {
+ /*
+ * Copy sp into xp, stuffing any MAGIC characters
+ * on the way
+ */
+ const char *s = sp;
+
+ XcheckN(xs, xp, pathlen * 2);
+ while (s < p) {
+ if (ISMAGIC(*s))
+ *xp++ = MAGIC;
+ *xp++ = *s++;
+ }
+ *xp++ = '/';
+ pathlen++;
+ }
+ sp = p;
+ XcheckN(xs, xp, patlen);
+ memcpy(xp, pat, patlen);
+
+ oldsize = XPsize(*wp);
+ /* mark dirs */
+ glob_str(Xstring(xs, xp), wp, true);
+ newsize = XPsize(*wp);
+
+ /* Check that each match is executable... */
+ words = (char **)XPptrv(*wp);
+ for (i = j = oldsize; i < newsize; i++) {
+ if (ksh_access(words[i], X_OK) == 0) {
+ words[j] = words[i];
+ if (!(flags & XCF_FULLPATH))
+ memmove(words[j], words[j] + pathlen,
+ strlen(words[j] + pathlen) + 1);
+ j++;
+ } else
+ afree(words[i], ATEMP);
+ }
+ wp->len = j;
+
+ if (!*sp++)
+ break;
+ }
+ Xfree(xs, xp);
+}
+
+/*
+ * if argument string contains any special characters, they will
+ * be escaped and the result will be put into edit buffer by
+ * keybinding-specific function
+ */
+static int
+x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t))
+{
+ size_t add = 0, wlen = len;
+ int rval = 0;
+
+ while (wlen - add > 0)
+ if (ctype(s[add], C_IFS | C_EDQ)) {
+ if (putbuf_func(s, add) != 0) {
+ rval = -1;
+ break;
+ }
+ putbuf_func(s[add] == '\n' ? "'" : "\\", 1);
+ putbuf_func(&s[add], 1);
+ if (s[add] == '\n')
+ putbuf_func("'", 1);
+
+ add++;
+ wlen -= add;
+ s += add;
+ add = 0;
+ } else
+ ++add;
+ if (wlen > 0 && rval == 0)
+ rval = putbuf_func(s, wlen);
+
+ return (rval);
+}
+
+
+/* +++ emacs editing mode +++ */
+
+static Area aedit;
+#define AEDIT &aedit /* area for kill ring and macro defns */
+
+/* values returned by keyboard functions */
+#define KSTD 0
+#define KEOL 1 /* ^M, ^J */
+#define KINTR 2 /* ^G, ^C */
+
+struct x_ftab {
+ int (*xf_func)(int c);
+ const char *xf_name;
+ short xf_flags;
+};
+
+struct x_defbindings {
+ unsigned char xdb_func; /* XFUNC_* */
+ unsigned char xdb_tab;
+ unsigned char xdb_char;
+};
+
+#define XF_ARG 1 /* command takes number prefix */
+#define XF_NOBIND 2 /* not allowed to bind to function */
+#define XF_PREFIX 4 /* function sets prefix */
+
+#define X_NTABS 4 /* normal, meta1, meta2, pc */
+#define X_TABSZ 256 /* size of keydef tables etc */
+
+/*-
+ * Arguments for do_complete()
+ * 0 = enumerate M-= complete as much as possible and then list
+ * 1 = complete M-Esc
+ * 2 = list M-?
+ */
+typedef enum {
+ CT_LIST, /* list the possible completions */
+ CT_COMPLETE, /* complete to longest prefix */
+ CT_COMPLIST /* complete and then list (if non-exact) */
+} Comp_type;
+
+/*
+ * The following are used for my horizontal scrolling stuff
+ */
+static char *xbuf; /* beg input buffer */
+static char *xend; /* end input buffer */
+static char *xcp; /* current position */
+static char *xep; /* current end */
+static char *xbp; /* start of visible portion of input buffer */
+static char *xlp; /* last char visible on screen */
+static bool x_adj_ok;
+/*
+ * we use x_adj_done so that functions can tell
+ * whether x_adjust() has been called while they are active.
+ */
+static int x_adj_done; /* is incremented by x_adjust() */
+
+static int x_displen;
+static int x_arg; /* general purpose arg */
+static bool x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */
+
+static bool xlp_valid; /* lastvis pointer was recalculated */
+
+static char **x_histp; /* history position */
+static int x_nextcmd; /* for newline-and-next */
+static char **x_histncp; /* saved x_histp for " */
+static char **x_histmcp; /* saved x_histp for " */
+static char *xmp; /* mark pointer */
+static unsigned char x_last_command;
+static unsigned char (*x_tab)[X_TABSZ]; /* key definition */
+#ifndef MKSH_SMALL
+static char *(*x_atab)[X_TABSZ]; /* macro definitions */
+#endif
+static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8];
+#define KILLSIZE 20
+static char *killstack[KILLSIZE];
+static int killsp, killtp;
+static int x_curprefix;
+#ifndef MKSH_SMALL
+static char *macroptr; /* bind key macro active? */
+#endif
+#if !MKSH_S_NOVI
+static int winwidth; /* width of window */
+static char *wbuf[2]; /* window buffers */
+static int wbuf_len; /* length of window buffers (x_cols - 3) */
+static int win; /* window buffer in use */
+static char morec; /* more character at right of window */
+static int lastref; /* argument to last refresh() */
+static int holdlen; /* length of holdbuf */
+#endif
+static int pwidth; /* width of prompt */
+static int prompt_trunc; /* how much of prompt to truncate or -1 */
+static int x_col; /* current column on line */
+
+static int x_ins(const char *);
+static void x_delete(size_t, bool);
+static size_t x_bword(void);
+static size_t x_fword(bool);
+static void x_goto(char *);
+static char *x_bs0(char *, char *) MKSH_A_PURE;
+static void x_bs3(char **);
+static int x_size2(char *, char **);
+static void x_zots(char *);
+static void x_zotc3(char **);
+static void x_vi_zotc(int);
+static void x_load_hist(char **);
+static int x_search(char *, int, int);
+#ifndef MKSH_SMALL
+static int x_search_dir(int);
+#endif
+static int x_match(char *, char *);
+static void x_redraw(int);
+static void x_push(size_t);
+static void x_bind_showone(int, int);
+static void x_e_ungetc(int);
+static int x_e_getc(void);
+static void x_e_putc2(int);
+static void x_e_putc3(const char **);
+static void x_e_puts(const char *);
+#ifndef MKSH_SMALL
+static int x_fold_case(int);
+#endif
+static char *x_lastcp(void);
+static void x_lastpos(void);
+static void do_complete(int, Comp_type);
+static size_t x_nb2nc(size_t) MKSH_A_PURE;
+
+static int unget_char = -1;
+
+static int x_do_ins(const char *, size_t);
+static void bind_if_not_bound(int, int, int);
+
+enum emacs_funcs {
+#define EMACSFN_ENUMS
+#include "emacsfn.h"
+ XFUNC_MAX
+};
+
+#define EMACSFN_DEFNS
+#include "emacsfn.h"
+
+static const struct x_ftab x_ftab[] = {
+#define EMACSFN_ITEMS
+#include "emacsfn.h"
+};
+
+static struct x_defbindings const x_defbindings[] = {
+ { XFUNC_del_back, 0, CTRL_QM },
+ { XFUNC_del_bword, 1, CTRL_QM },
+ { XFUNC_eot_del, 0, CTRL_D },
+ { XFUNC_del_back, 0, CTRL_H },
+ { XFUNC_del_bword, 1, CTRL_H },
+ { XFUNC_del_bword, 1, 'h' },
+ { XFUNC_mv_bword, 1, 'b' },
+ { XFUNC_mv_fword, 1, 'f' },
+ { XFUNC_del_fword, 1, 'd' },
+ { XFUNC_mv_back, 0, CTRL_B },
+ { XFUNC_mv_forw, 0, CTRL_F },
+ { XFUNC_search_char_forw, 0, CTRL_BC },
+ { XFUNC_search_char_back, 1, CTRL_BC },
+ { XFUNC_newline, 0, CTRL_M },
+ { XFUNC_newline, 0, CTRL_J },
+ { XFUNC_end_of_text, 0, CTRL_US },
+ { XFUNC_abort, 0, CTRL_G },
+ { XFUNC_prev_com, 0, CTRL_P },
+ { XFUNC_next_com, 0, CTRL_N },
+ { XFUNC_nl_next_com, 0, CTRL_O },
+ { XFUNC_search_hist, 0, CTRL_R },
+ { XFUNC_beg_hist, 1, '<' },
+ { XFUNC_end_hist, 1, '>' },
+ { XFUNC_goto_hist, 1, 'g' },
+ { XFUNC_mv_end, 0, CTRL_E },
+ { XFUNC_mv_beg, 0, CTRL_A },
+ { XFUNC_draw_line, 0, CTRL_L },
+ { XFUNC_cls, 1, CTRL_L },
+ { XFUNC_meta1, 0, CTRL_BO },
+ { XFUNC_meta2, 0, CTRL_X },
+ { XFUNC_kill, 0, CTRL_K },
+ { XFUNC_yank, 0, CTRL_Y },
+ { XFUNC_meta_yank, 1, 'y' },
+ { XFUNC_literal, 0, CTRL_CA },
+ { XFUNC_comment, 1, '#' },
+ { XFUNC_transpose, 0, CTRL_T },
+ { XFUNC_complete, 1, CTRL_BO },
+ { XFUNC_comp_list, 0, CTRL_I },
+ { XFUNC_comp_list, 1, '=' },
+ { XFUNC_enumerate, 1, '?' },
+ { XFUNC_expand, 1, '*' },
+ { XFUNC_comp_file, 1, CTRL_X },
+ { XFUNC_comp_comm, 2, CTRL_BO },
+ { XFUNC_list_comm, 2, '?' },
+ { XFUNC_list_file, 2, CTRL_Y },
+ { XFUNC_set_mark, 1, ' ' },
+ { XFUNC_kill_region, 0, CTRL_W },
+ { XFUNC_xchg_point_mark, 2, CTRL_X },
+ { XFUNC_literal, 0, CTRL_V },
+ { XFUNC_version, 1, CTRL_V },
+ { XFUNC_prev_histword, 1, '.' },
+ { XFUNC_prev_histword, 1, '_' },
+ { XFUNC_set_arg, 1, '0' },
+ { XFUNC_set_arg, 1, '1' },
+ { XFUNC_set_arg, 1, '2' },
+ { XFUNC_set_arg, 1, '3' },
+ { XFUNC_set_arg, 1, '4' },
+ { XFUNC_set_arg, 1, '5' },
+ { XFUNC_set_arg, 1, '6' },
+ { XFUNC_set_arg, 1, '7' },
+ { XFUNC_set_arg, 1, '8' },
+ { XFUNC_set_arg, 1, '9' },
+#ifndef MKSH_SMALL
+ { XFUNC_fold_upper, 1, 'U' },
+ { XFUNC_fold_upper, 1, 'u' },
+ { XFUNC_fold_lower, 1, 'L' },
+ { XFUNC_fold_lower, 1, 'l' },
+ { XFUNC_fold_capitalise, 1, 'C' },
+ { XFUNC_fold_capitalise, 1, 'c' },
+#endif
+ /*
+ * These for ANSI arrow keys: arguablely shouldn't be here by
+ * default, but its simpler/faster/smaller than using termcap
+ * entries.
+ */
+ { XFUNC_meta2, 1, '[' },
+ { XFUNC_meta2, 1, 'O' },
+ { XFUNC_prev_com, 2, 'A' },
+ { XFUNC_next_com, 2, 'B' },
+ { XFUNC_mv_forw, 2, 'C' },
+ { XFUNC_mv_back, 2, 'D' },
+#ifndef MKSH_SMALL
+ { XFUNC_vt_hack, 2, '1' },
+ { XFUNC_mv_beg | 0x80, 2, '7' },
+ { XFUNC_mv_beg, 2, 'H' },
+ { XFUNC_mv_end | 0x80, 2, '4' },
+ { XFUNC_mv_end | 0x80, 2, '8' },
+ { XFUNC_mv_end, 2, 'F' },
+ { XFUNC_del_char | 0x80, 2, '3' },
+ { XFUNC_del_char, 2, 'P' },
+ { XFUNC_search_hist_up | 0x80, 2, '5' },
+ { XFUNC_search_hist_dn | 0x80, 2, '6' },
+#endif
+ /* PC scancodes */
+#if !defined(MKSH_SMALL) || defined(__OS2__)
+ { XFUNC_meta3, 0, 0 },
+ { XFUNC_mv_beg, 3, 71 },
+ { XFUNC_prev_com, 3, 72 },
+#ifndef MKSH_SMALL
+ { XFUNC_search_hist_up, 3, 73 },
+#endif
+ { XFUNC_mv_back, 3, 75 },
+ { XFUNC_mv_forw, 3, 77 },
+ { XFUNC_mv_end, 3, 79 },
+ { XFUNC_next_com, 3, 80 },
+#ifndef MKSH_SMALL
+ { XFUNC_search_hist_dn, 3, 81 },
+#endif
+ { XFUNC_del_char, 3, 83 },
+#endif
+#ifndef MKSH_SMALL
+ /* more non-standard ones */
+ { XFUNC_eval_region, 1, CTRL_E },
+ { XFUNC_quote_region, 1, 'Q' },
+ { XFUNC_edit_line, 2, 'e' }
+#endif
+};
+
+static size_t
+x_nb2nc(size_t nb)
+{
+ char *cp;
+ size_t nc = 0;
+
+ for (cp = xcp; cp < (xcp + nb); ++nc)
+ cp += utf_ptradj(cp);
+ return (nc);
+}
+
+static void
+x_modified(void)
+{
+ if (!modified) {
+ x_histmcp = x_histp;
+ x_histp = histptr + 1;
+ modified = 1;
+ }
+}
+
+#ifdef MKSH_SMALL
+#define XFUNC_VALUE(f) (f)
+#else
+#define XFUNC_VALUE(f) (f & 0x7F)
+#endif
+
+static int
+x_e_getmbc(char *sbuf)
+{
+ int c, pos = 0;
+ unsigned char *buf = (unsigned char *)sbuf;
+
+ memset(buf, 0, 4);
+ buf[pos++] = c = x_e_getc();
+ if (c == -1)
+ return (-1);
+ if (UTFMODE) {
+ if ((rtt2asc(buf[0]) >= (unsigned char)0xC2) &&
+ (rtt2asc(buf[0]) < (unsigned char)0xF0)) {
+ c = x_e_getc();
+ if (c == -1)
+ return (-1);
+ if ((rtt2asc(c) & 0xC0) != 0x80) {
+ x_e_ungetc(c);
+ return (1);
+ }
+ buf[pos++] = c;
+ }
+ if ((rtt2asc(buf[0]) >= (unsigned char)0xE0) &&
+ (rtt2asc(buf[0]) < (unsigned char)0xF0)) {
+ /* XXX x_e_ungetc is one-octet only */
+ buf[pos++] = c = x_e_getc();
+ if (c == -1)
+ return (-1);
+ }
+ }
+ return (pos);
+}
+
+/*
+ * 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
+
+static void
+x_init_prompt(bool doprint)
+{
+ prompt_trunc = pprompt(prompt, doprint ? 0 : -1);
+ pwidth = prompt_trunc % x_cols;
+ prompt_trunc -= pwidth;
+ if ((mksh_uari_t)pwidth > ((mksh_uari_t)x_cols - 3 - MIN_EDIT_SPACE)) {
+ /* force newline after prompt */
+ prompt_trunc = -1;
+ pwidth = 0;
+ if (doprint)
+ x_e_putc2('\n');
+ }
+}
+
+static int
+x_emacs(char *buf)
+{
+ int c, i;
+ unsigned char f;
+
+ xbp = xbuf = buf;
+ xend = buf + LINE;
+ xlp = xcp = xep = buf;
+ *xcp = 0;
+ xlp_valid = true;
+ xmp = NULL;
+ x_curprefix = 0;
+ x_histmcp = x_histp = histptr + 1;
+ x_last_command = XFUNC_error;
+
+ x_init_prompt(true);
+ x_displen = (xx_cols = x_cols) - 2 - (x_col = pwidth);
+ x_adj_done = 0;
+ x_adj_ok = true;
+
+ x_histncp = NULL;
+ if (x_nextcmd >= 0) {
+ int off = source->line - x_nextcmd;
+ if (histptr - history >= off) {
+ x_load_hist(histptr - off);
+ x_histncp = x_histp;
+ }
+ x_nextcmd = -1;
+ }
+ editmode = 1;
+ while (/* CONSTCOND */ 1) {
+ x_flush();
+ if ((c = x_e_getc()) < 0)
+ return (0);
+
+ f = x_curprefix == -1 ? XFUNC_insert :
+ x_tab[x_curprefix][c];
+#ifndef MKSH_SMALL
+ if (f & 0x80) {
+ f &= 0x7F;
+ if ((i = x_e_getc()) != '~')
+ x_e_ungetc(i);
+ }
+
+ /* avoid bind key macro recursion */
+ if (macroptr && f == XFUNC_ins_string)
+ f = XFUNC_insert;
+#endif
+
+ if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
+ x_last_command != XFUNC_set_arg) {
+ x_arg = 1;
+ x_arg_defaulted = true;
+ }
+ i = c | (x_curprefix << 8);
+ x_curprefix = 0;
+ switch ((*x_ftab[f].xf_func)(i)) {
+ case KSTD:
+ if (!(x_ftab[f].xf_flags & XF_PREFIX))
+ x_last_command = f;
+ break;
+ case KEOL:
+ i = xep - xbuf;
+ return (i);
+ case KINTR:
+ /* special case for interrupt */
+ x_intr(SIGINT, c);
+ }
+ /* ad-hoc hack for fixing the cursor position */
+ x_goto(xcp);
+ }
+}
+
+static int
+x_insert(int c)
+{
+ static int left, pos, save_arg;
+ static char str[4];
+
+ /*
+ * Should allow tab and control chars.
+ */
+ if (c == 0) {
+ invmbs:
+ left = 0;
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ if (UTFMODE) {
+ if (((rtt2asc(c) & 0xC0) == 0x80) && left) {
+ str[pos++] = c;
+ if (!--left) {
+ str[pos] = '\0';
+ x_arg = save_arg;
+ while (x_arg--)
+ x_ins(str);
+ }
+ return (KSTD);
+ }
+ if (left) {
+ if (x_curprefix == -1) {
+ /* flush invalid multibyte */
+ str[pos] = '\0';
+ while (save_arg--)
+ x_ins(str);
+ }
+ }
+ if ((c >= 0xC2) && (c < 0xE0))
+ left = 1;
+ else if ((c >= 0xE0) && (c < 0xF0))
+ left = 2;
+ else if (c > 0x7F)
+ goto invmbs;
+ else
+ left = 0;
+ if (left) {
+ save_arg = x_arg;
+ pos = 1;
+ str[0] = c;
+ return (KSTD);
+ }
+ }
+ left = 0;
+ str[0] = c;
+ str[1] = '\0';
+ while (x_arg--)
+ x_ins(str);
+ return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+static int
+x_ins_string(int c)
+{
+ macroptr = x_atab[c >> 8][c & 255];
+ /*
+ * we no longer need to bother checking if macroptr is
+ * not NULL but first char is NUL; x_e_getc() does it
+ */
+ return (KSTD);
+}
+#endif
+
+static int
+x_do_ins(const char *cp, size_t len)
+{
+ if (xep + len >= xend) {
+ x_e_putc2(KSH_BEL);
+ return (-1);
+ }
+ memmove(xcp + len, xcp, xep - xcp + 1);
+ memmove(xcp, cp, len);
+ xcp += len;
+ xep += len;
+ x_modified();
+ return (0);
+}
+
+static int
+x_ins(const char *s)
+{
+ char *cp = xcp;
+ int adj = x_adj_done;
+
+ if (x_do_ins(s, strlen(s)) < 0)
+ return (-1);
+ /*
+ * x_zots() may result in a call to x_adjust()
+ * we want xcp to reflect the new position.
+ */
+ xlp_valid = false;
+ x_lastcp();
+ x_adj_ok = tobool(xcp >= xlp);
+ x_zots(cp);
+ if (adj == x_adj_done)
+ /* x_adjust() has not been called */
+ x_lastpos();
+ x_adj_ok = true;
+ return (0);
+}
+
+static int
+x_del_back(int c MKSH_A_UNUSED)
+{
+ ssize_t i = 0;
+
+ if (xcp == xbuf) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ do {
+ x_goto(xcp - 1);
+ } while ((++i < x_arg) && (xcp != xbuf));
+ x_delete(i, false);
+ return (KSTD);
+}
+
+static int
+x_del_char(int c MKSH_A_UNUSED)
+{
+ char *cp, *cp2;
+ size_t i = 0;
+
+ cp = xcp;
+ while (i < (size_t)x_arg) {
+ utf_ptradjx(cp, cp2);
+ if (cp2 > xep)
+ break;
+ cp = cp2;
+ i++;
+ }
+
+ if (!i) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ x_delete(i, false);
+ return (KSTD);
+}
+
+/* Delete nc chars to the right of the cursor (including cursor position) */
+static void
+x_delete(size_t nc, bool push)
+{
+ size_t i, nb, nw;
+ char *cp;
+
+ if (nc == 0)
+ return;
+
+ nw = 0;
+ cp = xcp;
+ for (i = 0; i < nc; ++i) {
+ char *cp2;
+ int j;
+
+ j = x_size2(cp, &cp2);
+ if (cp2 > xep)
+ break;
+ cp = cp2;
+ nw += j;
+ }
+ nb = cp - xcp;
+ /* nc = i; */
+
+ if (xmp != NULL && xmp > xcp) {
+ if (xcp + nb > xmp)
+ xmp = xcp;
+ else
+ xmp -= nb;
+ }
+ /*
+ * This lets us yank a word we have deleted.
+ */
+ if (push)
+ x_push(nb);
+
+ xep -= nb;
+ /* Copies the NUL */
+ memmove(xcp, xcp + nb, xep - xcp + 1);
+ /* don't redraw */
+ x_adj_ok = false;
+ xlp_valid = false;
+ x_zots(xcp);
+ /*
+ * if we are already filling the line,
+ * 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) {
+ nw = i = (nw < i) ? nw : i;
+ while (i--)
+ x_e_putc2(' ');
+ if (x_col == xx_cols - 2) {
+ x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' ');
+ ++nw;
+ }
+ while (nw--)
+ x_e_putc2('\b');
+ }
+ /*x_goto(xcp);*/
+ x_adj_ok = true;
+ xlp_valid = false;
+ x_lastpos();
+ x_modified();
+ return;
+}
+
+static int
+x_del_bword(int c MKSH_A_UNUSED)
+{
+ x_delete(x_bword(), true);
+ return (KSTD);
+}
+
+static int
+x_mv_bword(int c MKSH_A_UNUSED)
+{
+ x_bword();
+ return (KSTD);
+}
+
+static int
+x_mv_fword(int c MKSH_A_UNUSED)
+{
+ x_fword(true);
+ return (KSTD);
+}
+
+static int
+x_del_fword(int c MKSH_A_UNUSED)
+{
+ x_delete(x_fword(false), true);
+ return (KSTD);
+}
+
+static size_t
+x_bword(void)
+{
+ size_t nb = 0;
+ char *cp = xcp;
+
+ if (cp == xbuf) {
+ x_e_putc2(KSH_BEL);
+ return (0);
+ }
+ while (x_arg--) {
+ while (cp != xbuf && ctype(cp[-1], C_MFS)) {
+ cp--;
+ nb++;
+ }
+ while (cp != xbuf && !ctype(cp[-1], C_MFS)) {
+ cp--;
+ nb++;
+ }
+ }
+ x_goto(cp);
+ return (x_nb2nc(nb));
+}
+
+static size_t
+x_fword(bool move)
+{
+ size_t nc;
+ char *cp = xcp;
+
+ if (cp == xep) {
+ x_e_putc2(KSH_BEL);
+ return (0);
+ }
+ while (x_arg--) {
+ while (cp != xep && ctype(*cp, C_MFS))
+ cp++;
+ while (cp != xep && !ctype(*cp, C_MFS))
+ cp++;
+ }
+ nc = x_nb2nc(cp - xcp);
+ if (move)
+ x_goto(cp);
+ return (nc);
+}
+
+static void
+x_goto(char *cp)
+{
+ cp = cp >= xep ? xep : x_bs0(cp, xbuf);
+ if (cp < xbp || cp >= utf_skipcols(xbp, x_displen, NULL)) {
+ /* we are heading off screen */
+ xcp = cp;
+ x_adjust();
+ } else if (cp < xcp) {
+ /* move back */
+ while (cp < xcp)
+ x_bs3(&xcp);
+ } else if (cp > xcp) {
+ /* move forward */
+ while (cp > xcp)
+ x_zotc3(&xcp);
+ }
+}
+
+static char *
+x_bs0(char *cp, char *lower_bound)
+{
+ if (UTFMODE)
+ while ((!lower_bound || (cp > lower_bound)) &&
+ ((rtt2asc(*cp) & 0xC0) == 0x80))
+ --cp;
+ return (cp);
+}
+
+static void
+x_bs3(char **p)
+{
+ int i;
+
+ *p = x_bs0((*p) - 1, NULL);
+ i = x_size2(*p, NULL);
+ while (i--)
+ x_e_putc2('\b');
+}
+
+static int
+x_size2(char *cp, char **dcp)
+{
+ uint8_t c = *(unsigned char *)cp;
+
+ if (UTFMODE && (rtt2asc(c) > 0x7F))
+ return (utf_widthadj(cp, (const char **)dcp));
+ if (dcp)
+ *dcp = cp + 1;
+ if (c == '\t')
+ /* Kludge, tabs are always four spaces. */
+ return (4);
+ if (ksh_isctrl(c))
+ /* control unsigned char */
+ return (2);
+ return (1);
+}
+
+static void
+x_zots(char *str)
+{
+ int adj = x_adj_done;
+
+ x_lastcp();
+ while (*str && str < xlp && x_col < xx_cols && adj == x_adj_done)
+ x_zotc3(&str);
+}
+
+static void
+x_zotc3(char **cp)
+{
+ unsigned char c = **(unsigned char **)cp;
+
+ if (c == '\t') {
+ /* Kludge, tabs are always four spaces. */
+ x_e_puts(T4spaces);
+ (*cp)++;
+ } else if (ksh_isctrl(c)) {
+ x_e_putc2('^');
+ x_e_putc2(ksh_unctrl(c));
+ (*cp)++;
+ } else
+ x_e_putc3((const char **)cp);
+}
+
+static int
+x_mv_back(int c MKSH_A_UNUSED)
+{
+ if (xcp == xbuf) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ while (x_arg--) {
+ x_goto(xcp - 1);
+ if (xcp == xbuf)
+ break;
+ }
+ return (KSTD);
+}
+
+static int
+x_mv_forw(int c MKSH_A_UNUSED)
+{
+ char *cp = xcp, *cp2;
+
+ if (xcp == xep) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ while (x_arg--) {
+ utf_ptradjx(cp, cp2);
+ if (cp2 > xep)
+ break;
+ cp = cp2;
+ }
+ x_goto(cp);
+ return (KSTD);
+}
+
+static int
+x_search_char_forw(int c MKSH_A_UNUSED)
+{
+ char *cp = xcp;
+ char tmp[4];
+
+ *xep = '\0';
+ if (x_e_getmbc(tmp) < 0) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ while (x_arg--) {
+ if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL &&
+ (cp = strstr(xbuf, tmp)) == NULL) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ }
+ x_goto(cp);
+ return (KSTD);
+}
+
+static int
+x_search_char_back(int c MKSH_A_UNUSED)
+{
+ char *cp = xcp, *p, tmp[4];
+ bool b;
+
+ if (x_e_getmbc(tmp) < 0) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ for (; x_arg--; cp = p)
+ for (p = cp; ; ) {
+ if (p-- == xbuf)
+ p = xep;
+ if (p == cp) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ if ((tmp[1] && ((p+1) > xep)) ||
+ (tmp[2] && ((p+2) > xep)))
+ continue;
+ b = true;
+ if (*p != tmp[0])
+ b = false;
+ if (b && tmp[1] && p[1] != tmp[1])
+ b = false;
+ if (b && tmp[2] && p[2] != tmp[2])
+ b = false;
+ if (b)
+ break;
+ }
+ x_goto(cp);
+ return (KSTD);
+}
+
+static int
+x_newline(int c MKSH_A_UNUSED)
+{
+ x_e_putc2('\r');
+ x_e_putc2('\n');
+ x_flush();
+ *xep++ = '\n';
+ return (KEOL);
+}
+
+static int
+x_end_of_text(int c MKSH_A_UNUSED)
+{
+ unsigned char tmp[1], *cp = tmp;
+
+ *tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
+ (unsigned char)CTRL_D;
+ x_zotc3((char **)&cp);
+ x_putc('\r');
+ x_putc('\n');
+ x_flush();
+ return (KEOL);
+}
+
+static int
+x_beg_hist(int c MKSH_A_UNUSED)
+{
+ x_load_hist(history);
+ return (KSTD);
+}
+
+static int
+x_end_hist(int c MKSH_A_UNUSED)
+{
+ x_load_hist(histptr);
+ return (KSTD);
+}
+
+static int
+x_prev_com(int c MKSH_A_UNUSED)
+{
+ x_load_hist(x_histp - x_arg);
+ return (KSTD);
+}
+
+static int
+x_next_com(int c MKSH_A_UNUSED)
+{
+ x_load_hist(x_histp + x_arg);
+ return (KSTD);
+}
+
+/*
+ * 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.
+ */
+static int
+x_goto_hist(int c MKSH_A_UNUSED)
+{
+ if (x_arg_defaulted)
+ x_load_hist(history);
+ else
+ x_load_hist(histptr + x_arg - source->line);
+ return (KSTD);
+}
+
+static void
+x_load_hist(char **hp)
+{
+ char *sp = NULL;
+
+ if (hp == histptr + 1) {
+ sp = holdbufp;
+ modified = 0;
+ } else if (hp < history || hp > histptr) {
+ x_e_putc2(KSH_BEL);
+ return;
+ }
+ if (sp == NULL)
+ sp = *hp;
+ x_histp = hp;
+ if (modified)
+ strlcpy(holdbufp, xbuf, LINE);
+ strlcpy(xbuf, sp, xend - xbuf);
+ xbp = xbuf;
+ xep = xcp = strnul(xbuf);
+ x_adjust();
+ modified = 0;
+}
+
+static int
+x_nl_next_com(int c MKSH_A_UNUSED)
+{
+ if (!modified)
+ x_histmcp = x_histp;
+ if (!x_histncp || (x_histmcp != x_histncp && x_histmcp != histptr + 1))
+ /* fresh start of ^O */
+ x_histncp = x_histmcp;
+ x_nextcmd = source->line - (histptr - x_histncp) + 1;
+ return (x_newline('\n'));
+}
+
+static int
+x_eot_del(int c)
+{
+ if (xep == xbuf && x_arg_defaulted)
+ return (x_end_of_text(c));
+ else
+ return (x_del_char(c));
+}
+
+/* reverse incremental history search */
+static int
+x_search_hist(int c)
+{
+ int offset = -1; /* offset of match in xbuf, else -1 */
+ char pat[80 + 1]; /* pattern buffer */
+ char *p = pat;
+ unsigned char f;
+
+ *p = '\0';
+ while (/* CONSTCOND */ 1) {
+ if (offset < 0) {
+ x_e_puts("\nI-search: ");
+ x_e_puts(pat);
+ }
+ x_flush();
+ if ((c = x_e_getc()) < 0)
+ return (KSTD);
+ f = x_tab[0][c];
+ if (c == CTRL_BO) {
+ if ((f & 0x7F) == XFUNC_meta1) {
+ if ((c = x_e_getc()) < 0)
+ return (KSTD);
+ f = x_tab[1][c] & 0x7F;
+ if (f == XFUNC_meta1 || f == XFUNC_meta2)
+ x_meta1(CTRL_BO);
+ x_e_ungetc(c);
+ }
+ break;
+ }
+#ifndef MKSH_SMALL
+ if (f & 0x80) {
+ f &= 0x7F;
+ if ((c = x_e_getc()) != '~')
+ x_e_ungetc(c);
+ }
+#endif
+ if (f == XFUNC_search_hist)
+ offset = x_search(pat, 0, offset);
+ else if (f == XFUNC_del_back) {
+ if (p == pat) {
+ offset = -1;
+ break;
+ }
+ if (p > pat) {
+ p = x_bs0(p - 1, pat);
+ *p = '\0';
+ }
+ if (p == pat)
+ offset = -1;
+ else
+ offset = x_search(pat, 1, offset);
+ continue;
+ } else if (f == XFUNC_insert) {
+ /* add char to pattern */
+ /* overflow check... */
+ if ((size_t)(p - pat) >= sizeof(pat) - 1) {
+ x_e_putc2(KSH_BEL);
+ continue;
+ }
+ *p++ = c, *p = '\0';
+ if (offset >= 0) {
+ /* already have partial match */
+ offset = x_match(xbuf, pat);
+ if (offset >= 0) {
+ x_goto(xbuf + offset + (p - pat) -
+ (*pat == '^'));
+ continue;
+ }
+ }
+ offset = x_search(pat, 0, offset);
+ } else if (f == XFUNC_abort) {
+ if (offset >= 0)
+ x_load_hist(histptr + 1);
+ break;
+ } else {
+ /* other command */
+ x_e_ungetc(c);
+ break;
+ }
+ }
+ if (offset < 0)
+ x_redraw('\n');
+ return (KSTD);
+}
+
+/* search backward from current line */
+static int
+x_search(char *pat, int sameline, int offset)
+{
+ char **hp;
+ int i;
+
+ for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) {
+ i = x_match(*hp, pat);
+ if (i >= 0) {
+ if (offset < 0)
+ x_e_putc2('\n');
+ x_load_hist(hp);
+ x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
+ return (i);
+ }
+ }
+ x_e_putc2(KSH_BEL);
+ x_histp = histptr;
+ return (-1);
+}
+
+#ifndef MKSH_SMALL
+/* anchored search up from current line */
+static int
+x_search_hist_up(int c MKSH_A_UNUSED)
+{
+ return (x_search_dir(-1));
+}
+
+/* anchored search down from current line */
+static int
+x_search_hist_dn(int c MKSH_A_UNUSED)
+{
+ return (x_search_dir(1));
+}
+
+/* anchored search in the indicated direction */
+static int
+x_search_dir(int search_dir /* should've been bool */)
+{
+ char **hp = x_histp + search_dir;
+ size_t curs = xcp - xbuf;
+
+ while (histptr >= hp && hp >= history) {
+ if (strncmp(xbuf, *hp, curs) == 0) {
+ x_load_hist(hp);
+ x_goto(xbuf + curs);
+ break;
+ }
+ hp += search_dir;
+ }
+ return (KSTD);
+}
+#endif
+
+/* return position of first match of pattern in string, else -1 */
+static int
+x_match(char *str, char *pat)
+{
+ if (*pat == '^') {
+ return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1);
+ } else {
+ char *q = strstr(str, pat);
+ return ((q == NULL) ? -1 : q - str);
+ }
+}
+
+static int
+x_del_line(int c MKSH_A_UNUSED)
+{
+ *xep = 0;
+ x_push(xep - (xcp = xbuf));
+ xlp = xbp = xep = xbuf;
+ xlp_valid = true;
+ *xcp = 0;
+ xmp = NULL;
+ x_redraw('\r');
+ x_modified();
+ return (KSTD);
+}
+
+static int
+x_mv_end(int c MKSH_A_UNUSED)
+{
+ x_goto(xep);
+ return (KSTD);
+}
+
+static int
+x_mv_beg(int c MKSH_A_UNUSED)
+{
+ x_goto(xbuf);
+ return (KSTD);
+}
+
+static int
+x_draw_line(int c MKSH_A_UNUSED)
+{
+ x_redraw('\n');
+ return (KSTD);
+}
+
+static int
+x_cls(int c MKSH_A_UNUSED)
+{
+ shf_puts(MKSH_CLS_STRING, shl_out);
+ x_redraw(0);
+ return (KSTD);
+}
+
+/*
+ * clear line from x_col (current cursor position) to xx_cols - 2,
+ * then output lastch, then go back to x_col; if lastch is space,
+ * clear with termcap instead of spaces, or not if line_was_cleared;
+ * lastch MUST be an ASCII character with wcwidth(lastch) == 1
+ */
+static void
+x_clrtoeol(int lastch, bool line_was_cleared)
+{
+ int col;
+
+ if (lastch == ' ' && !line_was_cleared && x_term_mode == 1) {
+ shf_puts(KSH_ESC_STRING "[K", shl_out);
+ line_was_cleared = true;
+ }
+ if (lastch == ' ' && line_was_cleared)
+ return;
+
+ col = x_col;
+ while (col < (xx_cols - 2)) {
+ x_putc(' ');
+ ++col;
+ }
+ x_putc(lastch);
+ ++col;
+ while (col > x_col) {
+ x_putc('\b');
+ --col;
+ }
+}
+
+/* output the prompt, assuming a line has just been started */
+static void
+x_pprompt(void)
+{
+ if (prompt_trunc != -1)
+ pprompt(prompt, prompt_trunc);
+ x_col = pwidth;
+}
+
+/* output CR, then redraw the line, clearing to EOL if needed (cr ≠ 0, LF) */
+static void
+x_redraw(int cr)
+{
+ int lch;
+
+ x_adj_ok = false;
+ /* clear the line */
+ x_e_putc2(cr ? cr : '\r');
+ x_flush();
+ /* display the prompt */
+ if (xbp == xbuf)
+ x_pprompt();
+ x_displen = xx_cols - 2 - x_col;
+ /* display the line content */
+ xlp_valid = false;
+ x_zots(xbp);
+ /* check whether there is more off-screen */
+ lch = xep > xlp ? (xbp > xbuf ? '*' : '>') : (xbp > xbuf) ? '<' : ' ';
+ /* clear the rest of the line */
+ x_clrtoeol(lch, !cr || cr == '\n');
+ /* go back to actual cursor position */
+ x_lastpos();
+ x_adj_ok = true;
+}
+
+static int
+x_transpose(int c MKSH_A_UNUSED)
+{
+ unsigned int tmpa, tmpb;
+
+ /*-
+ * 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
+ * AT&T ksh in emacs mode: abCd abdC abcd_ (bell)
+ * AT&T ksh in gmacs mode: abCd baCd abcd_ abdc_
+ * gnu emacs: abCd acbD abcd_ abdc_
+ * Pdksh currently goes with GNU behavior since I believe this is the
+ * most common version of emacs, unless in gmacs mode, in which case
+ * it does the AT&T ksh gmacs mode.
+ * This should really be broken up into 3 functions so users can bind
+ * to the one they want.
+ */
+ if (xcp == xbuf) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ } else if (xcp == xep || Flag(FGMACS)) {
+ if (xcp - xbuf == 1) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ /*
+ * 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) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ x_bs3(&xcp);
+ if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ utf_wctomb(xcp, tmpa);
+ x_zotc3(&xcp);
+ utf_wctomb(xcp, tmpb);
+ x_zotc3(&xcp);
+ } else {
+ /*
+ * GNU emacs style: Swap the characters before and under the
+ * cursor, move cursor position along one.
+ */
+ if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ x_bs3(&xcp);
+ if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ utf_wctomb(xcp, tmpa);
+ x_zotc3(&xcp);
+ utf_wctomb(xcp, tmpb);
+ x_zotc3(&xcp);
+ }
+ x_modified();
+ return (KSTD);
+}
+
+static int
+x_literal(int c MKSH_A_UNUSED)
+{
+ x_curprefix = -1;
+ return (KSTD);
+}
+
+static int
+x_meta1(int c MKSH_A_UNUSED)
+{
+ x_curprefix = 1;
+ return (KSTD);
+}
+
+static int
+x_meta2(int c MKSH_A_UNUSED)
+{
+ x_curprefix = 2;
+ return (KSTD);
+}
+
+static int
+x_meta3(int c MKSH_A_UNUSED)
+{
+ x_curprefix = 3;
+ return (KSTD);
+}
+
+static int
+x_kill(int c MKSH_A_UNUSED)
+{
+ size_t col = xcp - xbuf;
+ size_t lastcol = xep - xbuf;
+ size_t ndel, narg;
+
+ if (x_arg_defaulted || (narg = x_arg) > lastcol)
+ narg = lastcol;
+ if (narg < col) {
+ x_goto(xbuf + narg);
+ ndel = col - narg;
+ } else
+ ndel = narg - col;
+ x_delete(x_nb2nc(ndel), true);
+ return (KSTD);
+}
+
+static void
+x_push(size_t nchars)
+{
+ afree(killstack[killsp], AEDIT);
+ strndupx(killstack[killsp], xcp, nchars, AEDIT);
+ killsp = (killsp + 1) % KILLSIZE;
+}
+
+static int
+x_yank(int c MKSH_A_UNUSED)
+{
+ if (killsp == 0)
+ killtp = KILLSIZE;
+ else
+ killtp = killsp;
+ killtp--;
+ if (killstack[killtp] == 0) {
+ x_e_puts("\nnothing to yank");
+ x_redraw('\n');
+ return (KSTD);
+ }
+ xmp = xcp;
+ x_ins(killstack[killtp]);
+ return (KSTD);
+}
+
+static int
+x_meta_yank(int c MKSH_A_UNUSED)
+{
+ size_t len;
+
+ if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
+ killstack[killtp] == 0) {
+ killtp = killsp;
+ x_e_puts("\nyank something first");
+ x_redraw('\n');
+ return (KSTD);
+ }
+ len = strlen(killstack[killtp]);
+ x_goto(xcp - len);
+ x_delete(x_nb2nc(len), false);
+ do {
+ if (killtp == 0)
+ killtp = KILLSIZE - 1;
+ else
+ killtp--;
+ } while (killstack[killtp] == 0);
+ x_ins(killstack[killtp]);
+ return (KSTD);
+}
+
+/* fake receiving an interrupt */
+static void
+x_intr(int signo, int c)
+{
+ x_vi_zotc(c);
+ *xep = '\0';
+ strip_nuls(xbuf, xep - xbuf);
+ if (*xbuf)
+ histsave(&source->line, xbuf, HIST_STORE, true);
+ xlp = xep = xcp = xbp = xbuf;
+ xlp_valid = true;
+ *xcp = 0;
+ x_modified();
+ x_flush();
+ trapsig(signo);
+ x_mode(false);
+ unwind(LSHELL);
+}
+
+static int
+x_abort(int c MKSH_A_UNUSED)
+{
+ return (KINTR);
+}
+
+static int
+x_error(int c MKSH_A_UNUSED)
+{
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+/* special VT100 style key sequence hack */
+static int
+x_vt_hack(int c)
+{
+ /* we only support PF2-'1' for now */
+ if (c != (2 << 8 | '1'))
+ return (x_error(c));
+
+ /* what's the next character? */
+ switch ((c = x_e_getc())) {
+ case '~':
+ x_arg = 1;
+ x_arg_defaulted = true;
+ return (x_mv_beg(0));
+ case ';':
+ /* "interesting" sequence detected */
+ break;
+ default:
+ goto unwind_err;
+ }
+
+ /* XXX x_e_ungetc is one-octet only */
+ if ((c = x_e_getc()) != '5' && c != '3')
+ goto unwind_err;
+
+ /*-
+ * At this point, we have read the following octets so far:
+ * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
+ * - 1 (vt_hack)
+ * - ;
+ * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
+ * We can now accept one more octet designating the key.
+ */
+
+ switch ((c = x_e_getc())) {
+ case 'C':
+ return (x_mv_fword(c));
+ case 'D':
+ return (x_mv_bword(c));
+ }
+
+ unwind_err:
+ x_e_ungetc(c);
+ return (x_error(c));
+}
+#endif
+
+int
+x_bind_check(void)
+{
+ return (x_tab == NULL);
+}
+
+static XString x_bind_show_xs;
+static char *x_bind_show_xp;
+
+static void
+x_bind_show_ch(unsigned char ch)
+{
+ Xcheck(x_bind_show_xs, x_bind_show_xp);
+ switch (ch) {
+ case ORD('^'):
+ case ORD('\\'):
+ case ORD('='):
+ *x_bind_show_xp++ = '\\';
+ *x_bind_show_xp++ = ch;
+ break;
+ default:
+ if (ksh_isctrl(ch)) {
+ *x_bind_show_xp++ = '^';
+ *x_bind_show_xp++ = ksh_unctrl(ch);
+ } else
+ *x_bind_show_xp++ = ch;
+ break;
+ }
+}
+
+static void
+x_bind_showone(int prefix, int key)
+{
+ unsigned char f = XFUNC_VALUE(x_tab[prefix][key]);
+
+ if (!x_bind_show_xs.areap)
+ XinitN(x_bind_show_xs, 16, AEDIT);
+
+ x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+ shf_puts("bind ", shl_stdout);
+#ifndef MKSH_SMALL
+ if (f == XFUNC_ins_string)
+ shf_puts("-m ", shl_stdout);
+#endif
+ switch (prefix) {
+ case 1:
+ x_bind_show_ch(CTRL_BO);
+ break;
+ case 2:
+ x_bind_show_ch(CTRL_X);
+ break;
+ case 3:
+ x_bind_show_ch(0);
+ break;
+ }
+ x_bind_show_ch(key);
+#ifndef MKSH_SMALL
+ if (x_tab[prefix][key] & 0x80)
+ *x_bind_show_xp++ = '~';
+#endif
+ *x_bind_show_xp = '\0';
+ x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+ print_value_quoted(shl_stdout, x_bind_show_xp);
+ shf_putc('=', shl_stdout);
+#ifndef MKSH_SMALL
+ if (f == XFUNC_ins_string) {
+ const unsigned char *cp = (const void *)x_atab[prefix][key];
+ unsigned char c;
+
+ while ((c = *cp++))
+ x_bind_show_ch(c);
+ *x_bind_show_xp = '\0';
+ x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+ print_value_quoted(shl_stdout, x_bind_show_xp);
+ } else
+#endif
+ shf_puts(x_ftab[f].xf_name, shl_stdout);
+ shf_putc('\n', shl_stdout);
+}
+
+int
+x_bind_list(void)
+{
+ size_t f;
+
+ for (f = 0; f < NELEM(x_ftab); f++)
+ if (!(x_ftab[f].xf_flags & XF_NOBIND))
+ shprintf(Tf_sN, x_ftab[f].xf_name);
+ return (0);
+}
+
+int
+x_bind_showall(void)
+{
+ int prefix, key;
+
+ for (prefix = 0; prefix < X_NTABS; prefix++)
+ for (key = 0; key < X_TABSZ; key++)
+ switch (XFUNC_VALUE(x_tab[prefix][key])) {
+ case XFUNC_error: /* unset */
+ case XFUNC_insert: /* auto-insert */
+ break;
+ default:
+ x_bind_showone(prefix, key);
+ break;
+ }
+ return (0);
+}
+
+static unsigned int
+x_bind_getc(const char **ccpp)
+{
+ unsigned int ch, ec;
+
+ if ((ch = ord(**ccpp)))
+ ++(*ccpp);
+ switch (ch) {
+ case ORD('^'):
+ ch = ksh_toctrl(**ccpp) | 0x100U;
+ if (**ccpp)
+ ++(*ccpp);
+ break;
+ case ORD('\\'):
+ switch ((ec = ord(**ccpp))) {
+ case ORD('^'):
+ case ORD('\\'):
+ case ORD('='):
+ ch = ec | 0x100U;
+ ++(*ccpp);
+ break;
+ }
+ break;
+ }
+ return (ch);
+}
+
+int
+x_bind(const char *s SMALLP(bool macro))
+{
+ const char *ccp = s;
+ int prefix, key;
+ unsigned int c;
+#ifndef MKSH_SMALL
+ bool hastilde = false;
+ char *ms = NULL;
+#endif
+
+ prefix = 0;
+ c = x_bind_getc(&ccp);
+ if (!c || c == ORD('=')) {
+ bi_errorf("no key to bind");
+ return (1);
+ }
+ key = c & 0xFF;
+ while ((c = x_bind_getc(&ccp)) != ORD('=')) {
+ if (!c) {
+ x_bind_showone(prefix, key);
+ return (0);
+ }
+ switch (XFUNC_VALUE(x_tab[prefix][key])) {
+ case XFUNC_meta1:
+ prefix = 1;
+ if (0)
+ /* FALLTHROUGH */
+ case XFUNC_meta2:
+ prefix = 2;
+ if (0)
+ /* FALLTHROUGH */
+ case XFUNC_meta3:
+ prefix = 3;
+ key = c & 0xFF;
+ continue;
+ }
+#ifndef MKSH_SMALL
+ if (c == ORD('~')) {
+ hastilde = true;
+ continue;
+ }
+#endif
+ bi_errorf("too long key sequence: %s", s);
+ return (-1);
+ }
+
+#ifndef MKSH_SMALL
+ if (macro) {
+ char *cp;
+
+ cp = ms = alloc(strlen(ccp) + 1, AEDIT);
+ while ((c = x_bind_getc(&ccp)))
+ *cp++ = c;
+ *cp = '\0';
+ c = XFUNC_ins_string;
+ } else
+#endif
+ if (!*ccp) {
+ c = XFUNC_insert;
+ } else {
+ for (c = 0; c < NELEM(x_ftab); ++c)
+ if (!strcmp(x_ftab[c].xf_name, ccp))
+ break;
+ if (c == NELEM(x_ftab) || x_ftab[c].xf_flags & XF_NOBIND) {
+ bi_errorf("%s: no such editing command", ccp);
+ return (1);
+ }
+ }
+
+#ifndef MKSH_SMALL
+ if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string)
+ afree(x_atab[prefix][key], AEDIT);
+ x_atab[prefix][key] = ms;
+ if (hastilde)
+ c |= 0x80U;
+#endif
+ x_tab[prefix][key] = c;
+
+ /* track what the user has bound, so x_mode(true) won't toast things */
+ if (c == XFUNC_insert)
+ x_bound[(prefix * X_TABSZ + key) / 8] &=
+ ~(1 << ((prefix * X_TABSZ + key) % 8));
+ else
+ x_bound[(prefix * X_TABSZ + key) / 8] |=
+ (1 << ((prefix * X_TABSZ + key) % 8));
+
+ return (0);
+}
+
+static void
+bind_if_not_bound(int p, int k, int func)
+{
+ int t;
+
+ /*
+ * Has user already bound this key?
+ * If so, do not override it.
+ */
+ t = p * X_TABSZ + k;
+ if (x_bound[t >> 3] & (1 << (t & 7)))
+ return;
+
+ x_tab[p][k] = func;
+}
+
+static int
+x_set_mark(int c MKSH_A_UNUSED)
+{
+ xmp = xcp;
+ return (KSTD);
+}
+
+static int
+x_kill_region(int c MKSH_A_UNUSED)
+{
+ size_t rsize;
+ char *xr;
+
+ if (xmp == NULL) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ if (xmp > xcp) {
+ rsize = xmp - xcp;
+ xr = xcp;
+ } else {
+ rsize = xcp - xmp;
+ xr = xmp;
+ }
+ x_goto(xr);
+ x_delete(x_nb2nc(rsize), true);
+ xmp = xr;
+ return (KSTD);
+}
+
+static int
+x_xchg_point_mark(int c MKSH_A_UNUSED)
+{
+ char *tmp;
+
+ if (xmp == NULL) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ tmp = xmp;
+ xmp = xcp;
+ x_goto(tmp);
+ return (KSTD);
+}
+
+static int
+x_noop(int c MKSH_A_UNUSED)
+{
+ return (KSTD);
+}
+
+/*
+ * File/command name completion routines
+ */
+static int
+x_comp_comm(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_COMMAND, CT_COMPLETE);
+ return (KSTD);
+}
+
+static int
+x_list_comm(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_COMMAND, CT_LIST);
+ return (KSTD);
+}
+
+static int
+x_complete(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
+ return (KSTD);
+}
+
+static int
+x_enumerate(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_COMMAND_FILE, CT_LIST);
+ return (KSTD);
+}
+
+static int
+x_comp_file(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_FILE, CT_COMPLETE);
+ return (KSTD);
+}
+
+static int
+x_list_file(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_FILE, CT_LIST);
+ return (KSTD);
+}
+
+static int
+x_comp_list(int c MKSH_A_UNUSED)
+{
+ do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
+ return (KSTD);
+}
+
+static int
+x_expand(int c MKSH_A_UNUSED)
+{
+ char **words;
+ int start, end, nwords, i;
+
+ i = XCF_FILE;
+ nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words);
+
+ if (nwords == 0) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ x_goto(xbuf + start);
+ x_delete(x_nb2nc(end - start), false);
+
+ i = 0;
+ while (i < nwords) {
+ if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
+ (++i < nwords && x_ins(T1space) < 0)) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ }
+ x_adjust();
+
+ return (KSTD);
+}
+
+static void
+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 completed;
+
+ nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words);
+ /* no match */
+ if (nwords == 0) {
+ x_e_putc2(KSH_BEL);
+ return;
+ }
+ if (type == CT_LIST) {
+ x_print_expansions(nwords, words,
+ tobool(flags & XCF_IS_COMMAND));
+ x_redraw(0);
+ x_free_words(nwords, words);
+ return;
+ }
+ olen = end - start;
+ nlen = x_longest_prefix(nwords, words);
+ if (nwords == 1) {
+ /*
+ * always complete single matches;
+ * any expansion of parameter substitution
+ * is always at most one result, too
+ */
+ completed = true;
+ } else {
+ char *unescaped;
+
+ /* make a copy of the original string part */
+ strndupx(unescaped, xbuf + start, olen, ATEMP);
+
+ /* expand any tilde and unescape the string for comparison */
+ unescaped = x_glob_hlp_tilde_and_rem_qchar(unescaped, true);
+
+ /*
+ * match iff entire original string is part of the
+ * longest prefix, implying the latter is at least
+ * the same size (after unescaping)
+ */
+ completed = !strncmp(words[0], unescaped, strlen(unescaped));
+
+ afree(unescaped, ATEMP);
+ }
+ if (type == CT_COMPLIST && nwords > 1) {
+ /*
+ * print expansions, since we didn't get back
+ * just a single match
+ */
+ x_print_expansions(nwords, words,
+ tobool(flags & XCF_IS_COMMAND));
+ }
+ if (completed) {
+ /* expand on the command line */
+ xmp = NULL;
+ xcp = xbuf + start;
+ xep -= olen;
+ memmove(xcp, xcp + olen, xep - xcp + 1);
+ x_escape(words[0], nlen, x_do_ins);
+ }
+ x_adjust();
+ /*
+ * append a space if this is a single non-directory match
+ * and not a parameter or homedir substitution
+ */
+ if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1]) &&
+ !(flags & XCF_IS_NOSPACE)) {
+ x_ins(T1space);
+ }
+
+ x_free_words(nwords, words);
+}
+
+/*-
+ * NAME:
+ * x_adjust - redraw the line adjusting starting point etc.
+ *
+ * DESCRIPTION:
+ * This function is called when we have exceeded the bounds
+ * of the edit window. It increments x_adj_done so that
+ * functions like x_ins and x_delete know that we have been
+ * called and can skip the x_bs() stuff which has already
+ * been done by x_redraw.
+ *
+ * RETURN VALUE:
+ * None
+ */
+static void
+x_adjust(void)
+{
+ int col_left, n;
+
+ /* flag the fact that we were called */
+ x_adj_done++;
+
+ /*
+ * calculate the amount of columns we need to "go back"
+ * from xcp to set xbp to (but never < xbuf) to 2/3 of
+ * the display width; take care of pwidth though
+ */
+ if ((col_left = xx_cols * 2 / 3) < MIN_EDIT_SPACE) {
+ /*
+ * cowardly refuse to do anything
+ * if the available space is too small;
+ * fall back to dumb pdksh code
+ */
+ if ((xbp = xcp - (x_displen / 2)) < xbuf)
+ xbp = xbuf;
+ /* elide UTF-8 fixup as penalty */
+ goto x_adjust_out;
+ }
+
+ /* fix up xbp to just past a character end first */
+ xbp = xcp >= xep ? xep : x_bs0(xcp, xbuf);
+ /* walk backwards */
+ while (xbp > xbuf && col_left > 0) {
+ xbp = x_bs0(xbp - 1, xbuf);
+ col_left -= (n = x_size2(xbp, NULL));
+ }
+ /* check if we hit the prompt */
+ if (xbp == xbuf && xcp != xbuf && col_left >= 0 && col_left < pwidth) {
+ /* so we did; force scrolling occurs */
+ xbp += utf_ptradj(xbp);
+ }
+
+ x_adjust_out:
+ xlp_valid = false;
+ x_redraw('\r');
+ x_flush();
+}
+
+static void
+x_e_ungetc(int c)
+{
+ unget_char = c < 0 ? -1 : (c & 255);
+}
+
+static int
+x_e_getc(void)
+{
+ int c;
+
+ if (unget_char >= 0) {
+ c = unget_char;
+ unget_char = -1;
+ return (c);
+ }
+
+#ifndef MKSH_SMALL
+ if (macroptr) {
+ if ((c = (unsigned char)*macroptr++))
+ return (c);
+ macroptr = NULL;
+ }
+#endif
+
+ return (x_getc());
+}
+
+static void
+x_e_putc2(int c)
+{
+ int width = 1;
+
+ if (ctype(c, C_CR | C_LF))
+ x_col = 0;
+ if (x_col < xx_cols) {
+#ifndef MKSH_EBCDIC
+ if (UTFMODE && (c > 0x7F)) {
+ char utf_tmp[3];
+ size_t x;
+
+ if (c < 0xA0)
+ c = 0xFFFD;
+ x = utf_wctomb(utf_tmp, c);
+ x_putc(utf_tmp[0]);
+ if (x > 1)
+ x_putc(utf_tmp[1]);
+ if (x > 2)
+ x_putc(utf_tmp[2]);
+ width = utf_wcwidth(c);
+ } else
+#endif
+ x_putc(c);
+ switch (c) {
+ case KSH_BEL:
+ break;
+ case '\r':
+ case '\n':
+ break;
+ case '\b':
+ x_col--;
+ break;
+ default:
+ x_col += width;
+ break;
+ }
+ }
+ if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+ x_adjust();
+}
+
+static void
+x_e_putc3(const char **cp)
+{
+ int width = 1, c = **(const unsigned char **)cp;
+
+ if (ctype(c, C_CR | C_LF))
+ x_col = 0;
+ if (x_col < xx_cols) {
+ if (UTFMODE && (c > 0x7F)) {
+ char *cp2;
+
+ width = utf_widthadj(*cp, (const char **)&cp2);
+ if (cp2 == *cp + 1) {
+ (*cp)++;
+#ifdef MKSH_EBCDIC
+ x_putc(asc2rtt(0xEF));
+ x_putc(asc2rtt(0xBF));
+ x_putc(asc2rtt(0xBD));
+#else
+ shf_puts("\xEF\xBF\xBD", shl_out);
+#endif
+ } else
+ while (*cp < cp2)
+ x_putcf(*(*cp)++);
+ } else {
+ (*cp)++;
+ x_putc(c);
+ }
+ switch (c) {
+ case KSH_BEL:
+ break;
+ case '\r':
+ case '\n':
+ break;
+ case '\b':
+ x_col--;
+ break;
+ default:
+ x_col += width;
+ break;
+ }
+ }
+ if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+ x_adjust();
+}
+
+static void
+x_e_puts(const char *s)
+{
+ int adj = x_adj_done;
+
+ while (*s && adj == x_adj_done)
+ x_e_putc3(&s);
+}
+
+/*-
+ * NAME:
+ * x_set_arg - set an arg value for next function
+ *
+ * DESCRIPTION:
+ * This is a simple implementation of M-[0-9].
+ *
+ * RETURN VALUE:
+ * KSTD
+ */
+static int
+x_set_arg(int c)
+{
+ unsigned int n = 0;
+ bool first = true;
+
+ /* strip command prefix */
+ c &= 255;
+ while (c >= 0 && ctype(c, C_DIGIT)) {
+ n = n * 10 + ksh_numdig(c);
+ 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(KSH_BEL);
+ x_arg = 1;
+ x_arg_defaulted = true;
+ } else {
+ x_e_ungetc(c);
+ x_arg = n;
+ x_arg_defaulted = false;
+ }
+ return (KSTD);
+}
+
+/* Comment or uncomment the current line. */
+static int
+x_comment(int c MKSH_A_UNUSED)
+{
+ ssize_t len = xep - xbuf;
+ int ret = x_do_comment(xbuf, xend - xbuf, &len);
+
+ if (ret < 0)
+ x_e_putc2(KSH_BEL);
+ else {
+ x_modified();
+ xep = xbuf + len;
+ *xep = '\0';
+ xcp = xbp = xbuf;
+ x_redraw('\r');
+ if (ret > 0)
+ return (x_newline('\n'));
+ }
+ return (KSTD);
+}
+
+static int
+x_version(int c MKSH_A_UNUSED)
+{
+ char *o_xbuf = xbuf, *o_xend = xend;
+ char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
+ char *v;
+
+ strdupx(v, KSH_VERSION, ATEMP);
+
+ xbuf = xbp = xcp = v;
+ xend = xep = strnul(v);
+ x_redraw('\r');
+ x_flush();
+
+ c = x_e_getc();
+ xbuf = o_xbuf;
+ xend = o_xend;
+ xbp = o_xbp;
+ xep = o_xep;
+ xcp = o_xcp;
+ x_redraw('\r');
+
+ if (c < 0)
+ return (KSTD);
+ /* This is what AT&T ksh seems to do... Very bizarre */
+ if (c != ' ')
+ x_e_ungetc(c);
+
+ afree(v, ATEMP);
+ return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+static int
+x_edit_line(int c MKSH_A_UNUSED)
+{
+ if (x_arg_defaulted) {
+ if (xep == xbuf) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ if (modified) {
+ *xep = '\0';
+ histsave(&source->line, xbuf, HIST_STORE, true);
+ x_arg = 0;
+ } else
+ x_arg = source->line - (histptr - x_histp);
+ }
+ if (x_arg)
+ shf_snprintf(xbuf, xend - xbuf, Tf_sd,
+ "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
+ else
+ strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
+ xep = strnul(xbuf);
+ return (x_newline('\n'));
+}
+#endif
+
+/*-
+ * NAME:
+ * x_prev_histword - recover word from prev command
+ *
+ * DESCRIPTION:
+ * This function recovers the last word from the previous
+ * command and inserts it into the current edit line. If a
+ * numeric arg is supplied then the n'th word from the
+ * start of the previous command is used.
+ * As a side effect, trashes the mark in order to achieve
+ * being called in a repeatable fashion.
+ *
+ * Bound to M-.
+ *
+ * RETURN VALUE:
+ * KSTD
+ */
+static int
+x_prev_histword(int c MKSH_A_UNUSED)
+{
+ char *rcp, *cp;
+ char **xhp;
+ 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(KSH_BEL);
+ x_modified();
+ return (KSTD);
+ }
+ x_set_mark(0);
+ if ((x_arg = last_arg) == -1) {
+ /* x_arg_defaulted */
+
+ rcp = &cp[strlen(cp) - 1];
+ /*
+ * ignore white-space after the last word
+ */
+ while (rcp > cp && ctype(*rcp, C_CFS))
+ rcp--;
+ while (rcp > cp && !ctype(*rcp, C_CFS))
+ rcp--;
+ if (ctype(*rcp, C_CFS))
+ rcp++;
+ x_ins(rcp);
+ } else {
+ /* not x_arg_defaulted */
+ char ch;
+
+ rcp = cp;
+ /*
+ * ignore white-space at start of line
+ */
+ while (*rcp && ctype(*rcp, C_CFS))
+ rcp++;
+ while (x_arg-- > 0) {
+ while (*rcp && !ctype(*rcp, C_CFS))
+ rcp++;
+ while (*rcp && ctype(*rcp, C_CFS))
+ rcp++;
+ }
+ cp = rcp;
+ while (*rcp && !ctype(*rcp, C_CFS))
+ rcp++;
+ ch = *rcp;
+ *rcp = '\0';
+ x_ins(cp);
+ *rcp = ch;
+ }
+ if (!modified)
+ x_histmcp = x_histp;
+ modified = m + 1;
+ return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+/* Uppercase N(1) words */
+static int
+x_fold_upper(int c MKSH_A_UNUSED)
+{
+ return (x_fold_case('U'));
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_lower(int c MKSH_A_UNUSED)
+{
+ return (x_fold_case('L'));
+}
+
+/* Titlecase N(1) words */
+static int
+x_fold_capitalise(int c MKSH_A_UNUSED)
+{
+ return (x_fold_case('C'));
+}
+
+/*-
+ * NAME:
+ * x_fold_case - convert word to UPPER/lower/Capital case
+ *
+ * DESCRIPTION:
+ * This function is used to implement M-U/M-u, M-L/M-l, M-C/M-c
+ * to UPPER CASE, lower case or Capitalise Words.
+ *
+ * RETURN VALUE:
+ * None
+ */
+static int
+x_fold_case(int c)
+{
+ char *cp = xcp;
+
+ if (cp == xep) {
+ x_e_putc2(KSH_BEL);
+ return (KSTD);
+ }
+ while (x_arg--) {
+ /*
+ * first skip over any white-space
+ */
+ while (cp != xep && ctype(*cp, C_MFS))
+ cp++;
+ /*
+ * do the first char on its own since it may be
+ * a different action than for the rest.
+ */
+ if (cp != xep) {
+ if (c == 'L')
+ /* lowercase */
+ *cp = ksh_tolower(*cp);
+ else
+ /* uppercase, capitalise */
+ *cp = ksh_toupper(*cp);
+ cp++;
+ }
+ /*
+ * now for the rest of the word
+ */
+ while (cp != xep && !ctype(*cp, C_MFS)) {
+ if (c == 'U')
+ /* uppercase */
+ *cp = ksh_toupper(*cp);
+ else
+ /* lowercase, capitalise */
+ *cp = ksh_tolower(*cp);
+ cp++;
+ }
+ }
+ x_goto(cp);
+ x_modified();
+ return (KSTD);
+}
+#endif
+
+/*-
+ * NAME:
+ * x_lastcp - last visible char
+ *
+ * DESCRIPTION:
+ * This function returns a pointer to that char in the
+ * edit buffer that will be the last displayed on the
+ * screen.
+ */
+static char *
+x_lastcp(void)
+{
+ if (!xlp_valid) {
+ int i = 0, j;
+ char *xlp2;
+
+ xlp = xbp;
+ while (xlp < xep) {
+ j = x_size2(xlp, &xlp2);
+ if ((i + j) > x_displen)
+ break;
+ i += j;
+ xlp = xlp2;
+ }
+ }
+ xlp_valid = true;
+ return (xlp);
+}
+
+/* correctly position the cursor on the screen from end of visible area */
+static void
+x_lastpos(void)
+{
+ char *cp = x_lastcp();
+
+ while (cp > xcp)
+ x_bs3(&cp);
+}
+
+static void
+x_mode(bool onoff)
+{
+ static bool x_cur_mode;
+
+ if (x_cur_mode == onoff)
+ return;
+ x_cur_mode = onoff;
+
+ if (onoff) {
+ x_mkraw(tty_fd, NULL, false);
+
+ edchars.erase = toedchar(tty_state.c_cc[VERASE]);
+ edchars.kill = toedchar(tty_state.c_cc[VKILL]);
+ edchars.intr = toedchar(tty_state.c_cc[VINTR]);
+ edchars.quit = toedchar(tty_state.c_cc[VQUIT]);
+ edchars.eof = toedchar(tty_state.c_cc[VEOF]);
+#ifdef VWERASE
+ edchars.werase = toedchar(tty_state.c_cc[VWERASE]);
+#else
+ edchars.werase = 0;
+#endif
+
+ if (!edchars.erase)
+ edchars.erase = CTRL_H;
+ if (!edchars.kill)
+ edchars.kill = CTRL_U;
+ if (!edchars.intr)
+ edchars.intr = CTRL_C;
+ if (!edchars.quit)
+ edchars.quit = CTRL_BK;
+ if (!edchars.eof)
+ edchars.eof = CTRL_D;
+ if (!edchars.werase)
+ edchars.werase = CTRL_W;
+
+ if (isedchar(edchars.erase)) {
+ bind_if_not_bound(0, edchars.erase, XFUNC_del_back);
+ bind_if_not_bound(1, edchars.erase, XFUNC_del_bword);
+ }
+ if (isedchar(edchars.kill))
+ bind_if_not_bound(0, edchars.kill, XFUNC_del_line);
+ if (isedchar(edchars.werase))
+ bind_if_not_bound(0, edchars.werase, XFUNC_del_bword);
+ if (isedchar(edchars.intr))
+ bind_if_not_bound(0, edchars.intr, XFUNC_abort);
+ if (isedchar(edchars.quit))
+ bind_if_not_bound(0, edchars.quit, XFUNC_noop);
+ } else
+ mksh_tcset(tty_fd, &tty_state);
+}
+
+#if !MKSH_S_NOVI
+/* +++ vi editing mode +++ */
+
+struct edstate {
+ char *cbuf;
+ ssize_t winleft;
+ ssize_t cbufsize;
+ ssize_t linelen;
+ ssize_t cursor;
+};
+
+static int vi_hook(int);
+static int nextstate(int);
+static int vi_insert(int);
+static int vi_cmd(int, const char *);
+static int domove(int, const char *, int);
+static int domovebeg(void);
+static int redo_insert(int);
+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 *, ssize_t, bool);
+static void del_range(int, int);
+static int findch(int, int, bool, bool) MKSH_A_PURE;
+static int forwword(int);
+static int backword(int);
+static int endword(int);
+static int Forwword(int);
+static int Backword(int);
+static int Endword(int);
+static int grabhist(int, int);
+static int grabsearch(int, int, int, const char *);
+static void redraw_line(bool);
+static void refresh(int);
+static int outofwin(void);
+static void rewindow(void);
+static int newcol(unsigned char, int);
+static void display(char *, char *, int);
+static void ed_mov_opt(int, char *);
+static int expand_word(int);
+static int complete_word(int, int);
+static int print_expansions(struct edstate *, int);
+static void vi_error(void);
+static void vi_macro_reset(void);
+static int x_vi_putbuf(const char *, size_t);
+#define char_len(c) (ksh_isctrl(c) ? 2 : 1)
+
+#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[rtt2asc(c) & 0x7F] & vB)
+#define is_cmd(c) (classify[rtt2asc(c) & 0x7F] & (vM | vE | vC | vU))
+#define is_move(c) (classify[rtt2asc(c) & 0x7F] & vM)
+#define is_extend(c) (classify[rtt2asc(c) & 0x7F] & vE)
+#define is_long(c) (classify[rtt2asc(c) & 0x7F] & vX)
+#define is_undoable(c) (!(classify[rtt2asc(c) & 0x7F] & vU))
+#define is_srch(c) (classify[rtt2asc(c) & 0x7F] & vS)
+#define is_zerocount(c) (classify[rtt2asc(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 */
+ vB, 0, 0, 0, 0, vC|vU, vC|vZ, 0,
+/* 1 ^H ^I ^J ^K ^L ^M ^N ^O */
+ vM, vC|vZ, 0, 0, vC|vU, 0, vC, 0,
+/* 2 ^P ^Q ^R ^S ^T ^U ^V ^W */
+ vC, 0, vC|vU, 0, 0, 0, vC, 0,
+/* 3 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
+ vC, 0, 0, vC|vZ, 0, 0, 0, 0,
+/* 4 <space> ! " # $ % & ' */
+ vM, 0, 0, vC, vM, vM, 0, 0,
+/* 5 ( ) * + , - . / */
+ 0, 0, vC, vC, vM, vC, 0, vC|vS,
+/* 6 0 1 2 3 4 5 6 7 */
+ vM, 0, 0, 0, 0, 0, 0, 0,
+/* 7 8 9 : ; < = > ? */
+ 0, 0, 0, vM, 0, vC, 0, vC|vS,
+/* 8 @ A B C D E F G */
+ vC|vX, vC, vM, vC, vC, vM, vM|vX, vC|vU|vZ,
+/* 9 H I J K L M N O */
+ 0, vC, 0, 0, 0, 0, vC|vU, vU,
+/* A P Q R S T U V W */
+ vC, 0, vC, vC, vM|vX, vC, 0, vM,
+/* B X Y Z [ \ ] ^ _ */
+ vC, vC|vU, 0, vU, vC|vZ, 0, vM, vC|vZ,
+/* C ` a b c d e f g */
+ 0, vC, vM, vE, vE, vM, vM|vX, vC|vZ,
+/* D h i j k l m n o */
+ vM, vC, vC|vU, vC|vU, vM, 0, vC|vU, 0,
+/* E p q r s t u v w */
+ vC, 0, vX, vC, vM|vX, vC|vU, vC|vU|vZ, vM,
+/* F x y z { | } ~ ^? */
+ vC, vE|vU, 0, 0, vM|vZ, 0, vC, 0
+};
+
+#define MAXVICMD 3
+#define SRCHLEN 40
+
+#define INSERT 1
+#define REPLACE 2
+
+#define VNORMAL 0 /* command, insert or replace mode */
+#define VARG1 1 /* digit prefix (first, eg, 5l) */
+#define VEXTCMD 2 /* cmd + movement (eg, cl) */
+#define VARG2 3 /* digit prefix (second, eg, 2c3l) */
+#define VXCH 4 /* f, F, t, T, @ */
+#define VFAIL 5 /* bad command */
+#define VCMD 6 /* single char command (eg, X) */
+#define VREDO 7 /* . */
+#define VLIT 8 /* ^V */
+#define VSEARCH 9 /* /, ? */
+#define VVERSION 10 /* <ESC> ^V */
+#define VPREFIX2 11 /* ^[[ and ^[O in insert mode */
+
+static struct edstate *save_edstate(struct edstate *old);
+static void restore_edstate(struct edstate *old, struct edstate *news);
+static void free_edstate(struct edstate *old);
+
+static struct edstate ebuf;
+static struct edstate undobuf;
+
+static struct edstate *vs; /* current Vi editing mode state */
+static struct edstate *undo;
+
+static char *ibuf; /* input buffer */
+static bool first_insert; /* set when starting in insert mode */
+static int saved_inslen; /* saved inslen for first insert */
+static int inslen; /* length of input buffer */
+static int srchlen; /* length of current search pattern */
+static char *ybuf; /* yank buffer */
+static int yanklen; /* length of yank buffer */
+static uint8_t fsavecmd = ORD(' '); /* last find command */
+static int fsavech; /* character to find */
+static char lastcmd[MAXVICMD]; /* last non-move command */
+static int lastac; /* argcnt for lastcmd */
+static uint8_t lastsearch = ORD(' '); /* last search command */
+static char srchpat[SRCHLEN]; /* last search pattern */
+static int insert; /* <>0 in insert mode */
+static int hnum; /* position in history */
+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.
+ * 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
+ * be detected.
+ */
+struct macro_state {
+ unsigned char *p; /* current position in buf */
+ unsigned char *buf; /* pointer to macro(s) being expanded */
+ size_t len; /* how much data in buffer */
+};
+static struct macro_state macro;
+
+/* last input was expanded */
+static enum expand_mode {
+ NONE = 0, EXPAND, COMPLETE, PRINT
+} expanded;
+
+static int
+x_vi(char *buf)
+{
+ int c;
+
+ state = VNORMAL;
+ ohnum = hnum = hlast = histnum(-1) + 1;
+ insert = INSERT;
+ saved_inslen = inslen;
+ first_insert = true;
+ inslen = 0;
+ vi_macro_reset();
+
+ ebuf.cbuf = buf;
+ if (undobuf.cbuf == NULL) {
+ ibuf = alloc(LINE, AEDIT);
+ ybuf = alloc(LINE, AEDIT);
+ undobuf.cbuf = alloc(LINE, AEDIT);
+ }
+ undobuf.cbufsize = ebuf.cbufsize = LINE;
+ undobuf.linelen = ebuf.linelen = 0;
+ undobuf.cursor = ebuf.cursor = 0;
+ undobuf.winleft = ebuf.winleft = 0;
+ vs = &ebuf;
+ undo = &undobuf;
+
+ x_init_prompt(true);
+ x_col = pwidth;
+
+ if (wbuf_len != x_cols - 3 && ((wbuf_len = x_cols - 3))) {
+ wbuf[0] = aresize(wbuf[0], wbuf_len, AEDIT);
+ wbuf[1] = aresize(wbuf[1], wbuf_len, AEDIT);
+ }
+ if (wbuf_len) {
+ memset(wbuf[0], ' ', wbuf_len);
+ memset(wbuf[1], ' ', wbuf_len);
+ }
+ winwidth = x_cols - pwidth - 3;
+ win = 0;
+ morec = ' ';
+ lastref = 1;
+ holdlen = 0;
+
+ editmode = 2;
+ x_flush();
+ while (/* CONSTCOND */ 1) {
+ if (macro.p) {
+ c = (unsigned char)*macro.p++;
+ /* end of current macro? */
+ if (!c) {
+ /* more macros left to finish? */
+ if (*macro.p++)
+ continue;
+ /* must be the end of all the macros */
+ vi_macro_reset();
+ c = x_getc();
+ }
+ } else
+ c = x_getc();
+
+ if (c == -1)
+ break;
+ if (state != VLIT) {
+ if (isched(c, edchars.intr) ||
+ isched(c, edchars.quit)) {
+ /* shove input buffer away */
+ xbuf = ebuf.cbuf;
+ xep = xbuf;
+ if (ebuf.linelen > 0)
+ xep += ebuf.linelen;
+ /* pretend we got an interrupt */
+ x_intr(isched(c, edchars.intr) ?
+ SIGINT : SIGQUIT, c);
+ } else if (isched(c, edchars.eof) &&
+ state != VVERSION) {
+ if (vs->linelen == 0) {
+ x_vi_zotc(c);
+ c = -1;
+ break;
+ }
+ continue;
+ }
+ }
+ if (vi_hook(c))
+ break;
+ x_flush();
+ }
+
+ x_putc('\r');
+ x_putc('\n');
+ x_flush();
+
+ if (c == -1 || (ssize_t)LINE <= vs->linelen)
+ return (-1);
+
+ if (vs->cbuf != buf)
+ memcpy(buf, vs->cbuf, vs->linelen);
+
+ buf[vs->linelen++] = '\n';
+
+ return (vs->linelen);
+}
+
+static int
+vi_hook(int ch)
+{
+ static char curcmd[MAXVICMD], locpat[SRCHLEN];
+ static int cmdlen, argc1, argc2;
+
+ switch (state) {
+
+ case VNORMAL:
+ /* PC scancodes */
+ if (!ch) {
+ cmdlen = 0;
+ switch (ch = x_getc()) {
+ case 71: ch = ORD('0'); goto pseudo_vi_command;
+ case 72: ch = ORD('k'); goto pseudo_vi_command;
+ case 73: ch = ORD('A'); goto vi_xfunc_search;
+ case 75: ch = ORD('h'); goto pseudo_vi_command;
+ case 77: ch = ORD('l'); goto pseudo_vi_command;
+ case 79: ch = ORD('$'); goto pseudo_vi_command;
+ case 80: ch = ORD('j'); goto pseudo_vi_command;
+ case 81: ch = ORD('B'); goto vi_xfunc_search;
+ case 83: ch = ORD('x'); goto pseudo_vi_command;
+ default: ch = 0; goto vi_insert_failed;
+ }
+ }
+ if (insert != 0) {
+ if (ch == CTRL_V) {
+ state = VLIT;
+ ch = ORD('^');
+ }
+ switch (vi_insert(ch)) {
+ case -1:
+ vi_insert_failed:
+ vi_error();
+ state = VNORMAL;
+ break;
+ case 0:
+ if (state == VLIT) {
+ vs->cursor--;
+ refresh(0);
+ } else
+ refresh(insert != 0);
+ break;
+ case 1:
+ return (1);
+ }
+ } else {
+ if (ctype(ch, C_CR | C_LF))
+ return (1);
+ cmdlen = 0;
+ argc1 = 0;
+ if (ctype(ch, C_DIGIT) && ord(ch) != ORD('0')) {
+ argc1 = ksh_numdig(ch);
+ state = VARG1;
+ } else {
+ pseudo_vi_command:
+ curcmd[cmdlen++] = ch;
+ state = nextstate(ch);
+ if (state == VSEARCH) {
+ save_cbuf();
+ vs->cursor = 0;
+ vs->linelen = 0;
+ if (putbuf(ord(ch) == ORD('/') ?
+ "/" : "?", 1, false) != 0)
+ return (-1);
+ refresh(0);
+ }
+ if (state == VVERSION) {
+ save_cbuf();
+ vs->cursor = 0;
+ vs->linelen = 0;
+ putbuf(KSH_VERSION,
+ strlen(KSH_VERSION), false);
+ refresh(0);
+ }
+ }
+ }
+ break;
+
+ case VLIT:
+ if (is_bad(ch)) {
+ del_range(vs->cursor, vs->cursor + 1);
+ vi_error();
+ } else
+ vs->cbuf[vs->cursor++] = ch;
+ refresh(1);
+ state = VNORMAL;
+ break;
+
+ case VVERSION:
+ restore_cbuf();
+ state = VNORMAL;
+ refresh(0);
+ break;
+
+ case VARG1:
+ if (ctype(ch, C_DIGIT))
+ argc1 = argc1 * 10 + ksh_numdig(ch);
+ else {
+ curcmd[cmdlen++] = ch;
+ state = nextstate(ch);
+ }
+ break;
+
+ case VEXTCMD:
+ argc2 = 0;
+ if (ctype(ch, C_DIGIT) && ord(ch) != ORD('0')) {
+ argc2 = ksh_numdig(ch);
+ state = VARG2;
+ return (0);
+ } else {
+ curcmd[cmdlen++] = ch;
+ if (ch == curcmd[0])
+ state = VCMD;
+ else if (is_move(ch))
+ state = nextstate(ch);
+ else
+ state = VFAIL;
+ }
+ break;
+
+ case VARG2:
+ if (ctype(ch, C_DIGIT))
+ argc2 = argc2 * 10 + ksh_numdig(ch);
+ else {
+ if (argc1 == 0)
+ argc1 = argc2;
+ else
+ argc1 *= argc2;
+ curcmd[cmdlen++] = ch;
+ if (ch == curcmd[0])
+ state = VCMD;
+ else if (is_move(ch))
+ state = nextstate(ch);
+ else
+ state = VFAIL;
+ }
+ break;
+
+ case VXCH:
+ if (ch == CTRL_BO)
+ state = VNORMAL;
+ else {
+ curcmd[cmdlen++] = ch;
+ state = VCMD;
+ }
+ break;
+
+ case VSEARCH:
+ if (ctype(ch, C_CR | C_LF) /* || ch == CTRL_BO */ ) {
+ restore_cbuf();
+ /* Repeat last search? */
+ if (srchlen == 0) {
+ if (!srchpat[0]) {
+ vi_error();
+ state = VNORMAL;
+ refresh(0);
+ return (0);
+ }
+ } else {
+ locpat[srchlen] = '\0';
+ memcpy(srchpat, locpat, srchlen + 1);
+ }
+ state = VCMD;
+ } else if (isched(ch, edchars.erase) || ch == CTRL_H) {
+ if (srchlen != 0) {
+ srchlen--;
+ vs->linelen -= char_len(locpat[srchlen]);
+ vs->cursor = vs->linelen;
+ refresh(0);
+ return (0);
+ }
+ restore_cbuf();
+ state = VNORMAL;
+ refresh(0);
+ } else if (isched(ch, edchars.kill)) {
+ srchlen = 0;
+ vs->linelen = 1;
+ vs->cursor = 1;
+ refresh(0);
+ return (0);
+ } else if (isched(ch, edchars.werase)) {
+ unsigned int i, n;
+ struct edstate new_es, *save_es;
+
+ new_es.cursor = srchlen;
+ new_es.cbuf = locpat;
+
+ save_es = vs;
+ vs = &new_es;
+ n = backword(1);
+ vs = save_es;
+
+ i = (unsigned)srchlen;
+ while (i-- > n)
+ vs->linelen -= char_len(locpat[i]);
+ srchlen = (int)n;
+ vs->cursor = vs->linelen;
+ refresh(0);
+ return (0);
+ } else {
+ if (srchlen == SRCHLEN - 1)
+ vi_error();
+ else {
+ locpat[srchlen++] = ch;
+ if (ksh_isctrl(ch)) {
+ if ((size_t)vs->linelen + 2 >
+ (size_t)vs->cbufsize)
+ vi_error();
+ vs->cbuf[vs->linelen++] = '^';
+ vs->cbuf[vs->linelen++] = ksh_unctrl(ch);
+ } else {
+ if (vs->linelen >= vs->cbufsize)
+ vi_error();
+ vs->cbuf[vs->linelen++] = ch;
+ }
+ vs->cursor = vs->linelen;
+ refresh(0);
+ }
+ return (0);
+ }
+ break;
+
+ case VPREFIX2:
+ vi_xfunc_search:
+ state = VFAIL;
+ switch (ch) {
+ case ORD('A'):
+ case ORD('B'):
+ /* the cursor may not be at the BOL */
+ if (!vs->cursor)
+ break;
+ /* nor further in the line than we can search for */
+ if ((size_t)vs->cursor >= sizeof(srchpat) - 1)
+ vs->cursor = sizeof(srchpat) - 2;
+ /* anchor the search pattern */
+ srchpat[0] = '^';
+ /* take current line up to the cursor */
+ memcpy(srchpat + 1, vs->cbuf, vs->cursor);
+ srchpat[vs->cursor + 1] = '\0';
+ /* set a magic flag */
+ argc1 = 2 + (int)vs->cursor;
+ /* and emulate a history search */
+ /* search backwards if PgUp, forwards for PgDn */
+ lastsearch = ch == ORD('A') ? '/' : '?';
+ *curcmd = 'n';
+ goto pseudo_VCMD;
+ }
+ break;
+ }
+
+ switch (state) {
+ case VCMD:
+ pseudo_VCMD:
+ state = VNORMAL;
+ switch (vi_cmd(argc1, curcmd)) {
+ case -1:
+ vi_error();
+ refresh(0);
+ break;
+ case 0:
+ if (insert != 0)
+ inslen = 0;
+ refresh(insert != 0);
+ break;
+ case 1:
+ refresh(0);
+ return (1);
+ case 2:
+ /* back from a 'v' command - don't redraw the screen */
+ return (1);
+ }
+ break;
+
+ case VREDO:
+ state = VNORMAL;
+ if (argc1 != 0)
+ lastac = argc1;
+ switch (vi_cmd(lastac, lastcmd)) {
+ case -1:
+ vi_error();
+ refresh(0);
+ break;
+ case 0:
+ if (insert != 0) {
+ if (lastcmd[0] == 's' ||
+ ksh_eq(lastcmd[0], 'C', 'c')) {
+ if (redo_insert(1) != 0)
+ vi_error();
+ } else {
+ if (redo_insert(lastac) != 0)
+ vi_error();
+ }
+ }
+ refresh(0);
+ break;
+ case 1:
+ refresh(0);
+ return (1);
+ case 2:
+ /* back from a 'v' command - can't happen */
+ break;
+ }
+ break;
+
+ case VFAIL:
+ state = VNORMAL;
+ vi_error();
+ break;
+ }
+ return (0);
+}
+
+static int
+nextstate(int ch)
+{
+ if (is_extend(ch))
+ return (VEXTCMD);
+ else if (is_srch(ch))
+ return (VSEARCH);
+ else if (is_long(ch))
+ return (VXCH);
+ else if (ch == '.')
+ return (VREDO);
+ else if (ch == CTRL_V)
+ return (VVERSION);
+ else if (is_cmd(ch))
+ return (VCMD);
+ else
+ return (VFAIL);
+}
+
+static int
+vi_insert(int ch)
+{
+ int tcursor;
+
+ if (isched(ch, edchars.erase) || ch == CTRL_H) {
+ if (insert == REPLACE) {
+ if (vs->cursor == undo->cursor) {
+ vi_error();
+ return (0);
+ }
+ if (inslen > 0)
+ inslen--;
+ vs->cursor--;
+ if (vs->cursor >= undo->linelen)
+ vs->linelen--;
+ else
+ vs->cbuf[vs->cursor] = undo->cbuf[vs->cursor];
+ } else {
+ if (vs->cursor == 0)
+ return (0);
+ if (inslen > 0)
+ inslen--;
+ vs->cursor--;
+ vs->linelen--;
+ memmove(&vs->cbuf[vs->cursor], &vs->cbuf[vs->cursor + 1],
+ vs->linelen - vs->cursor + 1);
+ }
+ expanded = NONE;
+ return (0);
+ }
+ if (isched(ch, edchars.kill)) {
+ if (vs->cursor != 0) {
+ inslen = 0;
+ memmove(vs->cbuf, &vs->cbuf[vs->cursor],
+ vs->linelen - vs->cursor);
+ vs->linelen -= vs->cursor;
+ vs->cursor = 0;
+ }
+ expanded = NONE;
+ return (0);
+ }
+ if (isched(ch, edchars.werase)) {
+ if (vs->cursor != 0) {
+ tcursor = backword(1);
+ memmove(&vs->cbuf[tcursor], &vs->cbuf[vs->cursor],
+ vs->linelen - vs->cursor);
+ vs->linelen -= vs->cursor - tcursor;
+ if (inslen < vs->cursor - tcursor)
+ inslen = 0;
+ else
+ inslen -= vs->cursor - tcursor;
+ vs->cursor = tcursor;
+ }
+ expanded = NONE;
+ return (0);
+ }
+ /*
+ * 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)
+ */
+ if (first_insert && ch != CTRL_BO)
+ saved_inslen = 0;
+ switch (ch) {
+ case '\0':
+ return (-1);
+
+ case '\r':
+ case '\n':
+ return (1);
+
+ case CTRL_BO:
+ expanded = NONE;
+ if (first_insert) {
+ first_insert = false;
+ if (inslen == 0) {
+ inslen = saved_inslen;
+ return (redo_insert(0));
+ }
+ lastcmd[0] = 'a';
+ lastac = 1;
+ }
+ if (lastcmd[0] == 's' || ksh_eq(lastcmd[0], 'C', 'c'))
+ return (redo_insert(0));
+ else
+ return (redo_insert(lastac - 1));
+
+ /* { start nonstandard vi commands */
+ case CTRL_X:
+ expand_word(0);
+ break;
+
+ case CTRL_F:
+ complete_word(0, 0);
+ break;
+
+ case CTRL_E:
+ print_expansions(vs, 0);
+ break;
+
+ case CTRL_I:
+ if (Flag(FVITABCOMPLETE)) {
+ complete_word(0, 0);
+ break;
+ }
+ /* FALLTHROUGH */
+ /* end nonstandard vi commands } */
+
+ default:
+ if (vs->linelen >= vs->cbufsize - 1)
+ return (-1);
+ ibuf[inslen++] = ch;
+ if (insert == INSERT) {
+ memmove(&vs->cbuf[vs->cursor + 1], &vs->cbuf[vs->cursor],
+ vs->linelen - vs->cursor);
+ vs->linelen++;
+ }
+ vs->cbuf[vs->cursor++] = ch;
+ if (insert == REPLACE && vs->cursor > vs->linelen)
+ vs->linelen++;
+ expanded = NONE;
+ }
+ return (0);
+}
+
+static int
+vi_cmd(int argcnt, const char *cmd)
+{
+ int ncursor;
+ int cur, c1, c2, c3 = 0;
+ int any;
+ struct edstate *t;
+
+ if (argcnt == 0 && !is_zerocount(*cmd))
+ argcnt = 1;
+
+ if (is_move(*cmd)) {
+ if ((cur = domove(argcnt, cmd, 0)) >= 0) {
+ if (cur == vs->linelen && cur != 0)
+ cur--;
+ vs->cursor = cur;
+ } else
+ return (-1);
+ } else {
+ /* Don't save state in middle of macro.. */
+ if (is_undoable(*cmd) && !macro.p) {
+ undo->winleft = vs->winleft;
+ memmove(undo->cbuf, vs->cbuf, vs->linelen);
+ undo->linelen = vs->linelen;
+ undo->cursor = vs->cursor;
+ lastac = argcnt;
+ memmove(lastcmd, cmd, MAXVICMD);
+ }
+ switch (ord(*cmd)) {
+
+ case CTRL_L:
+ case CTRL_R:
+ redraw_line(true);
+ break;
+
+ case ORD('@'):
+ {
+ static char alias[] = "_\0";
+ struct tbl *ap;
+ size_t olen, nlen;
+ char *p, *nbuf;
+
+ /* lookup letter in alias list... */
+ alias[1] = cmd[1];
+ ap = ktsearch(&aliases, alias, hash(alias));
+ if (!cmd[1] || !ap || !(ap->flag & ISSET))
+ return (-1);
+ /* check if this is a recursive call... */
+ if ((p = (char *)macro.p))
+ while ((p = strnul(p)) && p[1])
+ if (*++p == cmd[1])
+ return (-1);
+ /* insert alias into macro buffer */
+ 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, AEDIT);
+ memcpy(nbuf, ap->val.s, nlen);
+ nbuf[nlen++] = cmd[1];
+ if (macro.p) {
+ memcpy(nbuf + nlen, macro.p, olen);
+ afree(macro.buf, AEDIT);
+ nlen += olen;
+ } else {
+ nbuf[nlen++] = '\0';
+ nbuf[nlen++] = '\0';
+ }
+ macro.p = macro.buf = (unsigned char *)nbuf;
+ macro.len = nlen;
+ }
+ break;
+
+ case ORD('a'):
+ modified = 1;
+ hnum = hlast;
+ if (vs->linelen != 0)
+ vs->cursor++;
+ insert = INSERT;
+ break;
+
+ case ORD('A'):
+ modified = 1;
+ hnum = hlast;
+ del_range(0, 0);
+ vs->cursor = vs->linelen;
+ insert = INSERT;
+ break;
+
+ case ORD('S'):
+ vs->cursor = domovebeg();
+ del_range(vs->cursor, vs->linelen);
+ modified = 1;
+ hnum = hlast;
+ insert = INSERT;
+ break;
+
+ case ORD('Y'):
+ cmd = "y$";
+ /* ahhhhhh... */
+
+ /* FALLTHROUGH */
+ case ORD('c'):
+ case ORD('d'):
+ case ORD('y'):
+ if (*cmd == cmd[1]) {
+ c1 = *cmd == 'c' ? domovebeg() : 0;
+ c2 = vs->linelen;
+ } else if (!is_move(cmd[1]))
+ return (-1);
+ else {
+ if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
+ return (-1);
+ if (*cmd == 'c' && ksh_eq(cmd[1], 'W', 'w') &&
+ !ctype(vs->cbuf[vs->cursor], C_SPACE)) {
+ do {
+ --ncursor;
+ } while (ctype(vs->cbuf[ncursor], C_SPACE));
+ ncursor++;
+ }
+ if (ncursor > vs->cursor) {
+ c1 = vs->cursor;
+ c2 = ncursor;
+ } else {
+ c1 = ncursor;
+ c2 = vs->cursor;
+ if (cmd[1] == '%')
+ c2++;
+ }
+ }
+ if (*cmd != 'c' && c1 != c2)
+ yank_range(c1, c2);
+ if (*cmd != 'y') {
+ del_range(c1, c2);
+ vs->cursor = c1;
+ }
+ if (*cmd == 'c') {
+ modified = 1;
+ hnum = hlast;
+ insert = INSERT;
+ }
+ break;
+
+ case ORD('p'):
+ modified = 1;
+ hnum = hlast;
+ if (vs->linelen != 0)
+ vs->cursor++;
+ while (putbuf(ybuf, yanklen, false) == 0 &&
+ --argcnt > 0)
+ ;
+ if (vs->cursor != 0)
+ vs->cursor--;
+ if (argcnt != 0)
+ return (-1);
+ break;
+
+ case ORD('P'):
+ modified = 1;
+ hnum = hlast;
+ any = 0;
+ while (putbuf(ybuf, yanklen, false) == 0 &&
+ --argcnt > 0)
+ any = 1;
+ if (any && vs->cursor != 0)
+ vs->cursor--;
+ if (argcnt != 0)
+ return (-1);
+ break;
+
+ case ORD('C'):
+ modified = 1;
+ hnum = hlast;
+ del_range(vs->cursor, vs->linelen);
+ insert = INSERT;
+ break;
+
+ case ORD('D'):
+ yank_range(vs->cursor, vs->linelen);
+ del_range(vs->cursor, vs->linelen);
+ if (vs->cursor != 0)
+ vs->cursor--;
+ break;
+
+ case ORD('g'):
+ if (!argcnt)
+ argcnt = hlast;
+ /* FALLTHROUGH */
+ case ORD('G'):
+ if (!argcnt)
+ argcnt = 1;
+ else
+ argcnt = hlast - (source->line - argcnt);
+ if (grabhist(modified, argcnt - 1) < 0)
+ return (-1);
+ else {
+ modified = 0;
+ hnum = argcnt - 1;
+ }
+ break;
+
+ case ORD('i'):
+ modified = 1;
+ hnum = hlast;
+ insert = INSERT;
+ break;
+
+ case ORD('I'):
+ modified = 1;
+ hnum = hlast;
+ vs->cursor = domovebeg();
+ insert = INSERT;
+ break;
+
+ case ORD('j'):
+ case ORD('+'):
+ case CTRL_N:
+ if (grabhist(modified, hnum + argcnt) < 0)
+ return (-1);
+ else {
+ modified = 0;
+ hnum += argcnt;
+ }
+ break;
+
+ case ORD('k'):
+ case ORD('-'):
+ case CTRL_P:
+ if (grabhist(modified, hnum - argcnt) < 0)
+ return (-1);
+ else {
+ modified = 0;
+ hnum -= argcnt;
+ }
+ break;
+
+ case ORD('r'):
+ if (vs->linelen == 0)
+ return (-1);
+ modified = 1;
+ hnum = hlast;
+ if (cmd[1] == 0)
+ vi_error();
+ else {
+ int n;
+
+ if (vs->cursor + argcnt > vs->linelen)
+ return (-1);
+ for (n = 0; n < argcnt; ++n)
+ vs->cbuf[vs->cursor + n] = cmd[1];
+ vs->cursor += n - 1;
+ }
+ break;
+
+ case ORD('R'):
+ modified = 1;
+ hnum = hlast;
+ insert = REPLACE;
+ break;
+
+ case ORD('s'):
+ if (vs->linelen == 0)
+ return (-1);
+ modified = 1;
+ hnum = hlast;
+ if (vs->cursor + argcnt > vs->linelen)
+ argcnt = vs->linelen - vs->cursor;
+ del_range(vs->cursor, vs->cursor + argcnt);
+ insert = INSERT;
+ break;
+
+ case ORD('v'):
+ if (!argcnt) {
+ if (vs->linelen == 0)
+ return (-1);
+ if (modified) {
+ vs->cbuf[vs->linelen] = '\0';
+ histsave(&source->line, vs->cbuf,
+ HIST_STORE, true);
+ } else
+ argcnt = source->line + 1 -
+ (hlast - hnum);
+ }
+ if (argcnt)
+ shf_snprintf(vs->cbuf, vs->cbufsize, Tf_sd,
+ "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+ argcnt);
+ else
+ strlcpy(vs->cbuf,
+ "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+ vs->cbufsize);
+ vs->linelen = strlen(vs->cbuf);
+ return (2);
+
+ case ORD('x'):
+ if (vs->linelen == 0)
+ return (-1);
+ modified = 1;
+ hnum = hlast;
+ if (vs->cursor + argcnt > vs->linelen)
+ argcnt = vs->linelen - vs->cursor;
+ yank_range(vs->cursor, vs->cursor + argcnt);
+ del_range(vs->cursor, vs->cursor + argcnt);
+ break;
+
+ case ORD('X'):
+ if (vs->cursor > 0) {
+ modified = 1;
+ hnum = hlast;
+ if (vs->cursor < argcnt)
+ argcnt = vs->cursor;
+ yank_range(vs->cursor - argcnt, vs->cursor);
+ del_range(vs->cursor - argcnt, vs->cursor);
+ vs->cursor -= argcnt;
+ } else
+ return (-1);
+ break;
+
+ case ORD('u'):
+ t = vs;
+ vs = undo;
+ undo = t;
+ break;
+
+ case ORD('U'):
+ if (!modified)
+ return (-1);
+ if (grabhist(modified, ohnum) < 0)
+ return (-1);
+ modified = 0;
+ hnum = ohnum;
+ break;
+
+ case ORD('?'):
+ if (hnum == hlast)
+ hnum = -1;
+ /* ahhh */
+
+ /* FALLTHROUGH */
+ case ORD('/'):
+ c3 = 1;
+ srchlen = 0;
+ lastsearch = *cmd;
+ /* FALLTHROUGH */
+ case ORD('n'):
+ case ORD('N'):
+ if (lastsearch == ORD(' '))
+ return (-1);
+ if (lastsearch == ORD('?'))
+ c1 = 1;
+ else
+ c1 = 0;
+ if (*cmd == 'N')
+ c1 = !c1;
+ if ((c2 = grabsearch(modified, hnum,
+ c1, srchpat)) < 0) {
+ if (c3) {
+ restore_cbuf();
+ refresh(0);
+ }
+ return (-1);
+ } else {
+ modified = 0;
+ hnum = c2;
+ ohnum = hnum;
+ }
+ if (argcnt >= 2) {
+ /* flag from cursor-up command */
+ vs->cursor = argcnt - 2;
+ return (0);
+ }
+ break;
+ case ORD('_'):
+ {
+ bool inspace;
+ char *p, *sp;
+
+ if (histnum(-1) < 0)
+ return (-1);
+ p = *histpos();
+ if (argcnt) {
+ while (ctype(*p, C_SPACE))
+ p++;
+ while (*p && --argcnt) {
+ while (*p && !ctype(*p, C_SPACE))
+ p++;
+ while (ctype(*p, C_SPACE))
+ p++;
+ }
+ if (!*p)
+ return (-1);
+ sp = p;
+ } else {
+ sp = p;
+ inspace = false;
+ while (*p) {
+ if (ctype(*p, C_SPACE))
+ inspace = true;
+ else if (inspace) {
+ inspace = false;
+ sp = p;
+ }
+ p++;
+ }
+ p = sp;
+ }
+ modified = 1;
+ hnum = hlast;
+ if (vs->cursor != vs->linelen)
+ vs->cursor++;
+ while (*p && !ctype(*p, C_SPACE)) {
+ argcnt++;
+ p++;
+ }
+ if (putbuf(T1space, 1, false) != 0 ||
+ putbuf(sp, argcnt, false) != 0) {
+ if (vs->cursor != 0)
+ vs->cursor--;
+ return (-1);
+ }
+ insert = INSERT;
+ }
+ break;
+
+ case ORD('~'):
+ {
+ char *p;
+ int i;
+
+ if (vs->linelen == 0)
+ return (-1);
+ for (i = 0; i < argcnt; i++) {
+ p = &vs->cbuf[vs->cursor];
+ if (ctype(*p, C_LOWER)) {
+ modified = 1;
+ hnum = hlast;
+ *p = ksh_toupper(*p);
+ } else if (ctype(*p, C_UPPER)) {
+ modified = 1;
+ hnum = hlast;
+ *p = ksh_tolower(*p);
+ }
+ if (vs->cursor < vs->linelen - 1)
+ vs->cursor++;
+ }
+ break;
+ }
+
+ case ORD('#'):
+ {
+ int ret = x_do_comment(vs->cbuf, vs->cbufsize,
+ &vs->linelen);
+ if (ret >= 0)
+ vs->cursor = 0;
+ return (ret);
+ }
+
+ /* AT&T ksh */
+ case ORD('='):
+ /* Nonstandard vi/ksh */
+ case CTRL_E:
+ print_expansions(vs, 1);
+ break;
+
+
+ /* Nonstandard vi/ksh */
+ case CTRL_I:
+ if (!Flag(FVITABCOMPLETE))
+ return (-1);
+ complete_word(1, argcnt);
+ break;
+
+ /* some annoying AT&T kshs */
+ case CTRL_BO:
+ if (!Flag(FVIESCCOMPLETE))
+ return (-1);
+ /* FALLTHROUGH */
+ /* AT&T ksh */
+ case ORD('\\'):
+ /* Nonstandard vi/ksh */
+ case CTRL_F:
+ complete_word(1, argcnt);
+ break;
+
+
+ /* AT&T ksh */
+ case ORD('*'):
+ /* Nonstandard vi/ksh */
+ case CTRL_X:
+ expand_word(1);
+ break;
+
+
+ /* mksh: cursor movement */
+ case ORD('['):
+ case ORD('O'):
+ state = VPREFIX2;
+ if (vs->linelen != 0)
+ vs->cursor++;
+ insert = INSERT;
+ return (0);
+ }
+ if (insert == 0 && vs->cursor != 0 && vs->cursor >= vs->linelen)
+ vs->cursor--;
+ }
+ return (0);
+}
+
+static int
+domove(int argcnt, const char *cmd, int sub)
+{
+ int ncursor = 0, i = 0, t;
+ unsigned int bcount;
+
+ switch (ord(*cmd)) {
+ case ORD('b'):
+ if (!sub && vs->cursor == 0)
+ return (-1);
+ ncursor = backword(argcnt);
+ break;
+
+ case ORD('B'):
+ if (!sub && vs->cursor == 0)
+ return (-1);
+ ncursor = Backword(argcnt);
+ break;
+
+ case ORD('e'):
+ if (!sub && vs->cursor + 1 >= vs->linelen)
+ return (-1);
+ ncursor = endword(argcnt);
+ if (sub && ncursor < vs->linelen)
+ ncursor++;
+ break;
+
+ case ORD('E'):
+ if (!sub && vs->cursor + 1 >= vs->linelen)
+ return (-1);
+ ncursor = Endword(argcnt);
+ if (sub && ncursor < vs->linelen)
+ ncursor++;
+ break;
+
+ case ORD('f'):
+ case ORD('F'):
+ case ORD('t'):
+ case ORD('T'):
+ fsavecmd = *cmd;
+ fsavech = cmd[1];
+ /* FALLTHROUGH */
+ case ORD(','):
+ case ORD(';'):
+ if (fsavecmd == ORD(' '))
+ return (-1);
+ i = ksh_eq(fsavecmd, 'F', 'f');
+ t = rtt2asc(fsavecmd) > rtt2asc('a');
+ if (*cmd == ',')
+ t = !t;
+ if ((ncursor = findch(fsavech, argcnt, tobool(t),
+ tobool(i))) < 0)
+ return (-1);
+ if (sub && t)
+ ncursor++;
+ break;
+
+ case ORD('h'):
+ case CTRL_H:
+ if (!sub && vs->cursor == 0)
+ return (-1);
+ ncursor = vs->cursor - argcnt;
+ if (ncursor < 0)
+ ncursor = 0;
+ break;
+
+ case ORD(' '):
+ case ORD('l'):
+ if (!sub && vs->cursor + 1 >= vs->linelen)
+ return (-1);
+ if (vs->linelen != 0) {
+ ncursor = vs->cursor + argcnt;
+ if (ncursor > vs->linelen)
+ ncursor = vs->linelen;
+ }
+ break;
+
+ case ORD('w'):
+ if (!sub && vs->cursor + 1 >= vs->linelen)
+ return (-1);
+ ncursor = forwword(argcnt);
+ break;
+
+ case ORD('W'):
+ if (!sub && vs->cursor + 1 >= vs->linelen)
+ return (-1);
+ ncursor = Forwword(argcnt);
+ break;
+
+ case ORD('0'):
+ ncursor = 0;
+ break;
+
+ case ORD('^'):
+ ncursor = domovebeg();
+ break;
+
+ case ORD('|'):
+ ncursor = argcnt;
+ if (ncursor > vs->linelen)
+ ncursor = vs->linelen;
+ if (ncursor)
+ ncursor--;
+ break;
+
+ case ORD('$'):
+ if (vs->linelen != 0)
+ ncursor = vs->linelen;
+ else
+ ncursor = 0;
+ break;
+
+ case ORD('%'):
+ ncursor = vs->cursor;
+ while (ncursor < vs->linelen &&
+ (i = bracktype(vs->cbuf[ncursor])) == 0)
+ ncursor++;
+ if (ncursor == vs->linelen)
+ return (-1);
+ bcount = 1;
+ do {
+ if (i > 0) {
+ if (++ncursor >= vs->linelen)
+ return (-1);
+ } else {
+ if (--ncursor < 0)
+ return (-1);
+ }
+ t = bracktype(vs->cbuf[ncursor]);
+ if (t == i)
+ bcount++;
+ else if (t == -i)
+ bcount--;
+ } while (bcount != 0);
+ if (sub && i > 0)
+ ncursor++;
+ break;
+
+ default:
+ return (-1);
+ }
+ return (ncursor);
+}
+
+static int
+domovebeg(void)
+{
+ int ncursor = 0;
+
+ while (ncursor < vs->linelen - 1 &&
+ ctype(vs->cbuf[ncursor], C_SPACE))
+ ncursor++;
+ return (ncursor);
+}
+
+static int
+redo_insert(int count)
+{
+ while (count-- > 0)
+ if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0)
+ return (-1);
+ if (vs->cursor > 0)
+ vs->cursor--;
+ insert = 0;
+ return (0);
+}
+
+static void
+yank_range(int a, int b)
+{
+ yanklen = b - a;
+ if (yanklen != 0)
+ memmove(ybuf, &vs->cbuf[a], yanklen);
+}
+
+static int
+bracktype(int ch)
+{
+ switch (ord(ch)) {
+
+ case ORD('('):
+ return (1);
+
+ case ORD('['):
+ return (2);
+
+ case ORD('{'):
+ return (3);
+
+ case ORD(')'):
+ return (-1);
+
+ case ORD(']'):
+ return (-2);
+
+ case ORD('}'):
+ return (-3);
+
+ default:
+ return (0);
+ }
+}
+
+/*
+ * Non user interface editor routines below here
+ */
+
+static void
+save_cbuf(void)
+{
+ memmove(holdbufp, vs->cbuf, vs->linelen);
+ holdlen = vs->linelen;
+ holdbufp[holdlen] = '\0';
+}
+
+static void
+restore_cbuf(void)
+{
+ vs->cursor = 0;
+ vs->linelen = holdlen;
+ memmove(vs->cbuf, holdbufp, holdlen);
+}
+
+/* return a new edstate */
+static struct edstate *
+save_edstate(struct edstate *old)
+{
+ struct edstate *news;
+
+ news = alloc(sizeof(struct edstate), AEDIT);
+ news->cbuf = alloc(old->cbufsize, AEDIT);
+ memcpy(news->cbuf, old->cbuf, old->linelen);
+ news->cbufsize = old->cbufsize;
+ news->linelen = old->linelen;
+ news->cursor = old->cursor;
+ news->winleft = old->winleft;
+ return (news);
+}
+
+static void
+restore_edstate(struct edstate *news, struct edstate *old)
+{
+ memcpy(news->cbuf, old->cbuf, old->linelen);
+ news->linelen = old->linelen;
+ news->cursor = old->cursor;
+ news->winleft = old->winleft;
+ free_edstate(old);
+}
+
+static void
+free_edstate(struct edstate *old)
+{
+ afree(old->cbuf, AEDIT);
+ afree(old, AEDIT);
+}
+
+/*
+ * this is used for calling x_escape() in complete_word()
+ */
+static int
+x_vi_putbuf(const char *s, size_t len)
+{
+ return (putbuf(s, len, false));
+}
+
+static int
+putbuf(const char *buf, ssize_t len, bool repl)
+{
+ if (len == 0)
+ return (0);
+ if (repl) {
+ if (vs->cursor + len >= vs->cbufsize)
+ return (-1);
+ if (vs->cursor + len > vs->linelen)
+ vs->linelen = vs->cursor + len;
+ } else {
+ if (vs->linelen + len >= vs->cbufsize)
+ return (-1);
+ memmove(&vs->cbuf[vs->cursor + len], &vs->cbuf[vs->cursor],
+ vs->linelen - vs->cursor);
+ vs->linelen += len;
+ }
+ memmove(&vs->cbuf[vs->cursor], buf, len);
+ vs->cursor += len;
+ return (0);
+}
+
+static void
+del_range(int a, int b)
+{
+ if (vs->linelen != b)
+ memmove(&vs->cbuf[a], &vs->cbuf[b], vs->linelen - b);
+ vs->linelen -= b - a;
+}
+
+static int
+findch(int ch, int cnt, bool forw, bool incl)
+{
+ int ncursor;
+
+ if (vs->linelen == 0)
+ return (-1);
+ ncursor = vs->cursor;
+ while (cnt--) {
+ do {
+ if (forw) {
+ if (++ncursor == vs->linelen)
+ return (-1);
+ } else {
+ if (--ncursor < 0)
+ return (-1);
+ }
+ } while (vs->cbuf[ncursor] != ch);
+ }
+ if (!incl) {
+ if (forw)
+ ncursor--;
+ else
+ ncursor++;
+ }
+ return (ncursor);
+}
+
+static int
+forwword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor < vs->linelen && argcnt--) {
+ if (ctype(vs->cbuf[ncursor], C_ALNUX))
+ while (ncursor < vs->linelen &&
+ ctype(vs->cbuf[ncursor], C_ALNUX))
+ ncursor++;
+ else if (!ctype(vs->cbuf[ncursor], C_SPACE))
+ while (ncursor < vs->linelen &&
+ !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
+ ncursor++;
+ while (ncursor < vs->linelen &&
+ ctype(vs->cbuf[ncursor], C_SPACE))
+ ncursor++;
+ }
+ return (ncursor);
+}
+
+static int
+backword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor > 0 && argcnt--) {
+ while (--ncursor > 0 && ctype(vs->cbuf[ncursor], C_SPACE))
+ ;
+ if (ncursor > 0) {
+ if (ctype(vs->cbuf[ncursor], C_ALNUX))
+ while (--ncursor >= 0 &&
+ ctype(vs->cbuf[ncursor], C_ALNUX))
+ ;
+ else
+ while (--ncursor >= 0 &&
+ !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
+ ;
+ ncursor++;
+ }
+ }
+ return (ncursor);
+}
+
+static int
+endword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor < vs->linelen && argcnt--) {
+ while (++ncursor < vs->linelen - 1 &&
+ ctype(vs->cbuf[ncursor], C_SPACE))
+ ;
+ if (ncursor < vs->linelen - 1) {
+ if (ctype(vs->cbuf[ncursor], C_ALNUX))
+ while (++ncursor < vs->linelen &&
+ ctype(vs->cbuf[ncursor], C_ALNUX))
+ ;
+ else
+ while (++ncursor < vs->linelen &&
+ !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
+ ;
+ ncursor--;
+ }
+ }
+ return (ncursor);
+}
+
+static int
+Forwword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor < vs->linelen && argcnt--) {
+ while (ncursor < vs->linelen &&
+ !ctype(vs->cbuf[ncursor], C_SPACE))
+ ncursor++;
+ while (ncursor < vs->linelen &&
+ ctype(vs->cbuf[ncursor], C_SPACE))
+ ncursor++;
+ }
+ return (ncursor);
+}
+
+static int
+Backword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor > 0 && argcnt--) {
+ while (--ncursor >= 0 && ctype(vs->cbuf[ncursor], C_SPACE))
+ ;
+ while (ncursor >= 0 && !ctype(vs->cbuf[ncursor], C_SPACE))
+ ncursor--;
+ ncursor++;
+ }
+ return (ncursor);
+}
+
+static int
+Endword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = vs->cursor;
+ while (ncursor < vs->linelen - 1 && argcnt--) {
+ while (++ncursor < vs->linelen - 1 &&
+ ctype(vs->cbuf[ncursor], C_SPACE))
+ ;
+ if (ncursor < vs->linelen - 1) {
+ while (++ncursor < vs->linelen &&
+ !ctype(vs->cbuf[ncursor], C_SPACE))
+ ;
+ ncursor--;
+ }
+ }
+ return (ncursor);
+}
+
+static int
+grabhist(int save, int n)
+{
+ char *hptr;
+
+ if (n < 0 || n > hlast)
+ return (-1);
+ if (n == hlast) {
+ restore_cbuf();
+ ohnum = n;
+ return (0);
+ }
+ (void)histnum(n);
+ if ((hptr = *histpos()) == NULL) {
+ internal_warningf("grabhist: bad history array");
+ return (-1);
+ }
+ if (save)
+ save_cbuf();
+ if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+ vs->linelen = vs->cbufsize - 1;
+ memmove(vs->cbuf, hptr, vs->linelen);
+ vs->cursor = 0;
+ ohnum = n;
+ return (0);
+}
+
+static int
+grabsearch(int save, int start, int fwd, const char *pat)
+{
+ char *hptr;
+ int hist;
+ bool anchored;
+
+ if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1))
+ return (-1);
+ if (fwd)
+ start++;
+ else
+ start--;
+ anchored = *pat == '^' ? (++pat, true) : false;
+ if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
+ /* (start != 0 && fwd && match(holdbufp, pat) >= 0) */
+ if (start != 0 && fwd && strcmp(holdbufp, pat) >= 0) {
+ restore_cbuf();
+ return (0);
+ } else
+ return (-1);
+ }
+ if (save)
+ save_cbuf();
+ histnum(hist);
+ hptr = *histpos();
+ if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+ vs->linelen = vs->cbufsize - 1;
+ memmove(vs->cbuf, hptr, vs->linelen);
+ vs->cursor = 0;
+ return (hist);
+}
+
+static void
+redraw_line(bool newl)
+{
+ if (wbuf_len)
+ memset(wbuf[win], ' ', wbuf_len);
+ if (newl) {
+ x_putc('\r');
+ x_putc('\n');
+ }
+ x_pprompt();
+ morec = ' ';
+}
+
+static void
+refresh(int leftside)
+{
+ if (leftside < 0)
+ leftside = lastref;
+ else
+ lastref = leftside;
+ if (outofwin())
+ rewindow();
+ display(wbuf[1 - win], wbuf[win], leftside);
+ win = 1 - win;
+}
+
+static int
+outofwin(void)
+{
+ int cur, col;
+
+ if (vs->cursor < vs->winleft)
+ return (1);
+ col = 0;
+ cur = vs->winleft;
+ while (cur < vs->cursor)
+ col = newcol((unsigned char)vs->cbuf[cur++], col);
+ if (col >= winwidth)
+ return (1);
+ return (0);
+}
+
+static void
+rewindow(void)
+{
+ int tcur, tcol;
+ int holdcur1, holdcol1;
+ int holdcur2, holdcol2;
+
+ holdcur1 = holdcur2 = tcur = 0;
+ holdcol1 = holdcol2 = tcol = 0;
+ while (tcur < vs->cursor) {
+ if (tcol - holdcol2 > winwidth / 2) {
+ holdcur1 = holdcur2;
+ holdcol1 = holdcol2;
+ holdcur2 = tcur;
+ holdcol2 = tcol;
+ }
+ tcol = newcol((unsigned char)vs->cbuf[tcur++], tcol);
+ }
+ while (tcol - holdcol1 > winwidth / 2)
+ holdcol1 = newcol((unsigned char)vs->cbuf[holdcur1++],
+ holdcol1);
+ vs->winleft = holdcur1;
+}
+
+static int
+newcol(unsigned char ch, int col)
+{
+ if (ch == '\t')
+ return ((col | 7) + 1);
+ return (col + char_len(ch));
+}
+
+static void
+display(char *wb1, char *wb2, int leftside)
+{
+ unsigned char ch;
+ char *twb1, *twb2, mc;
+ int cur, col, cnt;
+ int ncol = 0;
+ int moreright;
+
+ col = 0;
+ cur = vs->winleft;
+ moreright = 0;
+ twb1 = wb1;
+ while (col < winwidth && cur < vs->linelen) {
+ if (cur == vs->cursor && leftside)
+ ncol = col + pwidth;
+ if ((ch = vs->cbuf[cur]) == '\t')
+ do {
+ *twb1++ = ' ';
+ } while (++col < winwidth && (col & 7) != 0);
+ else if (col < winwidth) {
+ if (ksh_isctrl(ch)) {
+ *twb1++ = '^';
+ if (++col < winwidth) {
+ *twb1++ = ksh_unctrl(ch);
+ col++;
+ }
+ } else {
+ *twb1++ = ch;
+ col++;
+ }
+ }
+ if (cur == vs->cursor && !leftside)
+ ncol = col + pwidth - 1;
+ cur++;
+ }
+ if (cur == vs->cursor)
+ ncol = col + pwidth;
+ if (col < winwidth) {
+ while (col < winwidth) {
+ *twb1++ = ' ';
+ col++;
+ }
+ } else
+ moreright++;
+ *twb1 = ' ';
+
+ col = pwidth;
+ cnt = winwidth;
+ twb1 = wb1;
+ twb2 = wb2;
+ while (cnt--) {
+ if (*twb1 != *twb2) {
+ if (x_col != col)
+ ed_mov_opt(col, wb1);
+ x_putc(*twb1);
+ x_col++;
+ }
+ twb1++;
+ twb2++;
+ col++;
+ }
+ if (vs->winleft > 0 && moreright)
+ /*
+ * POSIX says to use * for this but that is a globbing
+ * character and may confuse people; + is more innocuous
+ */
+ mc = '+';
+ else if (vs->winleft > 0)
+ mc = '<';
+ else if (moreright)
+ mc = '>';
+ else
+ mc = ' ';
+ if (mc != morec) {
+ ed_mov_opt(pwidth + winwidth + 1, wb1);
+ x_putc(mc);
+ x_col++;
+ morec = mc;
+ }
+ if (x_col != ncol)
+ ed_mov_opt(ncol, wb1);
+}
+
+static void
+ed_mov_opt(int col, char *wb)
+{
+ if (col < x_col) {
+ if (col + 1 < x_col - col) {
+ x_putc('\r');
+ x_pprompt();
+ while (x_col++ < col)
+ x_putcf(*wb++);
+ } else {
+ while (x_col-- > col)
+ x_putc('\b');
+ }
+ } else {
+ wb = &wb[x_col - pwidth];
+ while (x_col++ < col)
+ x_putcf(*wb++);
+ }
+ x_col = col;
+}
+
+
+/* replace word with all expansions (ie, expand word*) */
+static int
+expand_word(int cmd)
+{
+ static struct edstate *buf;
+ int rval = 0, nwords, start, end, i;
+ char **words;
+
+ /* Undo previous expansion */
+ if (cmd == 0 && expanded == EXPAND && buf) {
+ restore_edstate(vs, buf);
+ buf = 0;
+ expanded = NONE;
+ return (0);
+ }
+ if (buf) {
+ free_edstate(buf);
+ buf = 0;
+ }
+
+ i = XCF_COMMAND_FILE | XCF_FULLPATH;
+ nwords = x_cf_glob(&i, vs->cbuf, vs->linelen, vs->cursor,
+ &start, &end, &words);
+ if (nwords == 0) {
+ vi_error();
+ return (-1);
+ }
+
+ buf = save_edstate(vs);
+ expanded = EXPAND;
+ del_range(start, end);
+ vs->cursor = start;
+ i = 0;
+ while (i < nwords) {
+ if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
+ rval = -1;
+ break;
+ }
+ if (++i < nwords && putbuf(T1space, 1, false) != 0) {
+ rval = -1;
+ break;
+ }
+ }
+ i = buf->cursor - end;
+ if (rval == 0 && i > 0)
+ vs->cursor += i;
+ modified = 1;
+ hnum = hlast;
+ insert = INSERT;
+ lastac = 0;
+ refresh(0);
+ return (rval);
+}
+
+static int
+complete_word(int cmd, int count)
+{
+ static struct edstate *buf;
+ int rval, nwords, start, end, flags;
+ size_t match_len;
+ char **words;
+ char *match;
+ bool is_unique;
+
+ /* Undo previous completion */
+ if (cmd == 0 && expanded == COMPLETE && buf) {
+ print_expansions(buf, 0);
+ expanded = PRINT;
+ return (0);
+ }
+ if (cmd == 0 && expanded == PRINT && buf) {
+ restore_edstate(vs, buf);
+ buf = 0;
+ expanded = NONE;
+ return (0);
+ }
+ if (buf) {
+ free_edstate(buf);
+ buf = 0;
+ }
+
+ /*
+ * XCF_FULLPATH for count 'cause the menu printed by
+ * print_expansions() was done this way.
+ */
+ flags = XCF_COMMAND_FILE;
+ if (count)
+ flags |= XCF_FULLPATH;
+ nwords = x_cf_glob(&flags, vs->cbuf, vs->linelen, vs->cursor,
+ &start, &end, &words);
+ if (nwords == 0) {
+ vi_error();
+ return (-1);
+ }
+ if (count) {
+ int i;
+
+ count--;
+ if (count >= nwords) {
+ vi_error();
+ x_print_expansions(nwords, words,
+ tobool(flags & XCF_IS_COMMAND));
+ x_free_words(nwords, words);
+ redraw_line(false);
+ return (-1);
+ }
+ /*
+ * Expand the count'th word to its basename
+ */
+ if (flags & XCF_IS_COMMAND) {
+ match = words[count] +
+ x_basename(words[count], NULL);
+ /* If more than one possible match, use full path */
+ for (i = 0; i < nwords; i++)
+ if (i != count &&
+ strcmp(words[i] + x_basename(words[i],
+ NULL), match) == 0) {
+ match = words[count];
+ break;
+ }
+ } else
+ match = words[count];
+ match_len = strlen(match);
+ is_unique = true;
+ /* expanded = PRINT; next call undo */
+ } else {
+ match = words[0];
+ match_len = x_longest_prefix(nwords, words);
+ /* next call will list completions */
+ expanded = COMPLETE;
+ is_unique = nwords == 1;
+ }
+
+ buf = save_edstate(vs);
+ del_range(start, end);
+ vs->cursor = start;
+
+ /*
+ * 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
+ * to be used (ie, complete the next portion of the path).
+ */
+ expanded = NONE;
+
+ /*
+ * append a space if this is a non-directory match
+ * and not a parameter or homedir substitution
+ */
+ if (match_len > 0 && !mksh_cdirsep(match[match_len - 1]) &&
+ !(flags & XCF_IS_NOSPACE))
+ rval = putbuf(T1space, 1, false);
+ }
+ x_free_words(nwords, words);
+
+ modified = 1;
+ hnum = hlast;
+ insert = INSERT;
+ /* prevent this from being redone... */
+ lastac = 0;
+ refresh(0);
+
+ return (rval);
+}
+
+static int
+print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
+{
+ int start, end, nwords, i;
+ char **words;
+
+ 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, tobool(i & XCF_IS_COMMAND));
+ x_free_words(nwords, words);
+ redraw_line(false);
+ return (0);
+}
+#endif /* !MKSH_S_NOVI */
+
+/* Similar to x_zotc(emacs.c), but no tab weirdness */
+static void
+x_vi_zotc(int c)
+{
+ if (ksh_isctrl(c)) {
+ x_putc('^');
+ c = ksh_unctrl(c);
+ }
+ x_putc(c);
+}
+
+#if !MKSH_S_NOVI
+static void
+vi_error(void)
+{
+ /* Beem out of any macros as soon as an error occurs */
+ vi_macro_reset();
+ x_putc(KSH_BEL);
+ x_flush();
+}
+
+static void
+vi_macro_reset(void)
+{
+ if (macro.p) {
+ afree(macro.buf, AEDIT);
+ memset((char *)&macro, 0, sizeof(macro));
+ }
+}
+#endif /* !MKSH_S_NOVI */
+
+/* called from main.c */
+void
+x_init(void)
+{
+ int i, j;
+
+ /*
+ * set edchars to force initial binding, except we need
+ * default values for ^W for some deficient systems…
+ */
+ edchars.erase = edchars.kill = edchars.intr = edchars.quit =
+ edchars.eof = EDCHAR_INITIAL;
+ edchars.werase = 027;
+
+ /* command line editing specific memory allocation */
+ ainit(AEDIT);
+ holdbufp = alloc(LINE, AEDIT);
+
+ /* initialise Emacs command line editing mode */
+ x_nextcmd = -1;
+
+ 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++)
+ for (j = 0; j < X_TABSZ; j++)
+ x_tab[i][j] = XFUNC_error;
+ for (i = 0; i < (int)NELEM(x_defbindings); i++)
+ x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
+ = x_defbindings[i].xdb_func;
+
+#ifndef MKSH_SMALL
+ 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;
+#endif
+}
+
+#ifdef DEBUG_LEAKS
+void
+x_done(void)
+{
+ if (x_tab != NULL)
+ afreeall(AEDIT);
+}
+#endif
+
+void
+x_initterm(const char *termtype)
+{
+ /* default must be 0 (bss) */
+ x_term_mode = 0;
+ /* catch any of the TERM types tmux uses, don’t ask m̲e̲ about it… */
+ switch (*termtype) {
+ case 's':
+ if (!strncmp(termtype, "screen", 6) &&
+ (termtype[6] == '\0' || termtype[6] == '-'))
+ x_term_mode = 1;
+ break;
+ case 't':
+ if (!strncmp(termtype, "tmux", 4) &&
+ (termtype[4] == '\0' || termtype[4] == '-'))
+ x_term_mode = 1;
+ break;
+ }
+}
+
+#ifndef MKSH_SMALL
+static char *
+x_eval_region_helper(const char *cmd, size_t len)
+{
+ char * volatile cp;
+ newenv(E_ERRH);
+
+ if (!kshsetjmp(e->jbuf)) {
+ char *wds = alloc(len + 3, ATEMP);
+
+ wds[0] = FUNASUB;
+ memcpy(wds + 1, cmd, len);
+ wds[len + 1] = '\0';
+ wds[len + 2] = EOS;
+
+ cp = evalstr(wds, DOSCALAR);
+ afree(wds, ATEMP);
+ strdupx(cp, cp, AEDIT);
+ } else
+ /* command cannot be parsed */
+ cp = NULL;
+ quitenv(NULL);
+ return (cp);
+}
+
+static int
+x_operate_region(char *(*helper)(const char *, size_t))
+{
+ char *rgbeg, *rgend, *cp;
+ size_t newlen;
+ /* only for LINE overflow checking */
+ size_t restlen;
+
+ if (xmp == NULL) {
+ rgbeg = xbuf;
+ rgend = xep;
+ } else if (xmp < xcp) {
+ rgbeg = xmp;
+ rgend = xcp;
+ } else {
+ rgbeg = xcp;
+ rgend = xmp;
+ }
+
+ x_e_putc2('\r');
+ x_clrtoeol(' ', false);
+ x_flush();
+ x_mode(false);
+ cp = helper(rgbeg, rgend - rgbeg);
+ x_mode(true);
+
+ if (cp == NULL) {
+ /* error return from helper */
+ x_eval_region_err:
+ x_e_putc2(KSH_BEL);
+ x_redraw('\r');
+ return (KSTD);
+ }
+
+ newlen = strlen(cp);
+ restlen = xep - rgend;
+ /* check for LINE overflow, until this is dynamically allocated */
+ if (rgbeg + newlen + restlen >= xend)
+ goto x_eval_region_err;
+
+ xmp = rgbeg;
+ xcp = rgbeg + newlen;
+ xep = xcp + restlen;
+ memmove(xcp, rgend, restlen + /* NUL */ 1);
+ memcpy(xmp, cp, newlen);
+ afree(cp, AEDIT);
+ x_adjust();
+ x_modified();
+ return (KSTD);
+}
+
+static int
+x_eval_region(int c MKSH_A_UNUSED)
+{
+ return (x_operate_region(x_eval_region_helper));
+}
+
+static char *
+x_quote_region_helper(const char *cmd, size_t len)
+{
+ char *s;
+ size_t newlen;
+ struct shf shf;
+
+ strndupx(s, cmd, len, ATEMP);
+ newlen = len < 256 ? 256 : 4096;
+ shf_sopen(alloc(newlen, AEDIT), newlen, SHF_WR | SHF_DYNAMIC, &shf);
+ shf.areap = AEDIT;
+ shf.flags |= SHF_ALLOCB;
+ print_value_quoted(&shf, s);
+ afree(s, ATEMP);
+ return (shf_sclose(&shf));
+}
+
+static int
+x_quote_region(int c MKSH_A_UNUSED)
+{
+ return (x_operate_region(x_quote_region_helper));
+}
+#endif /* !MKSH_SMALL */
+#endif /* !MKSH_NO_CMDLINE_EDITING */
diff --git a/shells/mksh/files/emacsfn.h b/shells/mksh/files/emacsfn.h
new file mode 100644
index 00000000000..61629876289
--- /dev/null
+++ b/shells/mksh/files/emacsfn.h
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 2009, 2010, 2015, 2016, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#if defined(EMACSFN_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/emacsfn.h,v 1.11 2020/04/13 20:46:39 tg Exp $");
+#define FN(cname,sname,flags) static int x_##cname(int);
+#elif defined(EMACSFN_ENUMS)
+#define FN(cname,sname,flags) XFUNC_##cname,
+#define F0(cname,sname,flags) XFUNC_##cname = 0,
+#elif defined(EMACSFN_ITEMS)
+#define FN(cname,sname,flags) { x_##cname, sname, flags },
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+F0(abort, "abort", 0)
+FN(beg_hist, "beginning-of-history", 0)
+FN(cls, "clear-screen", 0)
+FN(comment, "comment", 0)
+FN(comp_comm, "complete-command", 0)
+FN(comp_file, "complete-file", 0)
+FN(comp_list, "complete-list", 0)
+FN(complete, "complete", 0)
+FN(del_back, "delete-char-backward", XF_ARG)
+FN(del_bword, "delete-word-backward", XF_ARG)
+FN(del_char, "delete-char-forward", XF_ARG)
+FN(del_fword, "delete-word-forward", XF_ARG)
+FN(del_line, "kill-line", 0)
+FN(draw_line, "redraw", 0)
+#ifndef MKSH_SMALL
+FN(edit_line, "edit-line", XF_ARG)
+#endif
+FN(end_hist, "end-of-history", 0)
+FN(end_of_text, "eot", 0)
+FN(enumerate, "list", 0)
+FN(eot_del, "eot-or-delete", XF_ARG)
+FN(error, "error", 0)
+#ifndef MKSH_SMALL
+FN(eval_region, "evaluate-region", 0)
+#endif
+FN(expand, "expand-file", 0)
+#ifndef MKSH_SMALL
+FN(fold_capitalise, "capitalize-word", XF_ARG)
+FN(fold_lower, "downcase-word", XF_ARG)
+FN(fold_upper, "upcase-word", XF_ARG)
+#endif
+FN(goto_hist, "goto-history", XF_ARG)
+#ifndef MKSH_SMALL
+FN(ins_string, "macro-string", XF_NOBIND)
+#endif
+FN(insert, "auto-insert", XF_ARG)
+FN(kill, "kill-to-eol", XF_ARG)
+FN(kill_region, "kill-region", 0)
+FN(list_comm, "list-command", 0)
+FN(list_file, "list-file", 0)
+FN(literal, "quote", 0)
+FN(meta1, "prefix-1", XF_PREFIX)
+FN(meta2, "prefix-2", XF_PREFIX)
+FN(meta3, "prefix-3", XF_PREFIX)
+FN(meta_yank, "yank-pop", 0)
+FN(mv_back, "backward-char", XF_ARG)
+FN(mv_beg, "beginning-of-line", 0)
+FN(mv_bword, "backward-word", XF_ARG)
+FN(mv_end, "end-of-line", 0)
+FN(mv_forw, "forward-char", XF_ARG)
+FN(mv_fword, "forward-word", XF_ARG)
+FN(newline, "newline", 0)
+FN(next_com, "down-history", XF_ARG)
+FN(nl_next_com, "newline-and-next", 0)
+FN(noop, "no-op", 0)
+FN(prev_com, "up-history", XF_ARG)
+FN(prev_histword, "prev-hist-word", XF_ARG)
+#ifndef MKSH_SMALL
+FN(quote_region, "quote-region", 0)
+#endif
+FN(search_char_back, "search-character-backward", XF_ARG)
+FN(search_char_forw, "search-character-forward", XF_ARG)
+FN(search_hist, "search-history", 0)
+#ifndef MKSH_SMALL
+FN(search_hist_dn, "search-history-down", 0)
+FN(search_hist_up, "search-history-up", 0)
+#endif
+FN(set_arg, "set-arg", XF_NOBIND)
+FN(set_mark, "set-mark-command", 0)
+FN(transpose, "transpose-chars", 0)
+FN(version, "version", 0)
+#ifndef MKSH_SMALL
+FN(vt_hack, "vt100-hack", XF_ARG)
+#endif
+FN(xchg_point_mark, "exchange-point-and-mark", 0)
+FN(yank, "yank", 0)
+
+#undef FN
+#undef F0
+#undef EMACSFN_DEFNS
+#undef EMACSFN_ENUMS
+#undef EMACSFN_ITEMS
diff --git a/shells/mksh/files/eval.c b/shells/mksh/files/eval.c
new file mode 100644
index 00000000000..33a8ea1aa8e
--- /dev/null
+++ b/shells/mksh/files/eval.c
@@ -0,0 +1,2111 @@
+/* $OpenBSD: eval.c,v 1.40 2013/09/14 20:09:30 millert Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.231 2020/05/05 21:34:27 tg Exp $");
+
+/*
+ * string expansion
+ *
+ * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution.
+ * second pass: alternation ({,}), filename expansion (*?[]).
+ */
+
+/* expansion generator state */
+typedef struct {
+ /* not including an "int type;" member, see expand() */
+ /* string */
+ const char *str;
+ /* source */
+ union {
+ /* string[] */
+ const char **strv;
+ /* file */
+ struct shf *shf;
+ } u;
+ /* variable in ${var...} */
+ struct tbl *var;
+ /* split "$@" / call waitlast in $() */
+ bool split;
+} Expand;
+
+#define XBASE 0 /* scanning original string */
+#define XARGSEP 1 /* ifs0 between "$*" */
+#define XARG 2 /* expanding $*, $@ */
+#define XCOM 3 /* expanding $() */
+#define XNULLSUB 4 /* "$@" when $# is 0, so don't generate word */
+#define XSUB 5 /* expanding ${} string */
+#define XSUBMID 6 /* middle of expanding ${}; must be XSUB+1 */
+#define XSUBPAT 7 /* expanding [[ x = ${} ]] string */
+#define XSUBPATMID 8 /* middle, must be XSUBPAT+1 */
+
+#define isXSUB(t) ((t) == XSUB || (t) == XSUBPAT)
+
+/* States used for field splitting */
+#define IFS_WORD 0 /* word has chars (or quotes except "$@") */
+#define IFS_WS 1 /* have seen IFS white-space */
+#define IFS_NWS 2 /* have seen IFS non-white-space */
+#define IFS_IWS 3 /* beginning of word, ignore IFS WS */
+#define IFS_QUOTE 4 /* beg.w/quote, become IFS_WORD unless "$@" */
+
+#define STYPE_CHAR 0xFF
+#define STYPE_DBL 0x100
+#define STYPE_AT 0x200
+#define STYPE_SINGLE 0x2FF
+#define STYPE_MASK 0x300
+
+static int varsub(Expand *, const char *, const char *, unsigned int *, int *);
+static int comsub(Expand *, const char *, int);
+static char *valsub(struct op *, Area *);
+static char *trimsub(char *, char *, int);
+static void glob(char *, XPtrV *, bool);
+static void globit(XString *, char **, char *, XPtrV *, int);
+static const char *maybe_expand_tilde(const char *, XString *, char **, bool);
+#ifndef MKSH_NOPWNAM
+static char *homedir(char *);
+#endif
+static void alt_expand(XPtrV *, char *, char *, char *, int);
+static int utflen(const char *) MKSH_A_PURE;
+static void utfincptr(const char *, mksh_ari_t *);
+
+/* UTFMODE functions */
+static int
+utflen(const char *s)
+{
+ size_t n;
+
+ if (UTFMODE) {
+ n = 0;
+ while (*s) {
+ s += utf_ptradj(s);
+ ++n;
+ }
+ } else
+ n = strlen(s);
+
+ if (n > 2147483647)
+ n = 2147483647;
+ return ((int)n);
+}
+
+static void
+utfincptr(const char *s, mksh_ari_t *lp)
+{
+ const char *cp = s;
+
+ while ((*lp)--)
+ cp += utf_ptradj(cp);
+ *lp = cp - s;
+}
+
+/* compile and expand word */
+char *
+substitute(const char *cp, int f)
+{
+ struct source *s, *sold;
+
+ sold = source;
+ s = pushs(SWSTR, ATEMP);
+ s->start = s->str = cp;
+ source = s;
+ if (yylex(ONEWORD) != LWORD)
+ internal_errorf(Tbadsubst);
+ source = sold;
+ afree(s, ATEMP);
+ return (evalstr(yylval.cp, f));
+}
+
+/*
+ * expand arg-list
+ */
+char **
+eval(const char **ap, int f)
+{
+ XPtrV w;
+
+ if (*ap == NULL) {
+ union mksh_ccphack vap;
+
+ vap.ro = ap;
+ return (vap.rw);
+ }
+ XPinit(w, 32);
+ /* space for shell name */
+ XPput(w, NULL);
+ while (*ap != NULL)
+ expand(*ap++, &w, f);
+ XPput(w, NULL);
+ return ((char **)XPclose(w) + 1);
+}
+
+/*
+ * expand string
+ */
+char *
+evalstr(const char *cp, int f)
+{
+ XPtrV w;
+ char *dp = null;
+
+ XPinit(w, 1);
+ expand(cp, &w, f);
+ if (XPsize(w))
+ dp = *XPptrv(w);
+ XPfree(w);
+ return (dp);
+}
+
+/*
+ * expand string - return only one component
+ * used from iosetup to expand redirection files
+ */
+char *
+evalonestr(const char *cp, int f)
+{
+ XPtrV w;
+ char *rv;
+
+ XPinit(w, 1);
+ expand(cp, &w, f);
+ switch (XPsize(w)) {
+ case 0:
+ rv = null;
+ break;
+ case 1:
+ rv = (char *) *XPptrv(w);
+ break;
+ default:
+ rv = evalstr(cp, f & ~DOGLOB);
+ break;
+ }
+ XPfree(w);
+ return (rv);
+}
+
+/* for nested substitution: ${var:=$var2} */
+typedef struct SubType {
+ struct tbl *var; /* variable for ${var..} */
+ struct SubType *prev; /* old type */
+ struct SubType *next; /* poped type (to avoid re-allocating) */
+ size_t base; /* start position of expanded word */
+ unsigned short stype; /* [=+-?%#] action after expanded word */
+ short f; /* saved value of f (DOPAT, etc) */
+ uint8_t quotep; /* saved value of quote (for ${..[%#]..}) */
+ uint8_t quotew; /* saved value of quote (for ${..[+-=]..}) */
+} SubType;
+
+void
+expand(
+ /* input word */
+ const char *ccp,
+ /* output words */
+ XPtrV *wp,
+ /* DO* flags */
+ int f)
+{
+ int c = 0;
+ /* expansion type */
+ int type;
+ /* quoted */
+ int quote = 0;
+ /* destination string and live pointer */
+ XString ds;
+ char *dp;
+ /* source */
+ const char *sp;
+ /* second pass flags */
+ int fdo;
+ /* have word */
+ int word;
+ /* field splitting of parameter/command substitution */
+ int doblank;
+ /* expansion variables */
+ Expand x = {
+ NULL, { NULL }, NULL, 0
+ };
+ SubType st_head, *st;
+ /* record number of trailing newlines in COMSUB */
+ int newlines = 0;
+ bool saw_eq, make_magic;
+ unsigned int tilde_ok;
+ size_t len;
+ char *cp;
+
+ if (ccp == NULL)
+ internal_errorf("expand(NULL)");
+ /* for alias, readonly, set, typeset commands */
+ if ((f & DOVACHECK) && is_wdvarassign(ccp)) {
+ f &= ~(DOVACHECK | DOBLANK | DOGLOB | DOTILDE);
+ f |= DOASNTILDE | DOSCALAR;
+ }
+ if (Flag(FNOGLOB))
+ f &= ~DOGLOB;
+ if (Flag(FMARKDIRS))
+ f |= DOMARKDIRS;
+ if (Flag(FBRACEEXPAND) && (f & DOGLOB))
+ f |= DOBRACE;
+
+ /* init destination string */
+ Xinit(ds, dp, 128, ATEMP);
+ type = XBASE;
+ sp = ccp;
+ fdo = 0;
+ saw_eq = false;
+ /* must be 1/0 */
+ tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0;
+ doblank = 0;
+ make_magic = false;
+ word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
+ /* clang doesn't know OSUBST comes before CSUBST */
+ memset(&st_head, 0, sizeof(st_head));
+ st = &st_head;
+
+ while (/* CONSTCOND */ 1) {
+ Xcheck(ds, dp);
+
+ switch (type) {
+ case XBASE:
+ /* original prefixed string */
+ c = ord(*sp++);
+ switch (c) {
+ case EOS:
+ c = 0;
+ break;
+ case CHAR:
+ c = ord(*sp++);
+ break;
+ case QCHAR:
+ /* temporary quote */
+ quote |= 2;
+ c = ord(*sp++);
+ break;
+ case OQUOTE:
+ if (word != IFS_WORD)
+ word = IFS_QUOTE;
+ tilde_ok = 0;
+ quote = 1;
+ continue;
+ case CQUOTE:
+ if (word == IFS_QUOTE)
+ word = IFS_WORD;
+ quote = st->quotew;
+ continue;
+ case COMASUB:
+ case COMSUB:
+ case FUNASUB:
+ case FUNSUB:
+ case VALSUB:
+ tilde_ok = 0;
+ if (f & DONTRUNCOMMAND) {
+ word = IFS_WORD;
+ *dp++ = '$';
+ switch (c) {
+ case COMASUB:
+ case COMSUB:
+ *dp++ = '(';
+ c = ORD(')');
+ break;
+ case FUNASUB:
+ case FUNSUB:
+ case VALSUB:
+ *dp++ = '{';
+ *dp++ = c == VALSUB ? '|' : ' ';
+ c = ORD('}');
+ break;
+ }
+ while (*sp != '\0') {
+ Xcheck(ds, dp);
+ *dp++ = *sp++;
+ }
+ if ((unsigned int)c == ORD(/*{*/'}'))
+ *dp++ = ';';
+ *dp++ = c;
+ } else {
+ type = comsub(&x, sp, c);
+ if (type != XBASE && (f & DOBLANK))
+ doblank++;
+ sp = strnul(sp) + 1;
+ newlines = 0;
+ }
+ continue;
+ case EXPRSUB:
+ tilde_ok = 0;
+ if (f & DONTRUNCOMMAND) {
+ word = IFS_WORD;
+ *dp++ = '$'; *dp++ = '('; *dp++ = '(';
+ while (*sp != '\0') {
+ Xcheck(ds, dp);
+ *dp++ = *sp++;
+ }
+ *dp++ = ')'; *dp++ = ')';
+ } else {
+ struct tbl v;
+
+ v.flag = DEFINED|ISSET|INTEGER;
+ /* not default */
+ v.type = 10;
+ v.name[0] = '\0';
+ v_evaluate(&v, substitute(sp, 0),
+ KSH_UNWIND_ERROR, true);
+ sp = strnul(sp) + 1;
+ x.str = str_val(&v);
+ type = XSUB;
+ if (f & DOBLANK)
+ doblank++;
+ }
+ continue;
+ 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...
+ */
+ /* skip the { or x (}) */
+ const char *varname = ++sp;
+ unsigned int stype;
+ int slen = 0;
+
+ /* skip variable */
+ sp = cstrchr(sp, '\0') + 1;
+ type = varsub(&x, varname, sp, &stype, &slen);
+ if (type < 0) {
+ char *beg, *end, *str;
+ unwind_substsyn:
+ /* restore sp */
+ sp = varname - 2;
+ beg = wdcopy(sp, ATEMP);
+ end = (wdscan(cstrchr(sp, '\0') + 1,
+ CSUBST) - sp) + beg;
+ /* ({) the } or x is already skipped */
+ if (end < wdscan(beg, EOS))
+ *end = EOS;
+ str = snptreef(NULL, 64, Tf_S, beg);
+ afree(beg, ATEMP);
+ errorf(Tf_sD_s, str, Tbadsubst);
+ }
+ if (f & DOBLANK)
+ doblank++;
+ tilde_ok = 0;
+ if (word == IFS_QUOTE && type != XNULLSUB)
+ word = IFS_WORD;
+ if (type == XBASE) {
+ /* expand? */
+ if (!st->next) {
+ SubType *newst;
+
+ newst = alloc(sizeof(SubType), ATEMP);
+ newst->next = NULL;
+ newst->prev = st;
+ st->next = newst;
+ }
+ st = st->next;
+ st->stype = stype;
+ st->base = Xsavepos(ds, dp);
+ st->f = f;
+ if (x.var == vtemp) {
+ st->var = tempvar(vtemp->name);
+ st->var->flag &= ~INTEGER;
+ /* can't fail here */
+ setstr(st->var,
+ str_val(x.var),
+ KSH_RETURN_ERROR | 0x4);
+ } else
+ st->var = x.var;
+
+ st->quotew = st->quotep = quote;
+ /* skip qualifier(s) */
+ if (stype)
+ sp += slen;
+ switch (stype & STYPE_SINGLE) {
+ case ORD('#') | STYPE_AT:
+ case ORD('Q') | STYPE_AT:
+ break;
+ case ORD('0'): {
+ char *beg, *mid, *end, *stg;
+ mksh_ari_t from = 0, num = -1, flen, finc = 0;
+
+ beg = wdcopy(sp, ATEMP);
+ mid = beg + (wdscan(sp, ADELIM) - sp);
+ stg = beg + (wdscan(sp, CSUBST) - sp);
+ mid[-2] = EOS;
+ if (ord(mid[-1]) == ORD(/*{*/ '}')) {
+ sp += mid - beg - 1;
+ end = NULL;
+ } else {
+ end = mid +
+ (wdscan(mid, ADELIM) - mid);
+ if (ord(end[-1]) != ORD(/*{*/ '}'))
+ /* more than max delimiters */
+ goto unwind_substsyn;
+ end[-2] = EOS;
+ sp += end - beg - 1;
+ }
+ evaluate(substitute(stg = wdstrip(beg, 0), 0),
+ &from, KSH_UNWIND_ERROR, true);
+ afree(stg, ATEMP);
+ if (end) {
+ evaluate(substitute(stg = wdstrip(mid, 0), 0),
+ &num, KSH_UNWIND_ERROR, true);
+ afree(stg, ATEMP);
+ }
+ afree(beg, ATEMP);
+ beg = str_val(st->var);
+ flen = utflen(beg);
+ if (from < 0) {
+ if (-from < flen)
+ finc = flen + from;
+ } else
+ finc = from < flen ? from : flen;
+ if (UTFMODE)
+ utfincptr(beg, &finc);
+ beg += finc;
+ flen = utflen(beg);
+ if (num < 0 || num > flen)
+ num = flen;
+ if (UTFMODE)
+ utfincptr(beg, &num);
+ strndupx(x.str, beg, num, ATEMP);
+ goto do_CSUBST;
+ }
+ case ORD('/') | STYPE_AT:
+ case ORD('/'): {
+ char *s, *p, *d, *sbeg;
+ char *pat = NULL, *rrep;
+ char fpat = 0, *tpat1, *tpat2;
+ char *ws, *wpat, *wrep, tch;
+ size_t rreplen;
+
+ s = ws = wdcopy(sp, ATEMP);
+ p = s + (wdscan(sp, ADELIM) - sp);
+ d = s + (wdscan(sp, CSUBST) - sp);
+ p[-2] = EOS;
+ if (ord(p[-1]) == ORD(/*{*/ '}'))
+ d = NULL;
+ else
+ d[-2] = EOS;
+ sp += (d ? d : p) - s - 1;
+ if (!(stype & STYPE_MASK) &&
+ s[0] == CHAR &&
+ ctype(s[1], C_SUB2))
+ fpat = s[1];
+ wpat = s + (fpat ? 2 : 0);
+ if (!(wrep = d ? p : NULL)) {
+ rrep = null;
+ rreplen = 0;
+ } else if (!(stype & STYPE_AT)) {
+ rrep = evalstr(wrep,
+ DOTILDE | DOSCALAR);
+ rreplen = strlen(rrep);
+ } else {
+ rrep = NULL;
+ /* shut up GCC */
+ rreplen = 0;
+ }
+
+ /* prepare string on which to work */
+ strdupx(s, str_val(st->var), ATEMP);
+ sbeg = s;
+ again_search:
+ pat = evalstr(wpat,
+ DOTILDE | DOSCALAR | DOPAT);
+ /* check for special cases */
+ if (!*pat && !fpat) {
+ /*
+ * empty unanchored
+ * pattern => reject
+ */
+ goto no_repl;
+ }
+ if ((stype & STYPE_MASK) &&
+ gmatchx(null, pat, false)) {
+ /*
+ * pattern matches empty
+ * string => don't loop
+ */
+ stype &= ~STYPE_MASK;
+ }
+
+ /* first see if we have any match at all */
+ if (ord(fpat) == ORD('#')) {
+ /* anchor at the beginning */
+ tpat1 = shf_smprintf("%s%c*", pat, MAGIC);
+ tpat2 = tpat1;
+ } else if (ord(fpat) == ORD('%')) {
+ /* anchor at the end */
+ tpat1 = shf_smprintf("%c*%s", MAGIC, pat);
+ tpat2 = pat;
+ } else {
+ /* float */
+ tpat1 = shf_smprintf("%c*%s%c*", MAGIC, pat, MAGIC);
+ tpat2 = tpat1 + 2;
+ }
+ again_repl:
+ /*
+ * 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))
+ goto end_repl;
+ d = strnul(s);
+ /* now anchor the beginning of the match */
+ if (ord(fpat) != ORD('#'))
+ while (sbeg <= d) {
+ if (gmatchx(sbeg, tpat2, false))
+ break;
+ else
+ sbeg++;
+ }
+ /* now anchor the end of the match */
+ p = d;
+ if (ord(fpat) != ORD('%'))
+ while (p >= sbeg) {
+ bool gotmatch;
+
+ c = ord(*p);
+ *p = '\0';
+ gotmatch = tobool(gmatchx(sbeg, pat, false));
+ *p = c;
+ if (gotmatch)
+ break;
+ p--;
+ }
+
+ /* record partial string as match */
+ tch = *p;
+ *p = '\0';
+ record_match(sbeg);
+ *p = tch;
+ /* get replacement string, if necessary */
+ if ((stype & STYPE_AT) &&
+ rrep != null) {
+ afree(rrep, ATEMP);
+ /* might access match! */
+ rrep = evalstr(wrep,
+ DOTILDE | DOSCALAR);
+ rreplen = strlen(rrep);
+ }
+
+ /*
+ * string:
+ * |--------|---------|-------\0
+ * s n1 sbeg n2 p n3 d
+ *
+ * replacement:
+ * |------------|
+ * rrep rreplen
+ */
+
+ /* move strings around and replace */
+ {
+ size_t n1 = sbeg - s;
+ size_t n2 = p - sbeg;
+ size_t n3 = d - p;
+ /* move part3 to the front, OR… */
+ if (rreplen < n2)
+ memmove(sbeg + rreplen,
+ p, n3 + 1);
+ /* … adjust size, move to back */
+ if (rreplen > n2) {
+ s = aresize(s,
+ n1 + rreplen + n3 + 1,
+ ATEMP);
+ memmove(s + n1 + rreplen,
+ s + n1 + n2,
+ n3 + 1);
+ }
+ /* insert replacement */
+ if (rreplen)
+ memcpy(s + n1, rrep, rreplen);
+ /* continue after the place */
+ sbeg = s + n1 + rreplen;
+ }
+ if (stype & STYPE_AT) {
+ afree(tpat1, ATEMP);
+ afree(pat, ATEMP);
+ goto again_search;
+ } else if (stype & STYPE_DBL)
+ goto again_repl;
+ end_repl:
+ afree(tpat1, ATEMP);
+ x.str = s;
+ no_repl:
+ afree(pat, ATEMP);
+ if (rrep != null)
+ afree(rrep, ATEMP);
+ afree(ws, ATEMP);
+ goto do_CSUBST;
+ }
+ case ORD('#'):
+ case ORD('%'):
+ /* ! DOBLANK,DOBRACE */
+ f = (f & DONTRUNCOMMAND) |
+ DOPAT | DOTILDE |
+ DOTEMP | DOSCALAR;
+ tilde_ok = 1;
+ st->quotew = quote = 0;
+ /*
+ * Prepend open pattern (so |
+ * in a trim will work as
+ * expected)
+ */
+ if (!Flag(FSH)) {
+ *dp++ = MAGIC;
+ *dp++ = ORD(0x80 | '@');
+ }
+ break;
+ case ORD('='):
+ /*
+ * Tilde expansion for string
+ * variables in POSIX mode is
+ * governed by Austinbug 351.
+ * In non-POSIX mode historic
+ * ksh behaviour (enable it!)
+ * us followed.
+ * Not doing tilde expansion
+ * for integer variables is a
+ * non-POSIX thing - makes
+ * sense though, since ~ is
+ * a arithmetic operator.
+ */
+ if (!(x.var->flag & INTEGER))
+ f |= DOASNTILDE | DOTILDE;
+ f |= DOTEMP | DOSCALAR;
+ /*
+ * These will be done after the
+ * value has been assigned.
+ */
+ f &= ~(DOBLANK|DOGLOB|DOBRACE);
+ tilde_ok = 1;
+ break;
+ case ORD('?'):
+ if (*sp == CSUBST)
+ errorf("%s: parameter null or not set",
+ st->var->name);
+ f &= ~DOBLANK;
+ f |= DOTEMP;
+ /* FALLTHROUGH */
+ default:
+ /* '-' '+' '?' */
+ if (quote)
+ word = IFS_WORD;
+ else if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
+ /* Enable tilde expansion */
+ tilde_ok = 1;
+ f |= DOTILDE;
+ }
+ } else
+ /* skip word */
+ sp += wdscan(sp, CSUBST) - sp;
+ continue;
+ }
+ case CSUBST:
+ /* only get here if expanding word */
+ do_CSUBST:
+ /* ({) 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 & STYPE_SINGLE) {
+ case ORD('#'):
+ case ORD('%'):
+ if (!Flag(FSH)) {
+ /* Append end-pattern */
+ *dp++ = MAGIC;
+ *dp++ = ')';
+ }
+ *dp = '\0';
+ dp = Xrestpos(ds, dp, st->base);
+ /*
+ * Must use st->var since calling
+ * global would break things
+ * like x[i+=1].
+ */
+ x.str = trimsub(str_val(st->var),
+ dp, st->stype);
+ if (x.str[0] != '\0') {
+ word = IFS_IWS;
+ type = XSUB;
+ } else if (quote) {
+ word = IFS_WORD;
+ type = XSUB;
+ } else {
+ if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
+ type = XNULLSUB;
+ }
+ if (f & DOBLANK)
+ doblank++;
+ st = st->prev;
+ continue;
+ case ORD('='):
+ /*
+ * 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
+ * global would cause with things
+ * like x[i+=1] to be evaluated twice.
+ */
+ /*
+ * Note: not exported by FEXPORT
+ * in AT&T ksh.
+ */
+ /*
+ * XXX POSIX says readonly is only
+ * fatal for special builtins (setstr
+ * does readonly check).
+ */
+ len = strlen(dp) + 1;
+ setstr(st->var,
+ debunk(alloc(len, ATEMP),
+ dp, len), KSH_UNWIND_ERROR);
+ x.str = str_val(st->var);
+ type = XSUB;
+ if (f & DOBLANK)
+ doblank++;
+ st = st->prev;
+ word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
+ continue;
+ case ORD('?'):
+ dp = Xrestpos(ds, dp, st->base);
+
+ errorf(Tf_sD_s, st->var->name,
+ debunk(dp, dp, strlen(dp) + 1));
+ break;
+ case ORD('#') | STYPE_AT:
+ x.str = shf_smprintf("%08X",
+ (unsigned int)hash(str_val(st->var)));
+ goto common_CSUBST;
+ case ORD('Q') | STYPE_AT: {
+ struct shf shf;
+
+ shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
+ print_value_quoted(&shf, str_val(st->var));
+ x.str = shf_sclose(&shf);
+ goto common_CSUBST;
+ }
+ case ORD('0'):
+ case ORD('/') | STYPE_AT:
+ case ORD('/'):
+ common_CSUBST:
+ dp = Xrestpos(ds, dp, st->base);
+ type = XSUB;
+ word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
+ if (f & DOBLANK)
+ doblank++;
+ st = st->prev;
+ continue;
+ /* default: '-' '+' */
+ }
+ st = st->prev;
+ type = XBASE;
+ continue;
+
+ case OPAT:
+ /* open pattern: *(foo|bar) */
+ /* Next char is the type of pattern */
+ make_magic = true;
+ c = ord(*sp++) | 0x80U;
+ break;
+
+ case SPAT:
+ /* pattern separator (|) */
+ make_magic = true;
+ c = ORD('|');
+ break;
+
+ case CPAT:
+ /* close pattern */
+ make_magic = true;
+ c = ORD(/*(*/ ')');
+ break;
+ }
+ break;
+
+ case XNULLSUB:
+ /*
+ * 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--;
+ if (dp == Xstring(ds, dp) && word != IFS_WORD)
+ word = IFS_IWS;
+ }
+ continue;
+
+ case XSUBPAT:
+ case XSUBPATMID:
+ XSUBPAT_beg:
+ switch ((c = ord(*x.str++))) {
+ case 0:
+ goto XSUB_end;
+ case ORD('\\'):
+ if ((c = ord(*x.str)) == 0)
+ /* keep backslash at EOS */
+ c = ORD('\\');
+ else
+ ++x.str;
+ quote |= 2;
+ break;
+ /* ctype(c, C_PATMO) */
+ case ORD('!'):
+ case ORD('*'):
+ case ORD('+'):
+ case ORD('?'):
+ case ORD('@'):
+ if (ord(*x.str) == ORD('('/*)*/)) {
+ ++x.str;
+ c |= 0x80U;
+ make_magic = true;
+ }
+ break;
+ case ORD('('):
+ c = ORD(' ') | 0x80U;
+ /* FALLTHROUGH */
+ case ORD('|'):
+ case ORD(')'):
+ make_magic = true;
+ break;
+ }
+ break;
+
+ case XSUB:
+ if (!quote && (f & DODBMAGIC)) {
+ const char *cs = x.str;
+ int level = 0;
+
+ while ((c = *cs++))
+ switch (c) {
+ case '\\':
+ if ((c = *cs))
+ ++cs;
+ break;
+ case ORD('('):
+ ++level;
+ break;
+ case ORD(')'):
+ --level;
+ break;
+ }
+ /* balanced parentheses? */
+ if (!level) {
+ type = XSUBPAT;
+ goto XSUBPAT_beg;
+ }
+ }
+ /* FALLTHROUGH */
+ case XSUBMID:
+ if ((c = ord(*x.str++)) == 0) {
+ XSUB_end:
+ type = XBASE;
+ if (f & DOBLANK)
+ doblank--;
+ continue;
+ }
+ break;
+
+ case XARGSEP:
+ type = XARG;
+ quote = 1;
+ /* FALLTHROUGH */
+ case XARG:
+ if ((c = ord(*x.str++)) == '\0') {
+ /*
+ * force null words to be created so
+ * set -- "" 2 ""; echo "$@" will do
+ * the right thing
+ */
+ if (quote && x.split)
+ word = IFS_WORD;
+ if ((x.str = *x.u.strv++) == NULL) {
+ type = XBASE;
+ if (f & DOBLANK)
+ doblank--;
+ continue;
+ }
+ c = ord(ifs0);
+ if ((f & DOHEREDOC)) {
+ /* pseudo-field-split reliably */
+ if (c == 0)
+ c = ORD(' ');
+ break;
+ }
+ if ((f & DOSCALAR)) {
+ /* do not field-split */
+ if (x.split) {
+ c = ORD(' ');
+ break;
+ }
+ if (c == 0)
+ continue;
+ }
+ if (c == 0) {
+ if (quote && !x.split)
+ continue;
+ if (!quote && word == IFS_WS)
+ continue;
+ /* this is so we don't terminate */
+ c = ORD(' ');
+ /* now force-emit a word */
+ goto emit_word;
+ }
+ if (quote && x.split) {
+ /* terminate word for "$@" */
+ type = XARGSEP;
+ quote = 0;
+ }
+ }
+ break;
+
+ case XCOM:
+ if (x.u.shf == NULL) {
+ /* $(<...) failed */
+ subst_exstat = 1;
+ /* fake EOF */
+ c = -1;
+ } else if (newlines) {
+ /* spit out saved NLs */
+ c = ORD('\n');
+ --newlines;
+ } else {
+ while ((c = shf_getc(x.u.shf)) == 0 ||
+ cinttype(c, C_NL)) {
+#ifdef MKSH_WITH_TEXTMODE
+ if (c == ORD('\r')) {
+ c = shf_getc(x.u.shf);
+ switch (c) {
+ case ORD('\n'):
+ break;
+ default:
+ shf_ungetc(c, x.u.shf);
+ /* FALLTHROUGH */
+ case -1:
+ c = ORD('\r');
+ break;
+ }
+ }
+#endif
+ if (c == ORD('\n'))
+ /* save newlines */
+ newlines++;
+ }
+ if (newlines && c != -1) {
+ shf_ungetc(c, x.u.shf);
+ c = ORD('\n');
+ --newlines;
+ }
+ }
+ if (c == -1) {
+ newlines = 0;
+ if (x.u.shf)
+ shf_close(x.u.shf);
+ if (x.split)
+ subst_exstat = waitlast();
+ type = XBASE;
+ if (f & DOBLANK)
+ doblank--;
+ continue;
+ }
+ break;
+ }
+
+ /* 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:
+ * | value of c
+ * word | ws nws 0
+ * -----------------------------------
+ * IFS_WORD w/WS w/NWS w
+ * IFS_WS -/WS -/NWS -
+ * IFS_NWS -/NWS w/NWS -
+ * IFS_IWS -/WS w/NWS -
+ * (w means generate a word)
+ */
+ if ((word == IFS_WORD) || (word == IFS_QUOTE) || (c &&
+ (word == IFS_IWS || word == IFS_NWS) &&
+ !ctype(c, C_IFSWS))) {
+ emit_word:
+ if (f & DOHERESTR)
+ *dp++ = '\n';
+ *dp++ = '\0';
+ cp = Xclose(ds, dp);
+ if (fdo & DOBRACE)
+ /* also does globbing */
+ alt_expand(wp, cp, cp,
+ cp + Xlength(ds, (dp - 1)),
+ fdo | (f & DOMARKDIRS));
+ else if (fdo & DOGLOB)
+ glob(cp, wp, tobool(f & DOMARKDIRS));
+ else if ((f & DOPAT) || !(fdo & DOMAGIC))
+ XPput(*wp, cp);
+ else
+ XPput(*wp, debunk(cp, cp,
+ strlen(cp) + 1));
+ fdo = 0;
+ saw_eq = false;
+ /* must be 1/0 */
+ tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0;
+ if (c == 0)
+ return;
+ Xinit(ds, dp, 128, ATEMP);
+ } else if (c == 0) {
+ return;
+ } else if (isXSUB(type) && ctype(c, C_IFS) &&
+ !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) {
+ *(cp = alloc(1, ATEMP)) = '\0';
+ XPput(*wp, cp);
+ ++type;
+ }
+ if (word != IFS_NWS)
+ word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
+ } else {
+ if (isXSUB(type))
+ ++type;
+
+ /* age tilde_ok info - ~ code tests second bit */
+ tilde_ok <<= 1;
+ /* mark any special second pass chars */
+ if (!quote)
+ switch (ord(c)) {
+ case ORD('['):
+ case ORD('!'):
+ case ORD('-'):
+ case ORD(']'):
+ /*
+ * For character classes - doesn't hurt
+ * to have magic !,-,]s outside of
+ * [...] expressions.
+ */
+ if (f & (DOPAT | DOGLOB)) {
+ fdo |= DOMAGIC;
+ if ((unsigned int)c == ORD('['))
+ fdo |= f & DOGLOB;
+ *dp++ = MAGIC;
+ }
+ break;
+ case ORD('*'):
+ case ORD('?'):
+ if (f & (DOPAT | DOGLOB)) {
+ fdo |= DOMAGIC | (f & DOGLOB);
+ *dp++ = MAGIC;
+ }
+ break;
+ case ORD('{'):
+ case ORD('}'):
+ case ORD(','):
+ if ((f & DOBRACE) &&
+ (ord(c) == ORD('{' /*}*/) ||
+ (fdo & DOBRACE))) {
+ fdo |= DOBRACE|DOMAGIC;
+ *dp++ = MAGIC;
+ }
+ break;
+ case ORD('='):
+ /* Note first unquoted = for ~ */
+ if (!(f & DOTEMP) && (!Flag(FPOSIX) ||
+ (f & DOASNTILDE)) && !saw_eq) {
+ saw_eq = true;
+ tilde_ok = 1;
+ }
+ break;
+ case ORD(':'):
+ /* : */
+ /* Note unquoted : for ~ */
+ if (!(f & DOTEMP) && (f & DOASNTILDE))
+ tilde_ok = 1;
+ break;
+ case ORD('~'):
+ /*
+ * tilde_ok is reset whenever
+ * any of ' " $( $(( ${ } are seen.
+ * Note that tilde_ok must be preserved
+ * through the sequence ${A=a=}~
+ */
+ if (type == XBASE &&
+ (f & (DOTILDE | DOASNTILDE)) &&
+ (tilde_ok & 2)) {
+ const char *tcp;
+ char *tdp = dp;
+
+ tcp = maybe_expand_tilde(sp,
+ &ds, &tdp,
+ tobool(f & DOASNTILDE));
+ if (tcp) {
+ if (dp != tdp)
+ word = IFS_WORD;
+ dp = tdp;
+ sp = tcp;
+ continue;
+ }
+ }
+ break;
+ }
+ else
+ /* undo temporary */
+ quote &= ~2;
+
+ if (make_magic) {
+ make_magic = false;
+ fdo |= DOMAGIC | (f & DOGLOB);
+ *dp++ = MAGIC;
+ } else if (ISMAGIC(c)) {
+ fdo |= DOMAGIC;
+ *dp++ = MAGIC;
+ }
+ /* save output char */
+ *dp++ = c;
+ word = IFS_WORD;
+ }
+ }
+}
+
+static bool
+hasnonempty(const char **strv)
+{
+ size_t i = 0;
+
+ while (strv[i])
+ if (*strv[i++])
+ return (true);
+ return (false);
+}
+
+/*
+ * Prepare to generate the string returned by ${} substitution.
+ */
+static int
+varsub(Expand *xp, const char *sp, const char *word,
+ /* becomes qualifier type */
+ unsigned int *stypep,
+ /* becomes qualifier type len (=, :=, etc.) valid iff *stypep != 0 */
+ int *slenp)
+{
+ unsigned int c;
+ int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */
+ unsigned int stype; /* substitution type */
+ int slen = 0;
+ const char *p;
+ struct tbl *vp;
+ bool zero_ok = false;
+ int sc;
+ XPtrV wv;
+
+ if ((stype = ord(sp[0])) == '\0')
+ /* Bad variable name */
+ return (-1);
+
+ xp->var = NULL;
+
+ /* entirety of named array? */
+ if ((p = cstrchr(sp, '[')) && (sc = ord(p[1])) &&
+ ord(p[2]) == ORD(']'))
+ /* keep p (for ${!foo[1]} below)! */
+ switch (sc) {
+ case ORD('*'):
+ sc = 3;
+ break;
+ case ORD('@'):
+ sc = 7;
+ break;
+ default:
+ /* bit2 = @, bit1 = array, bit0 = enabled */
+ sc = 0;
+ }
+ else
+ /* $* and $@ checked below */
+ sc = 0;
+
+ /*-
+ * ${%var}, string width (-U: screen columns, +U: octets)
+ * ${#var}, string length (-U: characters, +U: octets) or array size
+ * ${!var}, variable name
+ * ${*…} -> set flag for argv
+ * ${@…} -> set flag for argv
+ */
+ if (ctype(stype, C_SUB2 | CiVAR1)) {
+ switch (stype) {
+ case ORD('*'):
+ if (!sc)
+ sc = 1;
+ goto nopfx;
+ case ORD('@'):
+ if (!sc)
+ sc = 5;
+ goto nopfx;
+ }
+ /* varname required */
+ if ((c = ord(sp[1])) == '\0') {
+ if (stype == ORD('%'))
+ /* $% */
+ return (-1);
+ /* $# or $! */
+ goto nopfx;
+ }
+ /* can’t have any modifiers for ${#…} or ${%…} or ${!…} */
+ if (*word != CSUBST)
+ return (-1);
+ /* check for argv past prefix */
+ if (!sc) switch (c) {
+ case ORD('*'):
+ sc = 1;
+ break;
+ case ORD('@'):
+ sc = 5;
+ break;
+ }
+ /* skip past prefix */
+ ++sp;
+ /* determine result */
+ switch (stype) {
+ case ORD('!'):
+ if (sc & 2) {
+ stype = 0;
+ XPinit(wv, 32);
+ vp = global(arrayname(sp));
+ do {
+ if (vp->flag & ISSET)
+ XPput(wv, shf_smprintf(Tf_lu,
+ arrayindex(vp)));
+ } while ((vp = vp->u.array));
+ goto arraynames;
+ }
+ xp->var = global(sp);
+ /* use saved p from above */
+ xp->str = p ? shf_smprintf("%s[%lu]", xp->var->name,
+ arrayindex(xp->var)) : xp->var->name;
+ break;
+#ifdef DEBUG
+ default:
+ internal_errorf("stype mismatch");
+ /* NOTREACHED */
+#endif
+ case ORD('%'):
+ /* cannot do this on an array */
+ if (sc)
+ return (-1);
+ p = str_val(global(sp));
+ zero_ok = p != null;
+ /* partial utf_mbswidth reimplementation */
+ sc = 0;
+ while (*p) {
+ if (!UTFMODE ||
+ (wv.len = utf_mbtowc(&c, p)) == (size_t)-1)
+ /* not UTFMODE or not UTF-8 */
+ c = rtt2asc(*p++);
+ else
+ /* UTFMODE and UTF-8 */
+ p += wv.len;
+ /* c == char or wchar at p++ */
+ if ((slen = utf_wcwidth(c)) == -1) {
+ /* 646, 8859-1, 10646 C0/C1 */
+ sc = -1;
+ break;
+ }
+ sc += slen;
+ }
+ if (0)
+ /* FALLTHROUGH */
+ case ORD('#'):
+ switch (sc & 3) {
+ case 3:
+ vp = global(arrayname(sp));
+ if (vp->flag & (ISSET|ARRAY))
+ zero_ok = true;
+ sc = 0;
+ do {
+ if (vp->flag & ISSET)
+ sc++;
+ } while ((vp = vp->u.array));
+ break;
+ case 1:
+ sc = e->loc->argc;
+ break;
+ default:
+ p = str_val(global(sp));
+ zero_ok = p != null;
+ sc = utflen(p);
+ break;
+ }
+ /* ${%var} also here */
+ if (Flag(FNOUNSET) && sc == 0 && !zero_ok)
+ errorf(Tf_parm, sp);
+ xp->str = shf_smprintf(Tf_d, sc);
+ break;
+ }
+ /* unqualified variable/string substitution */
+ *stypep = 0;
+ return (XSUB);
+ }
+ nopfx:
+
+ /* check for qualifiers in word part */
+ stype = 0;
+ /*slen = 0;*/
+ c = word[/*slen +*/ 0] == CHAR ? ord(word[/*slen +*/ 1]) : 0;
+ if (c == ORD(':')) {
+ slen += 2;
+ stype = STYPE_DBL;
+ c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
+ }
+ if (!stype && c == ORD('/')) {
+ slen += 2;
+ stype = c;
+ if (word[slen] == ADELIM &&
+ ord(word[slen + 1]) == c) {
+ slen += 2;
+ stype |= STYPE_DBL;
+ }
+ } else if (stype == STYPE_DBL && (c == ORD(' ') || c == ORD('0'))) {
+ stype |= ORD('0');
+ } else if (ctype(c, C_SUB1)) {
+ slen += 2;
+ stype |= c;
+ } else if (ctype(c, C_SUB2)) {
+ /* Note: ksh88 allows :%, :%%, etc */
+ slen += 2;
+ stype = c;
+ if (word[slen + 0] == CHAR && ord(word[slen + 1]) == c) {
+ stype |= STYPE_DBL;
+ slen += 2;
+ }
+ } else if (c == ORD('@')) {
+ /* @x where x is command char */
+ switch (c = ord(word[slen + 2]) == CHAR ?
+ ord(word[slen + 3]) : 0) {
+ case ORD('#'):
+ case ORD('/'):
+ case ORD('Q'):
+ break;
+ default:
+ return (-1);
+ }
+ stype |= STYPE_AT | c;
+ slen += 4;
+ } else if (stype)
+ /* : is not ok */
+ return (-1);
+ if (!stype && *word != CSUBST)
+ return (-1);
+
+ if (!sc) {
+ xp->var = global(sp);
+ xp->str = str_val(xp->var);
+ /* can't assign things like $! or $1 */
+ if ((stype & STYPE_SINGLE) == ORD('=') &&
+ !*xp->str && ctype(*sp, C_VAR1 | C_DIGIT))
+ return (-1);
+ state = XSUB;
+ } else {
+ /* can’t assign/trim a vector (yet) */
+ switch (stype & STYPE_SINGLE) {
+ case ORD('-'):
+ case ORD('+'):
+ /* allowed ops */
+ case 0:
+ /* or no ops */
+ break;
+ /* case ORD('='):
+ case ORD('?'):
+ case ORD('#'):
+ case ORD('%'):
+ case ORD('/'):
+ case ORD('/') | STYPE_AT:
+ case ORD('0'):
+ case ORD('#') | STYPE_AT:
+ case ORD('Q') | STYPE_AT:
+ */ default:
+ return (-1);
+ }
+ /* do what we can */
+ if (sc & 2) {
+ XPinit(wv, 32);
+ vp = global(arrayname(sp));
+ do {
+ if (vp->flag & ISSET)
+ XPput(wv, str_val(vp));
+ } while ((vp = vp->u.array));
+ arraynames:
+ if ((c = (XPsize(wv) == 0)))
+ XPfree(wv);
+ else {
+ XPput(wv, NULL);
+ xp->u.strv = (const char **)XPptrv(wv);
+ }
+ } else {
+ if ((c = (e->loc->argc == 0)))
+ xp->var = global(sp);
+ else
+ xp->u.strv = (const char **)e->loc->argv + 1;
+ /* POSIX 2009? */
+ zero_ok = true;
+ }
+ /* have we got any elements? */
+ if (c) {
+ /* no */
+ xp->str = null;
+ state = sc & 4 ? XNULLSUB : XSUB;
+ } else {
+ /* yes → load first */
+ xp->str = *xp->u.strv++;
+ /* $@ or ${foo[@]} */
+ xp->split = tobool(sc & 4);
+ state = XARG;
+ }
+ }
+
+ c = stype & STYPE_CHAR;
+ /* test the compiler's code generator */
+ if ((!(stype & STYPE_AT) && (ctype(c, C_SUB2) ||
+ (((stype & STYPE_DBL) ? *xp->str == '\0' : xp->str == null) &&
+ (state != XARG || (ifs0 || xp->split ?
+ (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
+ ctype(c, C_EQUAL | C_MINUS | C_QUEST) : c == ORD('+')))) ||
+ stype == (ORD('0') | STYPE_DBL) ||
+ stype == (ORD('#') | STYPE_AT) ||
+ stype == (ORD('Q') | STYPE_AT) ||
+ (stype & STYPE_CHAR) == ORD('/'))
+ /* expand word instead of variable value */
+ state = XBASE;
+ if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
+ (ctype(c, C_SUB2) || (state != XBASE && c != ORD('+'))))
+ errorf(Tf_parm, sp);
+ *stypep = stype;
+ *slenp = slen;
+ return (state);
+}
+
+/*
+ * Run the command in $(...) and read its output.
+ */
+static int
+comsub(Expand *xp, const char *cp, int fn)
+{
+ Source *s, *sold;
+ struct op *t;
+ struct shf *shf;
+ bool doalias = false;
+ uint8_t old_utfmode = UTFMODE;
+
+ switch (fn) {
+ case COMASUB:
+ fn = COMSUB;
+ if (0)
+ /* FALLTHROUGH */
+ case FUNASUB:
+ fn = FUNSUB;
+ doalias = true;
+ }
+
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = cp;
+ sold = source;
+ t = compile(s, true, doalias);
+ afree(s, ATEMP);
+ source = sold;
+
+ UTFMODE = old_utfmode;
+
+ if (t == NULL)
+ return (XBASE);
+
+ /* no waitlast() unless specifically enabled later */
+ xp->split = false;
+
+ if (t->type == TCOM &&
+ *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+ /* $(<file) */
+ struct ioword *io = *t->ioact;
+ char *name;
+
+ switch (io->ioflag & IOTYPE) {
+ case IOREAD:
+ shf = shf_open(name = evalstr(io->ioname, DOTILDE),
+ O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC);
+ if (shf == NULL)
+ warningf(!Flag(FTALKING), Tf_sD_s_sD_s,
+ name, Tcant_open, "$(<...) input",
+ cstrerror(errno));
+ break;
+ case IOHERE:
+ if (!herein(io, &name)) {
+ xp->str = name;
+ /* as $(…) requires, trim trailing newlines */
+ name = strnul(name);
+ while (name > xp->str && name[-1] == '\n')
+ --name;
+ *name = '\0';
+ return (XSUB);
+ }
+ shf = NULL;
+ break;
+ default:
+ errorf(Tf_sD_s, T_funny_command,
+ snptreef(NULL, 32, Tft_R, io));
+ }
+ } else if (fn == FUNSUB) {
+ int ofd1;
+ struct temp *tf = NULL;
+
+ /*
+ * create a temporary file, open for reading and writing,
+ * with an shf open for reading (buffered) but yet unused
+ */
+ maketemp(ATEMP, TT_FUNSUB, &tf);
+ if (!tf->shf) {
+ errorf(Tf_temp,
+ Tcreate, tf->tffn, cstrerror(errno));
+ }
+ /* extract shf from temporary file, unlink and free it */
+ shf = tf->shf;
+ unlink(tf->tffn);
+ afree(tf, ATEMP);
+ /* save stdout and let it point to the tempfile */
+ ofd1 = savefd(1);
+ ksh_dup2(shf_fileno(shf), 1, false);
+ /*
+ * run tree, with output thrown into the tempfile,
+ * in a new function block
+ */
+ valsub(t, NULL);
+ subst_exstat = exstat & 0xFF;
+ /* rewind the tempfile and restore regular stdout */
+ lseek(shf_fileno(shf), (off_t)0, SEEK_SET);
+ restfd(1, ofd1);
+ } else if (fn == VALSUB) {
+ xp->str = valsub(t, ATEMP);
+ subst_exstat = exstat & 0xFF;
+ return (XSUB);
+ } else {
+ int ofd1, pv[2];
+
+ openpipe(pv);
+ shf = shf_fdopen(pv[0], SHF_RD, NULL);
+ ofd1 = savefd(1);
+ if (pv[1] != 1) {
+ ksh_dup2(pv[1], 1, false);
+ close(pv[1]);
+ }
+ execute(t, XXCOM | XPIPEO | XFORK, NULL);
+ restfd(1, ofd1);
+ startlast();
+ /* waitlast() */
+ xp->split = true;
+ }
+
+ xp->u.shf = shf;
+ return (XCOM);
+}
+
+/*
+ * perform #pattern and %pattern substitution in ${}
+ */
+static char *
+trimsub(char *str, char *pat, int how)
+{
+ char *end = strnul(str);
+ char *p, c;
+
+ switch (how & (STYPE_CHAR | STYPE_DBL)) {
+ case ORD('#'):
+ /* shortest match at beginning */
+ for (p = str; p <= end; p += utf_ptradj(p)) {
+ c = *p; *p = '\0';
+ if (gmatchx(str, pat, false)) {
+ record_match(str);
+ *p = c;
+ return (p);
+ }
+ *p = c;
+ }
+ break;
+ case ORD('#') | STYPE_DBL:
+ /* longest match at beginning */
+ for (p = end; p >= str; p--) {
+ c = *p; *p = '\0';
+ if (gmatchx(str, pat, false)) {
+ record_match(str);
+ *p = c;
+ return (p);
+ }
+ *p = c;
+ }
+ break;
+ case ORD('%'):
+ /* shortest match at end */
+ p = end;
+ while (p >= str) {
+ if (gmatchx(p, pat, false))
+ goto trimsub_match;
+ if (UTFMODE) {
+ char *op = p;
+ while ((p-- > str) && ((rtt2asc(*p) & 0xC0) == 0x80))
+ ;
+ if ((p < str) || (p + utf_ptradj(p) != op))
+ p = op - 1;
+ } else
+ --p;
+ }
+ break;
+ case ORD('%') | STYPE_DBL:
+ /* longest match at end */
+ for (p = str; p <= end; p++)
+ if (gmatchx(p, pat, false)) {
+ trimsub_match:
+ record_match(p);
+ strndupx(end, str, p - str, ATEMP);
+ return (end);
+ }
+ break;
+ }
+
+ /* no match, return string */
+ return (str);
+}
+
+/*
+ * glob
+ * Name derived from V6's /etc/glob, the program that expanded filenames.
+ */
+
+/* XXX cp not const 'cause slashes are temporarily replaced with NULs... */
+static void
+glob(char *cp, XPtrV *wp, bool markdirs)
+{
+ int oldsize = XPsize(*wp);
+
+ if (glob_str(cp, wp, markdirs) == 0)
+ XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
+ else
+ qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize,
+ sizeof(void *), ascpstrcmp);
+}
+
+#define GF_NONE 0
+#define GF_EXCHECK BIT(0) /* do existence check on file */
+#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
+ * the number of matches found.
+ */
+int
+glob_str(char *cp, XPtrV *wp, bool markdirs)
+{
+ int oldsize = XPsize(*wp);
+ XString xs;
+ char *xp;
+
+ Xinit(xs, xp, 256, ATEMP);
+ globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE);
+ Xfree(xs, xp);
+
+ return (XPsize(*wp) - oldsize);
+}
+
+static void
+globit(XString *xs, /* dest string */
+ char **xpp, /* ptr to dest end */
+ char *sp, /* source path */
+ XPtrV *wp, /* output list */
+ int check) /* GF_* flags */
+{
+ char *np; /* next source component */
+ char *xp = *xpp;
+ char *se;
+ char odirsep;
+
+ /* 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
+ * 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.
+ * Symlinks make things a bit tricky...
+ */
+ if ((check & GF_EXCHECK) ||
+ ((check & GF_MARKDIR) && (check & GF_GLOBBED))) {
+#define stat_check() (stat_done ? stat_done : (stat_done = \
+ stat(Xstring(*xs, xp), &statb) < 0 ? -1 : 1))
+ struct stat lstatb, statb;
+ /* -1: failed, 1 ok, 0 not yet done */
+ int stat_done = 0;
+
+ if (mksh_lstat(Xstring(*xs, xp), &lstatb) < 0)
+ return;
+ /*
+ * special case for systems which strip trailing
+ * slashes from regular files (eg, /etc/passwd/).
+ * SunOS 4.1.3 does this...
+ */
+ if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) &&
+ mksh_cdirsep(xp[-1]) && !S_ISDIR(lstatb.st_mode) &&
+ (!S_ISLNK(lstatb.st_mode) ||
+ stat_check() < 0 || !S_ISDIR(statb.st_mode)))
+ return;
+ /*
+ * Possibly tack on a trailing / if there isn't already
+ * one and if the file is a directory or a symlink to a
+ * directory
+ */
+ if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) &&
+ xp > Xstring(*xs, xp) && !mksh_cdirsep(xp[-1]) &&
+ (S_ISDIR(lstatb.st_mode) ||
+ (S_ISLNK(lstatb.st_mode) && stat_check() > 0 &&
+ S_ISDIR(statb.st_mode)))) {
+ *xp++ = '/';
+ *xp = '\0';
+ }
+ }
+ strndupx(np, Xstring(*xs, xp), Xlength(*xs, xp), ATEMP);
+ XPput(*wp, np);
+ return;
+ }
+
+ if (xp > Xstring(*xs, xp))
+ *xp++ = '/';
+ while (mksh_cdirsep(*sp)) {
+ Xcheck(*xs, xp);
+ *xp++ = *sp++;
+ }
+ np = mksh_sdirsep(sp);
+ if (np != NULL) {
+ se = np;
+ /* don't assume '/', can be multiple kinds */
+ odirsep = *np;
+ *np++ = '\0';
+ } else {
+ odirsep = '\0'; /* keep gcc quiet */
+ se = strnul(sp);
+ }
+
+
+ /*
+ * 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
+ * permission should be required (as per POSIX)).
+ */
+ if (!has_globbing(sp)) {
+ XcheckN(*xs, xp, se - sp + 1);
+ debunk(xp, sp, Xnleft(*xs, xp));
+ xp = strnul(xp);
+ *xpp = xp;
+ globit(xs, xpp, np, wp, check);
+ } else {
+ DIR *dirp;
+ struct dirent *d;
+ char *name;
+ size_t len, prefix_len;
+
+ /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */
+ *xp = '\0';
+ prefix_len = Xlength(*xs, xp);
+ dirp = opendir(prefix_len ? Xstring(*xs, xp) : Tdot);
+ if (dirp == NULL)
+ goto Nodir;
+ while ((d = readdir(dirp)) != NULL) {
+ name = d->d_name;
+ if (name[0] == '.' &&
+ (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
+ /* always ignore . and .. */
+ continue;
+ if ((*name == '.' && *sp != '.') ||
+ !gmatchx(name, sp, true))
+ continue;
+
+ len = strlen(d->d_name) + 1;
+ XcheckN(*xs, xp, len);
+ memcpy(xp, name, len);
+ *xpp = xp + len - 1;
+ globit(xs, xpp, np, wp, (check & GF_MARKDIR) |
+ GF_GLOBBED | (np ? GF_EXCHECK : GF_NONE));
+ xp = Xstring(*xs, xp) + prefix_len;
+ }
+ closedir(dirp);
+ Nodir:
+ ;
+ }
+
+ if (np != NULL)
+ *--np = odirsep;
+}
+
+/* remove MAGIC from string */
+char *
+debunk(char *dp, const char *sp, size_t dlen)
+{
+ char *d;
+ const char *s;
+
+ if ((s = cstrchr(sp, MAGIC))) {
+ if (s - sp >= (ssize_t)dlen)
+ return (dp);
+ memmove(dp, sp, s - sp);
+ for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++)
+ if (!ISMAGIC(*s) || !(*++s & 0x80) ||
+ !ctype(*s & 0x7F, C_PATMO | C_SPC))
+ *d++ = *s;
+ else {
+ /* extended pattern operators: *+?@! */
+ if ((*s & 0x7f) != ' ')
+ *d++ = *s & 0x7f;
+ if (d - dp < (ssize_t)dlen)
+ *d++ = '(';
+ }
+ *d = '\0';
+ } else if (dp != sp)
+ strlcpy(dp, sp, dlen);
+ return (dp);
+}
+
+/*
+ * 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.
+ */
+static const char *
+maybe_expand_tilde(const char *p, XString *dsp, char **dpp, bool isassign)
+{
+ XString ts;
+ char *dp = *dpp;
+ char *tp;
+ const char *r;
+
+ Xinit(ts, tp, 16, ATEMP);
+ /* : only for DOASNTILDE form */
+ while (p[0] == CHAR && /* not cdirsep */ p[1] != '/' &&
+ (!isassign || p[1] != ':')) {
+ Xcheck(ts, tp);
+ *tp++ = p[1];
+ p += 2;
+ }
+ *tp = '\0';
+ r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ?
+ do_tilde(Xstring(ts, tp)) : NULL;
+ Xfree(ts, tp);
+ if (r) {
+ while (*r) {
+ Xcheck(*dsp, dp);
+ if (ISMAGIC(*r))
+ *dp++ = MAGIC;
+ *dp++ = *r++;
+ }
+ *dpp = dp;
+ r = p;
+ }
+ return (r);
+}
+
+/*
+ * tilde expansion
+ *
+ * based on a version by Arnold Robbins
+ */
+char *
+do_tilde(char *cp)
+{
+ char *dp = null;
+#ifndef MKSH_NOPWNAM
+ bool do_simplify = true;
+#endif
+
+ if (cp[0] == '\0')
+ dp = str_val(global("HOME"));
+ else if (cp[0] == '+' && cp[1] == '\0')
+ dp = str_val(global(TPWD));
+ else if (ksh_isdash(cp))
+ dp = str_val(global(TOLDPWD));
+#ifndef MKSH_NOPWNAM
+ else {
+ dp = homedir(cp);
+ do_simplify = false;
+ }
+#endif
+
+ /* if parameters aren't set, don't expand ~ */
+ if (dp == NULL || dp == null)
+ return (NULL);
+
+ /* simplify parameters as if cwd upon entry */
+#ifndef MKSH_NOPWNAM
+ if (do_simplify)
+#endif
+ {
+ strdupx(dp, dp, ATEMP);
+ simplify_path(dp);
+ }
+ return (dp);
+}
+
+#ifndef MKSH_NOPWNAM
+/*
+ * map userid to user's home directory.
+ * note that 4.3's getpw adds more than 6K to the shell,
+ * and the YP version probably adds much more.
+ * we might consider our own version of getpwnam() to keep the size down.
+ */
+static char *
+homedir(char *name)
+{
+ struct tbl *ap;
+
+ ap = ktenter(&homedirs, name, hash(name));
+ if (!(ap->flag & ISSET)) {
+ struct passwd *pw;
+
+ pw = getpwnam(name);
+ if (pw == NULL)
+ return (NULL);
+ strdupx(ap->val.s, pw->pw_dir, APERM);
+ ap->flag |= DEFINED|ISSET|ALLOC;
+ }
+ return (ap->val.s);
+}
+#endif
+
+static void
+alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
+{
+ unsigned int count = 0;
+ char *brace_start, *brace_end, *comma = NULL;
+ char *field_start;
+ char *p = exp_start;
+
+ /* search for open brace */
+ while ((p = strchr(p, MAGIC)) && ord(p[1]) != ORD('{' /*}*/))
+ p += 2;
+ brace_start = p;
+
+ /* find matching close brace, if any */
+ if (p) {
+ comma = NULL;
+ count = 1;
+ p += 2;
+ while (*p && count) {
+ if (ISMAGIC(*p++)) {
+ if (ord(*p) == ORD('{' /*}*/))
+ ++count;
+ else if (ord(*p) == ORD(/*{*/ '}'))
+ --count;
+ else if (*p == ',' && count == 1)
+ comma = p;
+ ++p;
+ }
+ }
+ }
+ /* no valid expansions... */
+ if (!p || count != 0) {
+ /*
+ * 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. }
+ */
+ if (fdo & DOGLOB)
+ glob(start, wp, tobool(fdo & DOMARKDIRS));
+ else
+ XPput(*wp, debunk(start, start, end - start));
+ return;
+ }
+ brace_end = p;
+ if (!comma) {
+ alt_expand(wp, start, brace_end, end, fdo);
+ return;
+ }
+
+ /* expand expression */
+ field_start = brace_start + 2;
+ count = 1;
+ for (p = brace_start + 2; p != brace_end; p++) {
+ if (ISMAGIC(*p)) {
+ if (ord(*++p) == ORD('{' /*}*/))
+ ++count;
+ else if ((ord(*p) == ORD(/*{*/ '}') && --count == 0) ||
+ (*p == ',' && count == 1)) {
+ 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;
+ news = alloc(l1 + l2 + l3 + 1, ATEMP);
+ memcpy(news, start, l1);
+ memcpy(news + l1, field_start, l2);
+ memcpy(news + l1 + l2, brace_end, l3);
+ news[l1 + l2 + l3] = '\0';
+ alt_expand(wp, news, news + l1,
+ news + l1 + l2 + l3, fdo);
+ field_start = p + 1;
+ }
+ }
+ }
+ return;
+}
+
+/* helper function due to setjmp/longjmp woes */
+static char *
+valsub(struct op *t, Area *ap)
+{
+ char * volatile cp = NULL;
+ struct tbl * volatile vp = NULL;
+
+ newenv(E_FUNC);
+ newblock();
+ if (ap)
+ vp = local(TREPLY, false);
+ if (!kshsetjmp(e->jbuf))
+ execute(t, XXCOM | XERROK, NULL);
+ if (vp)
+ strdupx(cp, str_val(vp), ap);
+ quitenv(NULL);
+
+ return (cp);
+}
diff --git a/shells/mksh/files/exec.c b/shells/mksh/files/exec.c
new file mode 100644
index 00000000000..9d1b69b8d5c
--- /dev/null
+++ b/shells/mksh/files/exec.c
@@ -0,0 +1,1872 @@
+/* $OpenBSD: exec.c,v 1.52 2015/09/10 22:48:58 nicm Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.223 2020/04/07 23:14:41 tg Exp $");
+
+#ifndef MKSH_DEFAULT_EXECSHELL
+#define MKSH_DEFAULT_EXECSHELL MKSH_UNIXROOT "/bin/sh"
+#endif
+
+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 **, const char *, bool);
+static int iosetup(struct ioword *, struct tbl *);
+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 *);
+/* XXX: horrible kludge to fit within the framework */
+static void plain_fmt_entry(char *, size_t, unsigned int, const void *);
+static void select_fmt_entry(char *, size_t, unsigned int, const void *);
+
+/*
+ * execute command tree
+ */
+int
+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 = NULL;
+ char ** volatile up;
+ const char *s, *ccp;
+ struct ioword **iowp;
+ struct tbl *tp = NULL;
+
+ if (t == NULL)
+ return (0);
+
+ /* Caller doesn't care if XERROK should propagate. */
+ if (xerrok == NULL)
+ xerrok = &dummy;
+
+ if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE)
+ /* run in sub-process */
+ return (exchild(t, flags & ~XTIME, xerrok, -1));
+
+ newenv(E_EXEC);
+ 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
+ * null commands (see comexec() and c_eval()) and by c_set().
+ */
+ subst_exstat = 0;
+
+ /* for $LINENO */
+ current_lineno = t->lineno;
+
+ /* check if this is 'var=<<EOF' */
+ if (
+ /* we have zero arguments, i.e. no program 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]->ioflag & 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) ||
+ /* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR &&
+ ccp[3] == '=' && ccp[4] == EOS))) {
+ char *cp, *dp;
+
+ if ((rv = herein(t->ioact[0], &cp) /*? 1 : 0*/))
+ cp = NULL;
+ strdup2x(dp, evalstr(t->vars[0], DOASNTILDE | DOSCALAR),
+ rv ? null : cp);
+ typeset(dp, Flag(FEXPORT) ? EXPORT : 0, 0, 0, 0);
+ /* free the expanded value */
+ afree(cp, APERM);
+ afree(dp, ATEMP);
+ goto Break;
+ }
+
+ /*
+ * POSIX says expand command words first, then redirections,
+ * and assignments last..
+ */
+ up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
+ if (flags & XTIME)
+ /* Allow option parsing (bizarre, but POSIX) */
+ timex_hook(t, &up);
+ ap = (const char **)up;
+ if (ap[0])
+ tp = findcom(ap[0], FC_BI | FC_FUNC);
+ }
+ flags &= ~XTIME;
+
+ if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
+ 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
+ * cause (non-interactive) shell to exit.
+ */
+ if (tp && tp->type == CSHELL &&
+ (tp->flag & SPEC_BI))
+ errorfz();
+ /* Deal with FERREXIT, quitenv(), etc. */
+ goto Break;
+ }
+ }
+
+ switch (t->type) {
+ case TCOM:
+ rv = comexec(t, tp, (const char **)ap, flags, xerrok);
+ break;
+
+ case TPAREN:
+ rv = execute(t->left, flags | XFORK, xerrok);
+ break;
+
+ case TPIPE:
+ flags |= XFORK;
+ flags &= ~XEXEC;
+ e->savefd[0] = savefd(0);
+ e->savefd[1] = savefd(1);
+ while (t->type == TPIPE) {
+ openpipe(pv);
+ /* stdout of curr */
+ ksh_dup2(pv[1], 1, false);
+ /**
+ * Let exchild() close pv[0] in child
+ * (if this isn't done, commands like
+ * (: ; cat /etc/termcap) | sleep 1
+ * will hang forever).
+ */
+ exchild(t->left, flags | XPIPEO | XCCLOSE,
+ NULL, pv[0]);
+ /* stdin of next */
+ ksh_dup2(pv[0], 0, false);
+ closepipe(pv);
+ flags |= XPIPEI;
+ t = t->right;
+ }
+ /* 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 | XPIPEST, xerrok, 0);
+ if (!(flags&XBGND) && !(flags&XXCOM))
+ rv = i;
+ break;
+
+ case TLIST:
+ while (t->type == TLIST) {
+ execute(t->left, flags & XERROK, NULL);
+ t = t->right;
+ }
+ rv = execute(t, flags & XERROK, xerrok);
+ break;
+
+ case TCOPROC: {
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigset_t omask;
+
+ /*
+ * Block sigchild as we are using things changed in the
+ * signal handler
+ */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ e->type = E_ERRH;
+ if ((i = kshsetjmp(e->jbuf))) {
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ quitenv(NULL);
+ unwind(i);
+ /* NOTREACHED */
+ }
+#endif
+ /* Already have a (live) co-process? */
+ if (coproc.job && coproc.write >= 0)
+ errorf("coprocess already exists");
+
+ /* Can we re-use the existing co-process pipe? */
+ coproc_cleanup(true);
+
+ /* do this before opening pipes, in case these fail */
+ e->savefd[0] = savefd(0);
+ e->savefd[1] = savefd(1);
+
+ openpipe(pv);
+ if (pv[0] != 0) {
+ ksh_dup2(pv[0], 0, false);
+ close(pv[0]);
+ }
+ coproc.write = pv[1];
+ coproc.job = NULL;
+
+ if (coproc.readw >= 0)
+ ksh_dup2(coproc.readw, 1, false);
+ else {
+ openpipe(pv);
+ coproc.read = pv[0];
+ ksh_dup2(pv[1], 1, false);
+ /* 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);
+ /* no more need for error handler */
+ e->type = E_EXEC;
+#endif
+
+ /*
+ * exchild() closes coproc.* in child after fork,
+ * will also increment coproc.njobs when the
+ * job is actually created.
+ */
+ flags &= ~XEXEC;
+ exchild(t->left, flags | XBGND | XFORK | XCOPROC | XCCLOSE,
+ NULL, coproc.readw);
+ break;
+ }
+
+ case TASYNC:
+ /*
+ * XXX non-optimal, I think - "(foo &)", forks for (),
+ * forks again for async... parent should optimise
+ * this to "foo &"...
+ */
+ rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok);
+ break;
+
+ case TOR:
+ case TAND:
+ rv = execute(t->left, XERROK, NULL);
+ if ((rv == 0) == (t->type == TAND))
+ rv = execute(t->right, flags & XERROK, xerrok);
+ else {
+ flags |= XERROK;
+ if (xerrok)
+ *xerrok = 1;
+ }
+ break;
+
+ case TBANG:
+ rv = !execute(t->right, XERROK, xerrok);
+ flags |= XERROK;
+ if (xerrok)
+ *xerrok = 1;
+ break;
+
+ case TDBRACKET: {
+ Test_env te;
+
+ te.flags = TEF_DBRACKET;
+ te.pos.wp = t->args;
+ te.isa = dbteste_isa;
+ te.getopnd = dbteste_getopnd;
+ te.eval = test_eval;
+ te.error = dbteste_error;
+
+ rv = test_parse(&te);
+ break;
+ }
+
+ case TFOR:
+ case TSELECT: {
+ volatile bool is_first = true;
+
+ ap = (t->vars == NULL) ? e->loc->argv + 1 :
+ (const char **)eval((const char **)t->vars,
+ DOBLANK | DOGLOB | DOTILDE);
+ e->type = E_LOOP;
+ while ((i = kshsetjmp(e->jbuf))) {
+ if ((e->flags&EF_BRKCONT_PASS) ||
+ (i != LBREAK && i != LCONTIN)) {
+ quitenv(NULL);
+ unwind(i);
+ } else if (i == LBREAK) {
+ rv = 0;
+ goto Break;
+ }
+ }
+ /* 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 {
+ do_TSELECT:
+ if ((ccp = do_selectargs(ap, is_first))) {
+ is_first = false;
+ setstr(global(t->str), ccp, KSH_UNWIND_ERROR);
+ execute(t->left, flags & XERROK, xerrok);
+ goto do_TSELECT;
+ }
+ rv = 1;
+ }
+ break;
+ }
+
+ case TWHILE:
+ case TUNTIL:
+ e->type = E_LOOP;
+ while ((i = kshsetjmp(e->jbuf))) {
+ if ((e->flags&EF_BRKCONT_PASS) ||
+ (i != LBREAK && i != LCONTIN)) {
+ quitenv(NULL);
+ unwind(i);
+ } else if (i == LBREAK) {
+ rv = 0;
+ goto Break;
+ }
+ }
+ /* in case of a continue */
+ rv = 0;
+ while ((execute(t->left, XERROK, NULL) == 0) ==
+ (t->type == TWHILE))
+ rv = execute(t->right, flags & XERROK, xerrok);
+ break;
+
+ case TIF:
+ case TELIF:
+ if (t->right == NULL)
+ /* should be error */
+ break;
+ rv = execute(execute(t->left, XERROK, NULL) == 0 ?
+ t->right->left : t->right->right, flags & XERROK, xerrok);
+ break;
+
+ case TCASE:
+ i = 0;
+ ccp = evalstr(t->str, DOTILDE | DOSCALAR);
+ 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))) {
+ record_match(ccp);
+ 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:
+ rv = execute(t->left, flags & XERROK, xerrok);
+ break;
+
+ case TFUNCT:
+ rv = define(t->str, t);
+ break;
+
+ case TTIME:
+ /*
+ * 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 */
+ up = makenv();
+ restoresigs();
+ cleanup_proc_env();
+ /* I/O redirection cleanup to be done in child process */
+ if (!Flag(FPOSIX) && !Flag(FSH) && t->left->ioact != NULL)
+ for (iowp = t->left->ioact; *iowp != NULL; iowp++)
+ if ((*iowp)->ioflag & IODUPSELF)
+ fcntl((*iowp)->unit, F_SETFD, 0);
+ /* try to execute */
+ {
+ union mksh_ccphack cargs;
+
+ cargs.ro = t->args;
+ execve(t->str, cargs.rw, up);
+ rv = errno;
+ }
+ if (rv == ENOEXEC)
+ scriptexec(t, (const char **)up);
+ else
+ errorfx(126, Tf_sD_s, t->str, cstrerror(rv));
+ }
+ Break:
+ exstat = rv & 0xFF;
+ if (vp_pipest->flag & INT_L) {
+ unset(vp_pipest, 1);
+ vp_pipest->flag = DEFINED | ISSET | INTEGER | RDONLY |
+ ARRAY | INT_U | INT_L;
+ vp_pipest->val.i = rv;
+ }
+
+ /* restores IO */
+ quitenv(NULL);
+ if ((flags&XEXEC))
+ /* exit child */
+ unwind(LEXIT);
+ if (rv != 0 && !(flags & XERROK) &&
+ (xerrok == NULL || !*xerrok)) {
+ trapsig(ksh_SIGERR);
+ if (Flag(FERREXIT))
+ unwind(LERREXT);
+ }
+ return (rv);
+}
+
+/*
+ * execute simple command
+ */
+
+static int
+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;
+ /* Must be static (XXX but why?) */
+ static struct op texec;
+ int type_flags;
+ bool resetspec;
+ int fcflags = FC_BI | FC_FUNC | FC_PATH;
+ struct block *l_expand, *l_assign;
+ int optc;
+ const char *exec_argv0 = NULL;
+ bool exec_clrenv = false;
+
+ /* snag the last argument for $_ */
+ if (Flag(FTALKING) && *(lastp = ap)) {
+ /*
+ * 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()?.
+ */
+ while (*++lastp)
+ ;
+ /* setstr() can't fail here */
+ setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp,
+ KSH_RETURN_ERROR);
+ }
+
+ /**
+ * 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
+ * PATH).
+ * Odd cases:
+ * FOO=bar exec >/dev/null FOO is kept but not exported
+ * FOO=bar exec foobar FOO is exported
+ * FOO=bar command exec >/dev/null FOO is neither kept nor exported
+ * FOO=bar command FOO is neither kept nor exported
+ * PATH=... foobar use new PATH in foobar search
+ */
+ resetspec = false;
+ while (tp && tp->type == CSHELL) {
+ /* undo effects of command */
+ fcflags = FC_BI | FC_FUNC | FC_PATH;
+ if (tp->val.f == c_builtin) {
+ if ((cp = *++ap) == NULL ||
+ (!strcmp(cp, "--") && (cp = *++ap) == NULL)) {
+ tp = NULL;
+ break;
+ }
+ if ((tp = findcom(cp, FC_BI)) == NULL)
+ errorf(Tf_sD_sD_s, Tbuiltin, cp, Tnot_found);
+ if (tp->type == CSHELL && (tp->flag & LOW_BI))
+ break;
+ continue;
+ } else if (tp->val.f == c_exec) {
+ if (ap[1] == NULL)
+ break;
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ while ((optc = ksh_getopt(ap, &builtin_opt, "a:c")) != -1)
+ switch (optc) {
+ case 'a':
+ exec_argv0 = builtin_opt.optarg;
+ break;
+ case 'c':
+ exec_clrenv = true;
+ /* ensure we can actually do this */
+ resetspec = true;
+ break;
+ default:
+ rv = 2;
+ goto Leave;
+ }
+ ap += builtin_opt.optind;
+ flags |= XEXEC;
+ /* POSuX demands ksh88-like behaviour here */
+ if (Flag(FPOSIX))
+ fcflags = FC_PATH;
+ } else if (tp->val.f == c_command) {
+ bool saw_p = false;
+
+ /*
+ * 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 = true;
+ if (optc != -1)
+ /* command -vV or something */
+ break;
+ /* don't look for functions */
+ fcflags = FC_BI | FC_PATH;
+ if (saw_p) {
+ if (Flag(FRESTRICTED)) {
+ warningf(true, Tf_sD_s,
+ "command -p", "restricted");
+ rv = 1;
+ goto Leave;
+ }
+ fcflags |= FC_DEFPATH;
+ }
+ ap += builtin_opt.optind;
+ /*
+ * POSIX says special builtins lose their status
+ * if accessed using command.
+ */
+ resetspec = true;
+ if (!ap[0]) {
+ /* ensure command with no args exits with 0 */
+ subst_exstat = 0;
+ break;
+ }
+ } else if (tp->flag & LOW_BI) {
+ /* if we have any flags, do not use the builtin */
+ if ((ap[1] && ap[1][0] == '-' && ap[1][1] != '\0' &&
+ /* argument, begins with -, is not - or -- */
+ (ap[1][1] != '-' || ap[1][2] != '\0')) ||
+ /* always prefer the external utility */
+ (tp->flag & LOWER_BI)) {
+ struct tbl *ext_cmd;
+
+ ext_cmd = findcom(tp->name, FC_FUNC | FC_PATH);
+ if (ext_cmd && (ext_cmd->type == CFUNC ||
+ (ext_cmd->flag & ISSET)))
+ tp = ext_cmd;
+ }
+ break;
+ } else if (tp->val.f == c_trap) {
+ t->u.evalflags &= ~DOTCOMEXEC;
+ break;
+ } else
+ break;
+ tp = findcom(ap[0], fcflags & (FC_BI | FC_FUNC));
+ }
+ if (t->u.evalflags & DOTCOMEXEC)
+ flags |= XEXEC;
+ l_expand = e->loc;
+ if (!resetspec && (!ap[0] || (tp && (tp->flag & KEEPASN))))
+ type_flags = 0;
+ else {
+ /* create new variable/function block */
+ newblock();
+ /* all functions keep assignments */
+ type_flags = LOCAL | LOCAL_COPY | EXPORT;
+ }
+ l_assign = e->loc;
+ if (exec_clrenv)
+ l_assign->flags |= BF_STOPENV;
+ if (Flag(FEXPORT))
+ type_flags |= EXPORT;
+ if (Flag(FXTRACE))
+ change_xtrace(2, false);
+ for (i = 0; t->vars[i]; i++) {
+ /* do NOT lookup in the new var/fn block just created */
+ e->loc = l_expand;
+ cp = evalstr(t->vars[i], DOASNTILDE | DOSCALAR);
+ e->loc = l_assign;
+ if (Flag(FXTRACE)) {
+ const char *ccp;
+
+ ccp = skip_varname(cp, true);
+ if (*ccp == '+')
+ ++ccp;
+ if (*ccp == '=')
+ ++ccp;
+ shf_write(cp, ccp - cp, shl_xtrace);
+ print_value_quoted(shl_xtrace, ccp);
+ shf_putc(' ', shl_xtrace);
+ }
+ /* but assign in there as usual */
+ typeset(cp, type_flags, 0, 0, 0);
+ }
+
+ if (Flag(FXTRACE)) {
+ change_xtrace(2, false);
+ if (ap[rv = 0]) {
+ xtrace_ap_loop:
+ print_value_quoted(shl_xtrace, ap[rv]);
+ if (ap[++rv]) {
+ shf_putc(' ', shl_xtrace);
+ goto xtrace_ap_loop;
+ }
+ }
+ change_xtrace(1, false);
+ }
+
+ if ((cp = *ap) == NULL) {
+ rv = subst_exstat;
+ goto Leave;
+ } else if (!tp) {
+ if (Flag(FRESTRICTED) && mksh_vdirsep(cp)) {
+ warningf(true, Tf_sD_s, cp, "restricted");
+ rv = 1;
+ goto Leave;
+ }
+ tp = findcom(cp, fcflags);
+ }
+
+ switch (tp->type) {
+
+ /* shell built-in */
+ case CSHELL:
+ do_call_builtin:
+ if (l_expand != l_assign)
+ l_assign->flags |= (tp->flag & NEXTLOC_BI);
+ rv = call_builtin(tp, (const char **)ap, null, resetspec);
+ break;
+
+ /* function call */
+ case CFUNC: {
+ volatile uint32_t old_inuse;
+ const char * volatile old_kshname;
+ volatile uint8_t old_flags[FNFLAGS];
+
+ if (!(tp->flag & ISSET)) {
+ struct tbl *ftp;
+
+ if (!tp->u.fpath) {
+ fpath_error:
+ rv = (tp->u2.errnov == ENOENT) ? 127 : 126;
+ warningf(true, Tf_sD_s_sD_s, cp,
+ Tcant_find, Tfile_fd,
+ cstrerror(tp->u2.errnov));
+ break;
+ }
+ errno = 0;
+ if (include(tp->u.fpath, 0, NULL, false) < 0 ||
+ !(ftp = findfunc(cp, hash(cp), false)) ||
+ !(ftp->flag & ISSET)) {
+ rv = errno;
+ if ((ftp = findcom(cp, FC_BI)) &&
+ (ftp->type == CSHELL) &&
+ (ftp->flag & LOW_BI)) {
+ tp = ftp;
+ goto do_call_builtin;
+ }
+ if (rv) {
+ tp->u2.errnov = rv;
+ cp = tp->u.fpath;
+ goto fpath_error;
+ }
+ warningf(true, Tf_sD_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.
+ */
+ old_kshname = kshname;
+ if (tp->flag & FKSH)
+ kshname = ap[0];
+ else
+ ap[0] = kshname;
+ e->loc->argv = ap;
+ for (i = 0; *ap++ != NULL; i++)
+ ;
+ e->loc->argc = i - 1;
+ /*
+ * ksh-style functions handle getopts sanely,
+ * Bourne/POSIX functions are insane...
+ */
+ if (tp->flag & FKSH) {
+ e->loc->flags |= BF_DOGETOPTS;
+ e->loc->getopts_state = user_opt;
+ getopts_reset(1);
+ }
+
+ for (type_flags = 0; type_flags < FNFLAGS; ++type_flags)
+ old_flags[type_flags] = shell_flags[type_flags];
+ change_xtrace((Flag(FXTRACEREC) ? Flag(FXTRACE) : 0) |
+ ((tp->flag & TRACE) ? 1 : 0), false);
+ old_inuse = tp->flag & FINUSE;
+ tp->flag |= FINUSE;
+
+ e->type = E_FUNC;
+ if (!(i = kshsetjmp(e->jbuf))) {
+ execute(tp->val.t, flags & XERROK, NULL);
+ i = LRETURN;
+ }
+
+ kshname = old_kshname;
+ change_xtrace(old_flags[(int)FXTRACE], false);
+#ifndef MKSH_LEGACY_MODE
+ if (tp->flag & FKSH) {
+ /* Korn style functions restore Flags on return */
+ old_flags[(int)FXTRACE] = Flag(FXTRACE);
+ for (type_flags = 0; type_flags < FNFLAGS; ++type_flags)
+ shell_flags[type_flags] = old_flags[type_flags];
+ }
+#endif
+ tp->flag = (tp->flag & ~FINUSE) | old_inuse;
+
+ /*
+ * Were we deleted while executing? If so, free the
+ * execution tree.
+ */
+ if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
+ if (tp->flag & ALLOC) {
+ tp->flag &= ~ALLOC;
+ tfree(tp->val.t, tp->areap);
+ }
+ tp->flag = 0;
+ }
+ switch (i) {
+ case LRETURN:
+ case LERROR:
+ case LERREXT:
+ rv = exstat & 0xFF;
+ break;
+ case LINTR:
+ case LEXIT:
+ case LLEAVE:
+ case LSHELL:
+ quitenv(NULL);
+ unwind(i);
+ /* NOTREACHED */
+ default:
+ quitenv(NULL);
+ internal_errorf(Tunexpected_type, Tunwind, Tfunction, i);
+ }
+ break;
+ }
+
+ /* executable command */
+ case CEXEC:
+ /* tracked alias */
+ case CTALIAS:
+ if (!(tp->flag&ISSET)) {
+ if (tp->u2.errnov == ENOENT) {
+ rv = 127;
+ warningf(true, Tf_sD_s_s, cp,
+ "inaccessible or", Tnot_found);
+ } else {
+ rv = 126;
+ warningf(true, Tf_sD_sD_s, cp, "can't execute",
+ cstrerror(tp->u2.errnov));
+ }
+ break;
+ }
+
+ /* set $_ to program's full path */
+ /* setstr() can't fail here */
+ setstr(typeset("_", LOCAL | EXPORT, 0, INTEGER, 0),
+ tp->val.s, KSH_RETURN_ERROR);
+
+ /* to fork, we set up a TEXEC node and call execute */
+ texec.type = TEXEC;
+ /* for vistree/dumptree */
+ texec.left = t;
+ texec.str = tp->val.s;
+ texec.args = ap;
+
+ /* in this case we do not fork, of course */
+ if (flags & XEXEC) {
+ if (exec_argv0)
+ texec.args[0] = exec_argv0;
+ j_exit();
+ if (!(flags & XBGND)
+#ifndef MKSH_UNEMPLOYED
+ || Flag(FMONITOR)
+#endif
+ ) {
+ setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG);
+ setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG);
+ }
+ }
+
+ rv = exchild(&texec, flags, xerrok, -1);
+ break;
+ }
+ Leave:
+ if (flags & XEXEC) {
+ exstat = rv & 0xFF;
+ unwind(LEXIT);
+ }
+ return (rv);
+}
+
+static void
+scriptexec(struct op *tp, const char **ap)
+{
+ const char *sh;
+#ifndef MKSH_SMALL
+ int fd;
+ unsigned char buf[68];
+#endif
+ union mksh_ccphack args, cap;
+
+ sh = str_val(global(TEXECSHELL));
+ if (sh && *sh)
+ sh = search_path(sh, path, X_OK, NULL);
+ if (!sh || !*sh)
+ sh = MKSH_DEFAULT_EXECSHELL;
+
+ *tp->args-- = tp->str;
+
+#ifndef MKSH_SMALL
+ if ((fd = binopen2(tp->str, O_RDONLY | O_MAYEXEC)) >= 0) {
+ unsigned char *cp;
+#ifndef MKSH_EBCDIC
+ unsigned short m;
+#endif
+ ssize_t n;
+
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+ setmode(fd, O_TEXT);
+#endif
+ /* read first couple of octets from file */
+ n = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ /* read error or short read? */
+ if (n < 5)
+ goto nomagic;
+ /* terminate buffer */
+ buf[n] = '\0';
+
+ /* skip UTF-8 Byte Order Mark, if present */
+ cp = buf + (n = ((buf[0] == 0xEF) && (buf[1] == 0xBB) &&
+ (buf[2] == 0xBF)) ? 3 : 0);
+
+ /* scan for newline or NUL (end of buffer) */
+ while (!ctype(*cp, C_NL | C_NUL))
+ ++cp;
+ /* if the shebang line is longer than MAXINTERP, bail out */
+ if (!*cp)
+ goto noshebang;
+ /* replace newline by NUL */
+ *cp = '\0';
+
+ /* restore start of shebang position (buf+0 or buf+3) */
+ cp = buf + n;
+ /* bail out if no shebang magic found */
+ if (cp[0] == '#' && cp[1] == '!')
+ cp += 2;
+#ifdef __OS2__
+ else if (!strncmp(cp, Textproc, 7) &&
+ ctype(cp[7], C_BLANK))
+ cp += 8;
+#endif
+ else
+ goto noshebang;
+ /* skip whitespace before shell name */
+ while (ctype(*cp, C_BLANK))
+ ++cp;
+ /* just whitespace on the line? */
+ if (*cp == '\0')
+ goto noshebang;
+ /* no, we actually found an interpreter name */
+ sh = (char *)cp;
+ /* look for end of shell/interpreter name */
+ while (!ctype(*cp, C_BLANK | C_NUL))
+ ++cp;
+ /* any arguments? */
+ if (*cp) {
+ *cp++ = '\0';
+ /* skip spaces before arguments */
+ while (ctype(*cp, C_BLANK))
+ ++cp;
+ /* pass it all in ONE argument (historic reasons) */
+ if (*cp)
+ *tp->args-- = (char *)cp;
+ }
+#ifdef __OS2__
+ /*
+ * On OS/2, the directory structure differs from normal
+ * Unix, which can make many scripts whose shebang
+ * hardcodes the path to an interpreter fail (and there
+ * might be no /usr/bin/env); for user convenience, if
+ * the specified interpreter is not usable, do a PATH
+ * search to find it.
+ */
+ if (mksh_vdirsep(sh) && !search_path(sh, path, X_OK, NULL)) {
+ cp = search_path(_getname(sh), path, X_OK, NULL);
+ if (cp)
+ sh = cp;
+ }
+#endif
+ goto nomagic;
+ noshebang:
+#ifndef MKSH_EBCDIC
+ m = buf[0] << 8 | buf[1];
+ if (m == 0x7F45 && buf[2] == 'L' && buf[3] == 'F')
+ errorf("%s: not executable: %d-bit ELF file", tp->str,
+ 32 * buf[4]);
+ if ((m == /* OMAGIC */ 0407) ||
+ (m == /* NMAGIC */ 0410) ||
+ (m == /* ZMAGIC */ 0413) ||
+ (m == /* QMAGIC */ 0314) ||
+ (m == /* ECOFF_I386 */ 0x4C01) ||
+ (m == /* ECOFF_M68K */ 0x0150 || m == 0x5001) ||
+ (m == /* ECOFF_SH */ 0x0500 || m == 0x0005) ||
+ (m == /* bzip */ 0x425A) || (m == /* "MZ" */ 0x4D5A) ||
+ (m == /* "NE" */ 0x4E45) || (m == /* "LX" */ 0x4C58) ||
+ (m == /* ksh93 */ 0x0B13) || (m == /* LZIP */ 0x4C5A) ||
+ (m == /* xz */ 0xFD37 && buf[2] == 'z' && buf[3] == 'X' &&
+ buf[4] == 'Z') || (m == /* 7zip */ 0x377A) ||
+ (m == /* gzip */ 0x1F8B) || (m == /* .Z */ 0x1F9D))
+ errorf("%s: not executable: magic %04X", tp->str, m);
+#endif
+#ifdef __OS2__
+ cp = _getext(tp->str);
+ if (cp && (!stricmp(cp, ".cmd") || !stricmp(cp, ".bat"))) {
+ /* execute .cmd and .bat with OS2_SHELL, usually CMD.EXE */
+ sh = str_val(global("OS2_SHELL"));
+ *tp->args-- = "/c";
+ /* convert slahes to backslashes */
+ for (cp = tp->str; *cp; cp++) {
+ if (*cp == '/')
+ *cp = '\\';
+ }
+ }
+#endif
+ nomagic:
+ ;
+ }
+#endif
+ args.ro = tp->args;
+ *args.ro = sh;
+
+ cap.ro = ap;
+ execve(args.rw[0], args.rw, cap.rw);
+
+ /* report both the program that was run and the bogus interpreter */
+ errorf(Tf_sD_sD_s, tp->str, sh, cstrerror(errno));
+}
+
+/* actual 'builtin' built-in utility call is handled in comexec() */
+int
+c_builtin(const char **wp)
+{
+ return (call_builtin(get_builtin(*wp), wp, Tbuiltin, false));
+}
+
+struct tbl *
+get_builtin(const char *s)
+{
+ return (s && *s ? ktsearch(&builtins, s, hash(s)) : NULL);
+}
+
+/*
+ * Search function tables for a function. If create set, a table entry
+ * is created if none is found.
+ */
+struct tbl *
+findfunc(const char *name, uint32_t h, bool create)
+{
+ struct block *l;
+ struct tbl *tp = NULL;
+
+ for (l = e->loc; l; l = l->next) {
+ tp = ktsearch(&l->funs, name, h);
+ if (tp)
+ break;
+ if (!l->next && create) {
+ tp = ktenter(&l->funs, name, h);
+ tp->flag = DEFINED;
+ tp->type = CFUNC;
+ tp->val.t = NULL;
+ break;
+ }
+ }
+ return (tp);
+}
+
+/*
+ * define function. Returns 1 if function is being undefined (t == 0) and
+ * function did not exist, returns 0 otherwise.
+ */
+int
+define(const char *name, struct op *t)
+{
+ uint32_t nhash;
+ struct tbl *tp;
+ bool was_set = false;
+
+ nhash = hash(name);
+
+ 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 (tp->flag & FINUSE) {
+ tp->name[0] = '\0';
+ /* ensure it won't be found */
+ tp->flag &= ~DEFINED;
+ tp->flag |= FDELETE;
+ } else
+ break;
+ }
+
+ if (tp->flag & ALLOC) {
+ tp->flag &= ~(ISSET|ALLOC|FKSH);
+ tfree(tp->val.t, tp->areap);
+ }
+
+ if (t == NULL) {
+ /* undefine */
+ ktdelete(tp);
+ return (was_set ? 0 : 1);
+ }
+
+ tp->val.t = tcopy(t->left, tp->areap);
+ tp->flag |= (ISSET|ALLOC);
+ if (t->u.ksh_func)
+ tp->flag |= FKSH;
+
+ return (0);
+}
+
+/*
+ * add builtin
+ */
+const char *
+builtin(const char *name, int (*func) (const char **))
+{
+ struct tbl *tp;
+ uint32_t flag = DEFINED;
+
+ /* see if any flags should be set for this builtin */
+ flags_loop:
+ switch (*name) {
+ case '=':
+ /* command does variable assignment */
+ flag |= KEEPASN;
+ break;
+ case '*':
+ /* POSIX special builtin */
+ flag |= SPEC_BI;
+ break;
+ case '~':
+ /* external utility overrides built-in utility, always */
+ flag |= LOWER_BI;
+ /* FALLTHROUGH */
+ case '!':
+ /* external utility overrides built-in utility, with flags */
+ flag |= LOW_BI;
+ break;
+ case '-':
+ /* is declaration utility if argv[1] is one (POSIX: command) */
+ flag |= DECL_FWDR;
+ break;
+ case '^':
+ /* is declaration utility (POSIX: export, readonly) */
+ flag |= DECL_UTIL;
+ break;
+ case '#':
+ /* is set or shift */
+ flag |= NEXTLOC_BI;
+ break;
+ default:
+ goto flags_seen;
+ }
+ ++name;
+ goto flags_loop;
+ flags_seen:
+
+ /* enter into the builtins hash table */
+ tp = ktenter(&builtins, name, hash(name));
+ tp->flag = flag;
+ tp->type = CSHELL;
+ tp->val.f = func;
+
+ /* return name, for direct builtin call check in main.c */
+ return (name);
+}
+
+/*
+ * find command
+ * either function, hashed command, or built-in (in that order)
+ */
+struct tbl *
+findcom(const char *name, int flags)
+{
+ static struct tbl temp;
+ uint32_t h = hash(name);
+ struct tbl *tp = NULL, *tbi;
+ /* insert if not found */
+ unsigned char insert = Flag(FTRACKALL);
+ /* for function autoloading */
+ char *fpath;
+ union mksh_cchack npath;
+
+ if (mksh_vdirsep(name)) {
+ insert = 0;
+ /* prevent FPATH search below */
+ flags &= ~FC_FUNC;
+ goto Search;
+ }
+ tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL;
+ /*
+ * POSIX says special builtins first, then functions, then
+ * regular builtins, then search path...
+ */
+ if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
+ tp = tbi;
+ if (!tp && (flags & FC_FUNC)) {
+ tp = findfunc(name, h, false);
+ if (tp && !(tp->flag & ISSET)) {
+ if ((fpath = str_val(global(TFPATH))) == null) {
+ tp->u.fpath = NULL;
+ tp->u2.errnov = ENOENT;
+ } else
+ tp->u.fpath = search_path(name, fpath, R_OK,
+ &tp->u2.errnov);
+ }
+ }
+ if (!tp && (flags & FC_NORMBI) && tbi)
+ tp = tbi;
+ if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
+ tp = ktsearch(&taliases, name, h);
+ 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);
+ }
+ tp->flag &= ~ISSET;
+ }
+ }
+
+ Search:
+ if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) &&
+ (flags & FC_PATH)) {
+ if (!tp) {
+ if (insert && !(flags & FC_DEFPATH)) {
+ tp = ktenter(&taliases, name, h);
+ tp->type = CTALIAS;
+ } else {
+ tp = &temp;
+ tp->type = CEXEC;
+ }
+ /* make ~ISSET */
+ tp->flag = DEFINED;
+ }
+ 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)
+ afree(npath.rw, ATEMP);
+ tp->flag |= ISSET|ALLOC;
+ } else if ((flags & FC_FUNC) &&
+ (fpath = str_val(global(TFPATH))) != null &&
+ (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;
+ /* make ~ISSET */
+ tp->flag = DEFINED;
+ tp->u.fpath = npath.ro;
+ }
+ }
+ return (tp);
+}
+
+/*
+ * flush executable commands with relative paths
+ * (just relative or all?)
+ */
+void
+flushcom(bool all)
+{
+ struct tbl *tp;
+ struct tstate ts;
+
+ for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; )
+ if ((tp->flag&ISSET) && (all || !mksh_abspath(tp->val.s))) {
+ if (tp->flag&ALLOC) {
+ tp->flag &= ~(ALLOC|ISSET);
+ afree(tp->val.s, APERM);
+ }
+ tp->flag &= ~ISSET;
+ }
+}
+
+/* check if path is something we want to find */
+int
+search_access(const char *fn, int mode)
+{
+ 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 */
+ int eno;
+
+ eno = errno;
+ return (eno ? eno : EACCES);
+ }
+#ifdef __OS2__
+ /* treat all files as executable on OS/2 */
+ sb.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
+#endif
+ if (mode == X_OK && (!S_ISREG(sb.st_mode) ||
+ !(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))))
+ /* access(2) may say root can execute everything */
+ return (S_ISDIR(sb.st_mode) ? EISDIR : EACCES);
+ return (0);
+}
+
+#ifdef __OS2__
+/* check if path is something we want to find adding executable extensions */
+#define search_access(fn,mode) access_ex((search_access), (fn), (mode))
+#else
+#define search_access(fn,mode) (search_access)((fn), (mode))
+#endif
+
+/*
+ * search for command with PATH
+ */
+const char *
+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;
+ size_t namelen;
+ int ec = 0, ev;
+
+ if (mksh_vdirsep(name)) {
+ if ((ec = search_access(name, mode)) == 0) {
+ search_path_ok:
+ if (errnop)
+ *errnop = 0;
+#ifndef __OS2__
+ return (name);
+#else
+ return (real_exec_name(name));
+#endif
+ }
+ goto search_path_err;
+ }
+
+ namelen = strlen(name) + 1;
+ Xinit(xs, xp, 128, ATEMP);
+
+ sp = lpath;
+ while (sp != NULL) {
+ xp = Xstring(xs, xp);
+ if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
+ p = strnul(sp);
+ if (p != sp) {
+ XcheckN(xs, xp, p - sp);
+ memcpy(xp, sp, p - sp);
+ xp += p - sp;
+#ifdef __OS2__
+ if (xp > Xstring(xs, xp) && mksh_cdirsep(xp[-1]))
+ xp--;
+#endif
+ *xp++ = '/';
+ }
+ sp = p;
+ XcheckN(xs, xp, namelen);
+ memcpy(xp, name, 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);
+}
+
+static int
+call_builtin(struct tbl *tp, const char **wp, const char *where, bool resetspec)
+{
+ int rv;
+
+ if (!tp)
+ internal_errorf(Tf_sD_s, where, wp[0]);
+ builtin_argv0 = wp[0];
+ builtin_spec = tobool(!resetspec && (tp->flag & SPEC_BI));
+ shf_reopen(1, SHF_WR, shl_stdout);
+ shl_stdout_ok = true;
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ rv = (*tp->val.f)(wp);
+ shf_flush(shl_stdout);
+ shl_stdout_ok = false;
+ builtin_argv0 = NULL;
+ builtin_spec = false;
+ return (rv);
+}
+
+/*
+ * set up redirection, saving old fds in e->savefd
+ */
+static int
+iosetup(struct ioword *iop, struct tbl *tp)
+{
+ int u = -1;
+ char *cp = iop->ioname;
+ int iotype = iop->ioflag & IOTYPE;
+ bool do_open = true, do_close = false, do_fstat = false;
+ int flags = 0;
+ struct ioword iotmp;
+ struct stat statb;
+
+ if (iotype != IOHERE)
+ cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0));
+
+ /* Used for tracing and error messages to print expanded cp */
+ iotmp = *iop;
+ iotmp.ioname = (iotype == IOHERE) ? NULL : cp;
+ iotmp.ioflag |= IONAMEXP;
+
+ if (Flag(FXTRACE)) {
+ change_xtrace(2, false);
+ fptreef(shl_xtrace, 0, Tft_R, &iotmp);
+ change_xtrace(1, false);
+ }
+
+ switch (iotype) {
+ case IOREAD:
+ flags = O_RDONLY;
+ break;
+
+ case IOCAT:
+ flags = O_WRONLY | O_APPEND | O_CREAT;
+ break;
+
+ case IOWRITE:
+ if (Flag(FNOCLOBBER) && !(iop->ioflag & IOCLOB)) {
+ /* >file under set -C */
+ if (stat(cp, &statb)) {
+ /* nonexistent file */
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+ } else if (S_ISREG(statb.st_mode)) {
+ /* regular file, refuse clobbering */
+ goto clobber_refused;
+ } else {
+ /*
+ * allow redirections to things
+ * like /dev/null without error
+ */
+ flags = O_WRONLY;
+ /* but check again after opening */
+ do_fstat = true;
+ }
+ } else {
+ /* >|file or set +C */
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ }
+ break;
+
+ case IORDWR:
+ flags = O_RDWR | O_CREAT;
+ break;
+
+ case IOHERE:
+ do_open = false;
+ /* herein() returns -2 if error has been printed */
+ u = herein(iop, NULL);
+ /* cp may have wrong name */
+ break;
+
+ case IODUP: {
+ const char *emsg;
+
+ do_open = false;
+ if (ksh_isdash(cp)) {
+ /* prevent error return below */
+ u = 1009;
+ do_close = true;
+ } else if ((u = check_fd(cp,
+ X_OK | ((iop->ioflag & IORDUP) ? R_OK : W_OK),
+ &emsg)) < 0) {
+ char *sp;
+
+ warningf(true, Tf_sD_s,
+ (sp = snptreef(NULL, 32, Tft_R, &iotmp)), emsg);
+ afree(sp, ATEMP);
+ return (-1);
+ }
+ if (u == (int)iop->unit) {
+ /* "dup from" == "dup to" */
+ iop->ioflag |= IODUPSELF;
+ return (0);
+ }
+ break;
+ }
+ }
+
+ if (do_open) {
+ if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
+ warningf(true, Tf_sD_s, cp, "restricted");
+ return (-1);
+ }
+ u = binopen3(cp, flags, 0666);
+ if (do_fstat && u >= 0) {
+ /* prevent race conditions */
+ if (fstat(u, &statb) || S_ISREG(statb.st_mode)) {
+ close(u);
+ clobber_refused:
+ u = -1;
+ errno = EEXIST;
+ }
+ }
+ }
+ if (u < 0) {
+ /* herein() may already have printed message */
+ if (u == -1) {
+ u = errno;
+ warningf(true, Tf_cant_ss_s,
+#if 0
+ /* can't happen */
+ iotype == IODUP ? "dup" :
+#endif
+ (iotype == IOREAD || iotype == IOHERE) ?
+ Topen : Tcreate, cp, cstrerror(u));
+ }
+ return (-1);
+ }
+ /* Do not save if it has already been redirected (i.e. "cat >x >y"). */
+ if (e->savefd[iop->unit] == 0) {
+ /* If these are the same, it means unit was previously closed */
+ if (u == (int)iop->unit)
+ e->savefd[iop->unit] = -1;
+ else
+ /*
+ * 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
+ * dup2 below and dup2 in restfd() failing).
+ */
+ e->savefd[iop->unit] = savefd(iop->unit);
+ }
+
+ if (do_close)
+ close(iop->unit);
+ else if (u != (int)iop->unit) {
+ if (ksh_dup2(u, iop->unit, true) < 0) {
+ int eno;
+ char *sp;
+
+ eno = errno;
+ warningf(true, Tf_s_sD_s, Tredirection_dup,
+ (sp = snptreef(NULL, 32, Tft_R, &iotmp)),
+ cstrerror(eno));
+ afree(sp, ATEMP);
+ if (iotype != IODUP)
+ close(u);
+ return (-1);
+ }
+ if (iotype != IODUP)
+ close(u);
+ /*
+ * 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->ioflag & IORDUP)
+ /* possible exec <&p */
+ coproc_read_close(u);
+ else
+ /* possible exec >&p */
+ coproc_write_close(u);
+ }
+ }
+ if (u == 2)
+ /* Clear any write errors */
+ shf_reopen(2, SHF_WR, shl_out);
+ return (0);
+}
+
+/*
+ * 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
+hereinval(struct ioword *iop, int sub, char **resbuf, struct shf *shf)
+{
+ const char * volatile ccp = iop->heredoc;
+ struct source *s, *osource;
+
+ osource = source;
+ newenv(E_ERRH);
+ if (kshsetjmp(e->jbuf)) {
+ source = osource;
+ quitenv(shf);
+ /* special to iosetup(): don't print error */
+ return (-2);
+ }
+ if (iop->ioflag & IOHERESTR) {
+ ccp = evalstr(iop->delim, DOHERESTR | DOSCALAR);
+ } else if (sub) {
+ /* do substitutions on the content of heredoc */
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = ccp;
+ source = s;
+ if (yylex(sub) != LWORD)
+ internal_errorf("herein: yylex");
+ source = osource;
+ ccp = evalstr(yylval.cp, DOSCALAR | DOHEREDOC);
+ }
+
+ if (resbuf == NULL)
+ shf_puts(ccp, shf);
+ else
+ strdupx(*resbuf, ccp, APERM);
+
+ quitenv(NULL);
+ return (0);
+}
+
+int
+herein(struct ioword *iop, char **resbuf)
+{
+ int fd = -1;
+ struct shf *shf;
+ struct temp *h;
+ int i;
+
+ /* lexer substitution flags */
+ i = (iop->ioflag & IOEVAL) ? (ONEWORD | HEREDOC) : 0;
+
+ /* skip all the fd setup if we just want the value */
+ if (resbuf != NULL)
+ return (hereinval(iop, i, 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 = binopen3(h->tffn, O_RDONLY, 0)) < 0) {
+ i = errno;
+ warningf(true, Tf_temp,
+ !shf ? Tcreate : Topen, h->tffn, cstrerror(i));
+ if (shf)
+ shf_close(shf);
+ /* special to iosetup(): don't print error */
+ return (-2);
+ }
+
+ if (hereinval(iop, i, NULL, shf) == -2) {
+ close(fd);
+ /* special to iosetup(): don't print error */
+ return (-2);
+ }
+
+ if (shf_close(shf) == -1) {
+ i = errno;
+ close(fd);
+ warningf(true, Tf_temp,
+ Twrite, h->tffn, cstrerror(i));
+ /* special to iosetup(): don't print error */
+ return (-2);
+ }
+
+ return (fd);
+}
+
+/*
+ * ksh special - the select command processing section
+ * print the args in column form - assuming that we can
+ */
+static const char *
+do_selectargs(const char **ap, bool print_menu)
+{
+ static const char *read_args[] = {
+ Tread, Tdr, TREPLY, NULL
+ };
+ char *s;
+ int i, argct;
+
+ for (argct = 0; ap[argct]; argct++)
+ ;
+ 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
+ */
+ if (print_menu || !*str_val(global(TREPLY)))
+ pr_menu(ap);
+ shellf(Tf_s, str_val(global("PS3")));
+ if (call_builtin(findcom(Tread, FC_BI), read_args, Tselect,
+ false))
+ return (NULL);
+ if (*(s = str_val(global(TREPLY))))
+ return ((getn(s, &i) && i >= 1 && i <= argct) ?
+ ap[i - 1] : null);
+ print_menu = true;
+ }
+}
+
+struct select_menu_info {
+ const char * const *args;
+ int num_width;
+};
+
+/* format a single select menu item */
+static void
+select_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg)
+{
+ const struct select_menu_info *smi =
+ (const struct select_menu_info *)arg;
+
+ shf_snprintf(buf, buflen, "%*u) %s",
+ smi->num_width, i + 1, smi->args[i]);
+}
+
+/*
+ * print a select style menu
+ */
+void
+pr_menu(const char * const *ap)
+{
+ struct select_menu_info smi;
+ const char * const *pp;
+ size_t acols = 0, aocts = 0, i;
+ unsigned int n;
+ struct columnise_opts co;
+
+ /*
+ * width/column calculations were done once and saved, but this
+ * means select can't be used recursively so we re-calculate
+ * each time (could save in a structure that is returned, but
+ * it's probably not worth the bother)
+ */
+
+ /*
+ * get dimensions of the list
+ */
+ for (n = 0, pp = ap; *pp; n++, pp++) {
+ i = strlen(*pp);
+ if (i > aocts)
+ aocts = i;
+ i = utf_mbswidth(*pp);
+ if (i > acols)
+ acols = i;
+ }
+
+ /*
+ * we will print an index of the form "%d) " in front of
+ * each entry, so get the maximum width of this
+ */
+ for (i = n, smi.num_width = 1; i >= 10; i /= 10)
+ smi.num_width++;
+
+ smi.args = ap;
+ co.shf = shl_out;
+ co.linesep = '\n';
+ co.prefcol = co.do_last = true;
+ print_columns(&co, n, select_fmt_entry, (void *)&smi,
+ smi.num_width + 2 + aocts, smi.num_width + 2 + acols);
+}
+
+static void
+plain_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg)
+{
+ strlcpy(buf, ((const char * const *)arg)[i], buflen);
+}
+
+void
+pr_list(struct columnise_opts *cop, char * const *ap)
+{
+ size_t acols = 0, aocts = 0, i;
+ unsigned int n;
+ char * const *pp;
+
+ for (n = 0, pp = ap; *pp; n++, pp++) {
+ i = strlen(*pp);
+ if (i > aocts)
+ aocts = i;
+ i = utf_mbswidth(*pp);
+ if (i > acols)
+ acols = i;
+ }
+
+ print_columns(cop, n, plain_fmt_entry, (const void *)ap,
+ aocts, acols);
+}
+
+/*
+ * [[ ... ]] evaluation routines
+ */
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+dbteste_isa(Test_env *te, Test_meta meta)
+{
+ Test_op ret = TO_NONOP;
+ bool uqword;
+ const char *p;
+
+ if (!*te->pos.wp)
+ return (meta == TM_END ? TO_NONNULL : TO_NONOP);
+
+ /* unquoted word? */
+ for (p = *te->pos.wp; *p == CHAR; p += 2)
+ ;
+ uqword = *p == EOS;
+
+ if (meta == TM_UNOP || meta == TM_BINOP) {
+ if (uqword) {
+ /* longer than the longest operator */
+ char buf[8];
+ char *q = buf;
+
+ p = *te->pos.wp;
+ while (*p++ == CHAR &&
+ (size_t)(q - buf) < sizeof(buf) - 1)
+ *q++ = *p++;
+ *q = '\0';
+ ret = test_isop(meta, buf);
+ }
+ } else if (meta == TM_END)
+ ret = TO_NONOP;
+ else
+ ret = (uqword && !strcmp(*te->pos.wp,
+ dbtest_tokens[(int)meta])) ? TO_NONNULL : TO_NONOP;
+
+ /* Accept the token? */
+ if (ret != TO_NONOP)
+ te->pos.wp++;
+
+ return (ret);
+}
+
+static const char *
+dbteste_getopnd(Test_env *te, Test_op op, bool do_eval)
+{
+ const char *s = *te->pos.wp;
+ int flags = DOTILDE | DOSCALAR;
+
+ if (!s)
+ return (NULL);
+
+ te->pos.wp++;
+
+ if (!do_eval)
+ return (null);
+
+ if (op == TO_STEQL || op == TO_STNEQ) {
+ flags |= DOPAT;
+ if (!Flag(FSH))
+ flags |= DODBMAGIC;
+ }
+
+ return (evalstr(s, flags));
+}
+
+static void
+dbteste_error(Test_env *te, int offset, const char *msg)
+{
+ te->flags |= TEF_ERROR;
+ internal_warningf("dbteste_error: %s (offset %d)", msg, offset);
+}
diff --git a/shells/mksh/files/expr.c b/shells/mksh/files/expr.c
new file mode 100644
index 00000000000..67642795931
--- /dev/null
+++ b/shells/mksh/files/expr.c
@@ -0,0 +1,1234 @@
+/* $OpenBSD: expr.c,v 1.24 2014/12/08 14:26:31 otto Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2016, 2017, 2018, 2019
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.107 2020/03/27 02:49:40 tg Exp $");
+
+#define EXPRTOK_DEFNS
+#include "exprtok.h"
+
+/* precisions; used to be enum prec but we do arithmetics on it */
+#define P_PRIMARY 0 /* VAR, LIT, (), ! ~ ++ -- */
+#define P_MULT 1 /* * / % */
+#define P_ADD 2 /* + - */
+#define P_SHIFT 3 /* ^< ^> << >> */
+#define P_RELATION 4 /* < <= > >= */
+#define P_EQUALITY 5 /* == != */
+#define P_BAND 6 /* & */
+#define P_BXOR 7 /* ^ */
+#define P_BOR 8 /* | */
+#define P_LAND 9 /* && */
+#define P_LOR 10 /* || */
+#define P_TERN 11 /* ?: */
+ /* = += -= *= /= %= ^<= ^>= <<= >>= &= ^= |= */
+#define P_ASSIGN 12
+#define P_COMMA 13 /* , */
+#define MAX_PREC P_COMMA
+
+enum token {
+#define EXPRTOK_ENUM
+#include "exprtok.h"
+};
+
+static const char opname[][4] = {
+#define EXPRTOK_NAME
+#include "exprtok.h"
+};
+
+static const uint8_t oplen[] = {
+#define EXPRTOK_LEN
+#include "exprtok.h"
+};
+
+static const uint8_t opprec[] = {
+#define EXPRTOK_PREC
+#include "exprtok.h"
+};
+
+typedef struct expr_state {
+ /* expression being evaluated */
+ const char *expression;
+ /* lexical position */
+ const char *tokp;
+ /* value from token() */
+ struct tbl *val;
+ /* variable that is being recursively expanded (EXPRINEVAL flag set) */
+ struct tbl *evaling;
+ /* token from token() */
+ enum token tok;
+ /* don't do assignments (for ?:, &&, ||) */
+ uint8_t noassign;
+ /* evaluating an $(()) expression? */
+ bool arith;
+ /* unsigned arithmetic calculation */
+ bool natural;
+} Expr_state;
+
+enum error_type {
+ ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE,
+ ET_LVALUE, ET_RDONLY, ET_STR
+};
+
+static void evalerr(Expr_state *, enum error_type, const char *)
+ MKSH_A_NORETURN;
+static struct tbl *evalexpr(Expr_state *, unsigned int);
+static void exprtoken(Expr_state *);
+static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool);
+static void assign_check(Expr_state *, enum token, struct tbl *);
+static struct tbl *intvar(Expr_state *, struct tbl *);
+
+/*
+ * parse and evaluate expression
+ */
+int
+evaluate(const char *expr, mksh_ari_t *rval, int error_ok, bool arith)
+{
+ struct tbl v;
+ int ret;
+
+ v.flag = DEFINED | INTEGER;
+ v.type = 0;
+ ret = v_evaluate(&v, expr, error_ok, arith);
+ *rval = v.val.i;
+ return (ret);
+}
+
+/*
+ * parse and evaluate expression, storing result in vp.
+ */
+int
+v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok,
+ bool arith)
+{
+ struct tbl *v;
+ Expr_state curstate;
+ Expr_state * const es = &curstate;
+ int i;
+
+ /* save state to allow recursive calls */
+ memset(&curstate, 0, sizeof(curstate));
+ curstate.expression = curstate.tokp = expr;
+ curstate.tok = BAD;
+ curstate.arith = arith;
+
+ newenv(E_ERRH);
+ if ((i = kshsetjmp(e->jbuf))) {
+ /* Clear EXPRINEVAL in of any variables we were playing with */
+ if (curstate.evaling)
+ curstate.evaling->flag &= ~EXPRINEVAL;
+ quitenv(NULL);
+ if (i == LAEXPR) {
+ if (error_ok == KSH_RETURN_ERROR)
+ return (0);
+ errorfz();
+ }
+ unwind(i);
+ /* NOTREACHED */
+ }
+
+ exprtoken(es);
+ if (es->tok == END) {
+ es->tok = LIT;
+ es->val = tempvar("");
+ }
+ v = intvar(es, evalexpr(es, MAX_PREC));
+
+ if (es->tok != END)
+ evalerr(es, ET_UNEXPECTED, NULL);
+
+ if (es->arith && es->natural)
+ vp->flag |= INT_U;
+ if (vp->flag & INTEGER)
+ setint_v(vp, v, es->arith);
+ else
+ /* can fail if readonly */
+ setstr(vp, str_val(v), error_ok);
+
+ quitenv(NULL);
+
+ return (1);
+}
+
+static void
+evalerr(Expr_state *es, enum error_type type, const char *str)
+{
+ char tbuf[2];
+ const char *s;
+
+ es->arith = false;
+ switch (type) {
+ case ET_UNEXPECTED:
+ switch (es->tok) {
+ case VAR:
+ s = es->val->name;
+ break;
+ case LIT:
+ s = str_val(es->val);
+ break;
+ case END:
+ s = "end of expression";
+ break;
+ case BAD:
+ tbuf[0] = *es->tokp;
+ tbuf[1] = '\0';
+ s = tbuf;
+ break;
+ default:
+ s = opname[(int)es->tok];
+ }
+ warningf(true, Tf_sD_s_qs, es->expression,
+ Tunexpected, s);
+ break;
+
+ case ET_BADLIT:
+ warningf(true, Tf_sD_s_qs, es->expression,
+ Tbadnum, str);
+ break;
+
+ case ET_RECURSIVE:
+ warningf(true, Tf_sD_s_qs, es->expression,
+ "expression recurses on parameter", str);
+ break;
+
+ case ET_LVALUE:
+ warningf(true, Tf_sD_s_s,
+ es->expression, str, "requires lvalue");
+ break;
+
+ case ET_RDONLY:
+ warningf(true, Tf_sD_s_s,
+ es->expression, str, "applied to read-only variable");
+ break;
+
+ default: /* keep gcc happy */
+ case ET_STR:
+ warningf(true, Tf_sD_s, es->expression, str);
+ break;
+ }
+ unwind(LAEXPR);
+}
+
+/* do a ++ or -- operation */
+static struct tbl *
+do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix)
+{
+ struct tbl *vl;
+ mksh_uari_t oval;
+
+ assign_check(es, op, vasn);
+
+ vl = intvar(es, vasn);
+ oval = vl->val.u;
+ if (op == O_PLUSPLUS)
+ ++vl->val.u;
+ else
+ --vl->val.u;
+ if (!es->noassign) {
+ if (vasn->flag & INTEGER)
+ setint_v(vasn, vl, es->arith);
+ else
+ setint(vasn, vl->val.i);
+ }
+ if (!is_prefix)
+ /* undo the increment/decrement */
+ vl->val.u = oval;
+
+ return (vl);
+}
+
+static struct tbl *
+evalexpr(Expr_state *es, unsigned int prec)
+{
+ struct tbl *vl, *vr = NULL, *vasn;
+ enum token op;
+ mksh_uari_t res = 0, t1, t2, t3;
+
+ if (prec == P_PRIMARY) {
+ switch ((int)(op = es->tok)) {
+ case O_BNOT:
+ case O_LNOT:
+ case O_MINUS:
+ case O_PLUS:
+ exprtoken(es);
+ vl = intvar(es, evalexpr(es, P_PRIMARY));
+ switch ((int)op) {
+ case O_BNOT:
+ vl->val.u = ~vl->val.u;
+ break;
+ case O_LNOT:
+ vl->val.u = !vl->val.u;
+ break;
+ case O_MINUS:
+ vl->val.u = -vl->val.u;
+ break;
+ case O_PLUS:
+ /* nop */
+ break;
+ }
+ break;
+
+ case OPEN_PAREN:
+ exprtoken(es);
+ vl = evalexpr(es, MAX_PREC);
+ if (es->tok != CLOSE_PAREN)
+ evalerr(es, ET_STR, "missing )");
+ exprtoken(es);
+ break;
+
+ case O_PLUSPLUS:
+ case O_MINUSMINUS:
+ exprtoken(es);
+ vl = do_ppmm(es, op, es->val, true);
+ exprtoken(es);
+ break;
+
+ case VAR:
+ case LIT:
+ vl = es->val;
+ exprtoken(es);
+ break;
+
+ default:
+ evalerr(es, ET_UNEXPECTED, NULL);
+ /* NOTREACHED */
+ }
+
+ if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) {
+ vl = do_ppmm(es, es->tok, vl, false);
+ exprtoken(es);
+ }
+
+ return (vl);
+ /* prec == P_PRIMARY */
+ }
+
+ vl = evalexpr(es, prec - 1);
+ while ((int)(op = es->tok) >= (int)O_EQ && (int)op <= (int)O_COMMA &&
+ opprec[(int)op] == prec) {
+ switch ((int)op) {
+ case O_TERN:
+ case O_LAND:
+ case O_LOR:
+ break;
+ default:
+ exprtoken(es);
+ }
+
+ vasn = vl;
+ if (op != O_ASN)
+ /* vl may not have a value yet */
+ vl = intvar(es, vl);
+ if (IS_ASSIGNOP(op)) {
+ if (!es->noassign)
+ assign_check(es, op, vasn);
+ vr = intvar(es, evalexpr(es, P_ASSIGN));
+ } else if (op == O_TERN) {
+ bool ev = vl->val.u != 0;
+
+ if (!ev)
+ es->noassign++;
+ exprtoken(es);
+ vl = evalexpr(es, MAX_PREC);
+ if (!ev)
+ es->noassign--;
+ if (es->tok != CTERN)
+ evalerr(es, ET_STR, "missing :");
+ if (ev)
+ es->noassign++;
+ exprtoken(es);
+ vr = evalexpr(es, P_TERN);
+ if (ev)
+ es->noassign--;
+ vl = ev ? vl : vr;
+ continue;
+ } else if (op != O_LAND && op != O_LOR)
+ vr = intvar(es, evalexpr(es, prec - 1));
+
+ /* common ops setup */
+ switch ((int)op) {
+ case O_DIV:
+ case O_DIVASN:
+ case O_MOD:
+ case O_MODASN:
+ if (vr->val.u == 0) {
+ if (!es->noassign)
+ evalerr(es, ET_STR, "zero divisor");
+ vr->val.u = 1;
+ }
+ /* calculate the absolute values */
+ t1 = vl->val.i < 0 ? -vl->val.u : vl->val.u;
+ t2 = vr->val.i < 0 ? -vr->val.u : vr->val.u;
+ break;
+#ifndef MKSH_LEGACY_MODE
+ case O_LSHIFT:
+ case O_LSHIFTASN:
+ case O_RSHIFT:
+ case O_RSHIFTASN:
+ case O_ROL:
+ case O_ROLASN:
+ case O_ROR:
+ case O_RORASN:
+ t1 = vl->val.u;
+ t2 = vr->val.u & 31;
+ break;
+#endif
+ case O_LAND:
+ case O_LOR:
+ t1 = vl->val.u;
+ t2 = 0; /* gcc */
+ break;
+ default:
+ t1 = vl->val.u;
+ t2 = vr->val.u;
+ break;
+ }
+
+#define cmpop(op) (es->natural ? \
+ (mksh_uari_t)(vl->val.u op vr->val.u) : \
+ (mksh_uari_t)(vl->val.i op vr->val.i) \
+)
+
+ /* op calculation */
+ switch ((int)op) {
+ case O_TIMES:
+ case O_TIMESASN:
+ res = t1 * t2;
+ break;
+ case O_MOD:
+ case O_MODASN:
+ if (es->natural) {
+ res = vl->val.u % vr->val.u;
+ break;
+ }
+ goto signed_division;
+ case O_DIV:
+ case O_DIVASN:
+ if (es->natural) {
+ res = vl->val.u / vr->val.u;
+ break;
+ }
+ signed_division:
+ /*
+ * a / b = abs(a) / abs(b) * sgn((u)a^(u)b)
+ */
+ t3 = t1 / t2;
+#ifndef MKSH_LEGACY_MODE
+ res = ((vl->val.u ^ vr->val.u) & 0x80000000) ? -t3 : t3;
+#else
+ res = ((t1 == vl->val.u ? 0 : 1) ^
+ (t2 == vr->val.u ? 0 : 1)) ? -t3 : t3;
+#endif
+ if (op == O_MOD || op == O_MODASN) {
+ /*
+ * primitive modulo, to get the sign of
+ * the result correct:
+ * (a % b) = a - ((a / b) * b)
+ * the subtraction and multiplication
+ * are, amazingly enough, sign ignorant
+ */
+ res = vl->val.u - (res * vr->val.u);
+ }
+ break;
+ case O_PLUS:
+ case O_PLUSASN:
+ res = t1 + t2;
+ break;
+ case O_MINUS:
+ case O_MINUSASN:
+ res = t1 - t2;
+ break;
+#ifndef MKSH_LEGACY_MODE
+ case O_ROL:
+ case O_ROLASN:
+ res = (t1 << t2) | (t1 >> (32 - t2));
+ break;
+ case O_ROR:
+ case O_RORASN:
+ res = (t1 >> t2) | (t1 << (32 - t2));
+ break;
+#endif
+ case O_LSHIFT:
+ case O_LSHIFTASN:
+ res = t1 << t2;
+ break;
+ case O_RSHIFT:
+ case O_RSHIFTASN:
+ res = es->natural || vl->val.i >= 0 ?
+ t1 >> t2 :
+ ~(~t1 >> t2);
+ break;
+ case O_LT:
+ res = cmpop(<);
+ break;
+ case O_LE:
+ res = cmpop(<=);
+ break;
+ case O_GT:
+ res = cmpop(>);
+ break;
+ case O_GE:
+ res = cmpop(>=);
+ break;
+ case O_EQ:
+ res = t1 == t2;
+ break;
+ case O_NE:
+ res = t1 != t2;
+ break;
+ case O_BAND:
+ case O_BANDASN:
+ res = t1 & t2;
+ break;
+ case O_BXOR:
+ case O_BXORASN:
+ res = t1 ^ t2;
+ break;
+ case O_BOR:
+ case O_BORASN:
+ res = t1 | t2;
+ break;
+ case O_LAND:
+ if (!t1)
+ es->noassign++;
+ exprtoken(es);
+ vr = intvar(es, evalexpr(es, prec - 1));
+ res = t1 && vr->val.u;
+ if (!t1)
+ es->noassign--;
+ break;
+ case O_LOR:
+ if (t1)
+ es->noassign++;
+ exprtoken(es);
+ vr = intvar(es, evalexpr(es, prec - 1));
+ res = t1 || vr->val.u;
+ if (t1)
+ es->noassign--;
+ break;
+ case O_ASN:
+ case O_COMMA:
+ res = t2;
+ break;
+ }
+
+#undef cmpop
+
+ if (IS_ASSIGNOP(op)) {
+ vr->val.u = res;
+ if (!es->noassign) {
+ if (vasn->flag & INTEGER)
+ setint_v(vasn, vr, es->arith);
+ else
+ setint(vasn, vr->val.i);
+ }
+ vl = vr;
+ } else
+ vl->val.u = res;
+ }
+ return (vl);
+}
+
+static void
+exprtoken(Expr_state *es)
+{
+ const char *cp = es->tokp;
+ int c;
+ char *tvar;
+
+ /* skip whitespace */
+ skip_spaces:
+ --cp;
+ do {
+ c = ord(*++cp);
+ } while (ctype(c, C_SPACE));
+ if (es->tokp == es->expression && (unsigned int)c == ORD('#')) {
+ /* expression begins with # */
+ /* switch to unsigned */
+ es->natural = true;
+ ++cp;
+ goto skip_spaces;
+ }
+ es->tokp = cp;
+
+ if (c == '\0')
+ es->tok = END;
+ else if (ctype(c, C_ALPHX)) {
+ do {
+ c = ord(*++cp);
+ } while (ctype(c, C_ALNUX));
+ if ((unsigned int)c == ORD('[')) {
+ size_t len;
+
+ len = array_ref_len(cp);
+ if (len == 0)
+ evalerr(es, ET_STR, "missing ]");
+ cp += len;
+ }
+ if (es->noassign) {
+ es->val = tempvar("");
+ es->val->flag |= EXPRLVALUE;
+ } else {
+ strndupx(tvar, es->tokp, cp - es->tokp, ATEMP);
+ es->val = global(tvar);
+ afree(tvar, ATEMP);
+ }
+ es->tok = VAR;
+ } else if (c == '1' && cp[1] == '#') {
+ cp += 2;
+ if (*cp)
+ cp += utf_ptradj(cp);
+ strndupx(tvar, es->tokp, cp - es->tokp, ATEMP);
+ goto process_tvar;
+#ifndef MKSH_SMALL
+ } else if (c == '\'') {
+ if (*++cp == '\0') {
+ es->tok = END;
+ evalerr(es, ET_UNEXPECTED, NULL);
+ }
+ cp += utf_ptradj(cp);
+ if (*cp++ != '\'')
+ evalerr(es, ET_STR,
+ "multi-character character constant");
+ /* 'x' -> 1#x (x = one multibyte character) */
+ c = cp - es->tokp;
+ tvar = alloc(c + /* NUL */ 1, ATEMP);
+ tvar[0] = '1';
+ tvar[1] = '#';
+ memcpy(tvar + 2, es->tokp + 1, c - 2);
+ tvar[c] = '\0';
+ goto process_tvar;
+#endif
+ } else if (ctype(c, C_DIGIT)) {
+ while (ctype(c, C_ALNUM | C_HASH))
+ c = ord(*cp++);
+ strndupx(tvar, es->tokp, --cp - es->tokp, ATEMP);
+ process_tvar:
+ es->val = tempvar("");
+ es->val->flag &= ~INTEGER;
+ es->val->type = 0;
+ es->val->val.s = tvar;
+ if (setint_v(es->val, es->val, es->arith) == NULL)
+ evalerr(es, ET_BADLIT, tvar);
+ afree(tvar, ATEMP);
+ es->tok = LIT;
+ } else {
+ int i, n0;
+
+ for (i = 0; (n0 = ord(opname[i][0])); i++)
+ if (c == n0 && strncmp(cp, opname[i],
+ (size_t)oplen[i]) == 0) {
+ es->tok = (enum token)i;
+ cp += oplen[i];
+ break;
+ }
+ if (!n0)
+ es->tok = BAD;
+ }
+ es->tokp = cp;
+}
+
+static void
+assign_check(Expr_state *es, enum token op, struct tbl *vasn)
+{
+ if (es->tok == END || !vasn ||
+ (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE)))
+ evalerr(es, ET_LVALUE, opname[(int)op]);
+ else if (vasn->flag & RDONLY)
+ evalerr(es, ET_RDONLY, opname[(int)op]);
+}
+
+struct tbl *
+tempvar(const char *vname)
+{
+ struct tbl *vp;
+ size_t vsize;
+
+ vsize = strlen(vname) + 1;
+ vp = alloc(offsetof(struct tbl, name[0]) + vsize, ATEMP);
+ memcpy(vp->name, vname, vsize);
+ vp->flag = ISSET|INTEGER;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ vp->ua.hval = 0;
+ vp->val.i = 0;
+ return (vp);
+}
+
+/* cast (string) variable to temporary integer variable */
+static struct tbl *
+intvar(Expr_state *es, struct tbl *vp)
+{
+ struct tbl *vq;
+
+ /* try to avoid replacing a temp var with another temp var */
+ if (vp->name[0] == '\0' &&
+ (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER))
+ return (vp);
+
+ vq = tempvar("");
+ if (setint_v(vq, vp, es->arith) == NULL) {
+ if (vp->flag & EXPRINEVAL)
+ evalerr(es, ET_RECURSIVE, vp->name);
+ es->evaling = vp;
+ vp->flag |= EXPRINEVAL;
+ v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith);
+ vp->flag &= ~EXPRINEVAL;
+ es->evaling = NULL;
+ }
+ return (vq);
+}
+
+
+/*
+ * UTF-8 support code: high-level functions
+ */
+
+int
+utf_widthadj(const char *src, const char **dst)
+{
+ size_t len;
+ unsigned int wc;
+ int width;
+
+ if (!UTFMODE || (len = utf_mbtowc(&wc, src)) == (size_t)-1 ||
+ wc == 0)
+ len = width = 1;
+ else if ((width = utf_wcwidth(wc)) < 0)
+ /* XXX use 2 for x_zotc3 here? */
+ width = 1;
+
+ if (dst)
+ *dst = src + len;
+ return (width);
+}
+
+size_t
+utf_mbswidth(const char *s)
+{
+ size_t len, width = 0;
+ unsigned int wc;
+ int cw;
+
+ if (!UTFMODE)
+ return (strlen(s));
+
+ while (*s)
+ if (((len = utf_mbtowc(&wc, s)) == (size_t)-1) ||
+ ((cw = utf_wcwidth(wc)) == -1)) {
+ s++;
+ width += 1;
+ } else {
+ s += len;
+ width += cw;
+ }
+ return (width);
+}
+
+const char *
+utf_skipcols(const char *p, int cols, int *colp)
+{
+ int c = 0;
+ const char *q;
+
+ while (c < cols) {
+ if (!*p) {
+ /* end of input; special handling for edit.c */
+ if (!colp)
+ return (p + cols - c);
+ *colp = c;
+ return (p);
+ }
+ c += utf_widthadj(p, &p);
+ }
+ if (UTFMODE)
+ while (utf_widthadj(p, &q) == 0)
+ p = q;
+ if (colp)
+ *colp = c;
+ return (p);
+}
+
+size_t
+utf_ptradj(const char *src)
+{
+ register size_t n;
+
+ if (!UTFMODE || rtt2asc(*src) < 0xC2 ||
+ (n = utf_mbtowc(NULL, src)) == (size_t)-1)
+ n = 1;
+ return (n);
+}
+
+/*
+ * UTF-8 support code: low-level functions
+ */
+
+/* CESU-8 multibyte and wide character conversion crafted for mksh */
+
+size_t
+utf_mbtowc(unsigned int *dst, const char *src)
+{
+ const unsigned char *s = (const unsigned char *)src;
+ unsigned int c, wc;
+
+ if ((wc = ord(rtt2asc(*s++))) < 0x80) {
+ out:
+ if (dst != NULL)
+ *dst = wc;
+ return (wc ? ((const char *)s - src) : 0);
+ }
+ if (wc < 0xC2 || wc >= 0xF0)
+ /* < 0xC0: spurious second byte */
+ /* < 0xC2: non-minimalistic mapping error in 2-byte seqs */
+ /* > 0xEF: beyond BMP */
+ goto ilseq;
+
+ if (wc < 0xE0) {
+ wc = (wc & 0x1F) << 6;
+ if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
+ goto ilseq;
+ wc |= c & 0x3F;
+ goto out;
+ }
+
+ wc = (wc & 0x0F) << 12;
+
+ if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
+ goto ilseq;
+ wc |= (c & 0x3F) << 6;
+
+ if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
+ goto ilseq;
+ wc |= c & 0x3F;
+
+ /* Check for non-minimalistic mapping error in 3-byte seqs */
+ if (wc >= 0x0800 && wc <= 0xFFFD)
+ goto out;
+ ilseq:
+ return ((size_t)(-1));
+}
+
+size_t
+utf_wctomb(char *dst, unsigned int wc)
+{
+ unsigned char *d;
+
+ if (wc < 0x80) {
+ *dst = asc2rtt(wc);
+ return (1);
+ }
+
+ d = (unsigned char *)dst;
+ if (wc < 0x0800)
+ *d++ = asc2rtt((wc >> 6) | 0xC0);
+ else {
+ *d++ = asc2rtt(((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0);
+ *d++ = asc2rtt(((wc >> 6) & 0x3F) | 0x80);
+ }
+ *d++ = asc2rtt((wc & 0x3F) | 0x80);
+ return ((char *)d - dst);
+}
+
+/*
+ * 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)
+{
+#ifdef __OS2__
+ return (access_ex(access, fn, mode));
+#else
+ int rv;
+ struct stat sb;
+
+ if ((rv = access(fn, mode)) == 0 && (mode & X_OK) &&
+ (kshuid == 0 || ksheuid == 0) &&
+ (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) &&
+ (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
+ rv = -1;
+
+ return (rv);
+#endif
+}
+
+#ifndef MIRBSD_BOOTFLOPPY
+/* From: X11/xc/programs/xterm/wcwidth.c,v 1.10 */
+
+struct mb_ucsrange {
+ unsigned short beg;
+ unsigned short end;
+};
+
+static int mb_ucsbsearch(const struct mb_ucsrange arr[], size_t elems,
+ unsigned int val) MKSH_A_PURE;
+
+/*
+ * Generated from the UCD 13.0.0 by
+ * MirOS: contrib/code/Snippets/eawparse,v 1.14 2020/03/27 01:33:21 tg Exp $
+ */
+
+/*-
+ * Parts Copyright © 1991–2020 Unicode, Inc. All rights reserved.
+ * Distributed under the Terms of Use in
+ * https://www.unicode.org/copyright.html.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of the Unicode data files and any associated documentation
+ * (the "Data Files") or Unicode software and any associated documentation
+ * (the "Software") to deal in the Data Files or Software
+ * without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, and/or sell copies of
+ * the Data Files or Software, and to permit persons to whom the Data Files
+ * or Software are furnished to do so, provided that either
+ * (a) this copyright and permission notice appear with all copies
+ * of the Data Files or Software, or
+ * (b) this copyright and permission notice appear in associated
+ * Documentation.
+ *
+ * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+ * NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+ * DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder
+ * shall not be used in advertising or otherwise to promote the sale,
+ * use or other dealings in these Data Files or Software without prior
+ * written authorization of the copyright holder.
+ */
+
+static const struct mb_ucsrange mb_ucs_combining[] = {
+ { 0x0300, 0x036F },
+ { 0x0483, 0x0489 },
+ { 0x0591, 0x05BD },
+ { 0x05BF, 0x05BF },
+ { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 },
+ { 0x05C7, 0x05C7 },
+ { 0x0610, 0x061A },
+ { 0x061C, 0x061C },
+ { 0x064B, 0x065F },
+ { 0x0670, 0x0670 },
+ { 0x06D6, 0x06DC },
+ { 0x06DF, 0x06E4 },
+ { 0x06E7, 0x06E8 },
+ { 0x06EA, 0x06ED },
+ { 0x0711, 0x0711 },
+ { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 },
+ { 0x07EB, 0x07F3 },
+ { 0x07FD, 0x07FD },
+ { 0x0816, 0x0819 },
+ { 0x081B, 0x0823 },
+ { 0x0825, 0x0827 },
+ { 0x0829, 0x082D },
+ { 0x0859, 0x085B },
+ { 0x08D3, 0x08E1 },
+ { 0x08E3, 0x0902 },
+ { 0x093A, 0x093A },
+ { 0x093C, 0x093C },
+ { 0x0941, 0x0948 },
+ { 0x094D, 0x094D },
+ { 0x0951, 0x0957 },
+ { 0x0962, 0x0963 },
+ { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC },
+ { 0x09C1, 0x09C4 },
+ { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 },
+ { 0x09FE, 0x09FE },
+ { 0x0A01, 0x0A02 },
+ { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 },
+ { 0x0A47, 0x0A48 },
+ { 0x0A4B, 0x0A4D },
+ { 0x0A51, 0x0A51 },
+ { 0x0A70, 0x0A71 },
+ { 0x0A75, 0x0A75 },
+ { 0x0A81, 0x0A82 },
+ { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 },
+ { 0x0AC7, 0x0AC8 },
+ { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 },
+ { 0x0AFA, 0x0AFF },
+ { 0x0B01, 0x0B01 },
+ { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F },
+ { 0x0B41, 0x0B44 },
+ { 0x0B4D, 0x0B4D },
+ { 0x0B55, 0x0B56 },
+ { 0x0B62, 0x0B63 },
+ { 0x0B82, 0x0B82 },
+ { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD },
+ { 0x0C00, 0x0C00 },
+ { 0x0C04, 0x0C04 },
+ { 0x0C3E, 0x0C40 },
+ { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D },
+ { 0x0C55, 0x0C56 },
+ { 0x0C62, 0x0C63 },
+ { 0x0C81, 0x0C81 },
+ { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF },
+ { 0x0CC6, 0x0CC6 },
+ { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 },
+ { 0x0D00, 0x0D01 },
+ { 0x0D3B, 0x0D3C },
+ { 0x0D41, 0x0D44 },
+ { 0x0D4D, 0x0D4D },
+ { 0x0D62, 0x0D63 },
+ { 0x0D81, 0x0D81 },
+ { 0x0DCA, 0x0DCA },
+ { 0x0DD2, 0x0DD4 },
+ { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 },
+ { 0x0E34, 0x0E3A },
+ { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 },
+ { 0x0EB4, 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, 0x180E },
+ { 0x1885, 0x1886 },
+ { 0x18A9, 0x18A9 },
+ { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 },
+ { 0x1932, 0x1932 },
+ { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 },
+ { 0x1A1B, 0x1A1B },
+ { 0x1A56, 0x1A56 },
+ { 0x1A58, 0x1A5E },
+ { 0x1A60, 0x1A60 },
+ { 0x1A62, 0x1A62 },
+ { 0x1A65, 0x1A6C },
+ { 0x1A73, 0x1A7C },
+ { 0x1A7F, 0x1A7F },
+ { 0x1AB0, 0x1AC0 },
+ { 0x1B00, 0x1B03 },
+ { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A },
+ { 0x1B3C, 0x1B3C },
+ { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 },
+ { 0x1B80, 0x1B81 },
+ { 0x1BA2, 0x1BA5 },
+ { 0x1BA8, 0x1BA9 },
+ { 0x1BAB, 0x1BAD },
+ { 0x1BE6, 0x1BE6 },
+ { 0x1BE8, 0x1BE9 },
+ { 0x1BED, 0x1BED },
+ { 0x1BEF, 0x1BF1 },
+ { 0x1C2C, 0x1C33 },
+ { 0x1C36, 0x1C37 },
+ { 0x1CD0, 0x1CD2 },
+ { 0x1CD4, 0x1CE0 },
+ { 0x1CE2, 0x1CE8 },
+ { 0x1CED, 0x1CED },
+ { 0x1CF4, 0x1CF4 },
+ { 0x1CF8, 0x1CF9 },
+ { 0x1DC0, 0x1DF9 },
+ { 0x1DFB, 0x1DFF },
+ { 0x200B, 0x200F },
+ { 0x202A, 0x202E },
+ { 0x2060, 0x2064 },
+ { 0x2066, 0x206F },
+ { 0x20D0, 0x20F0 },
+ { 0x2CEF, 0x2CF1 },
+ { 0x2D7F, 0x2D7F },
+ { 0x2DE0, 0x2DFF },
+ { 0x302A, 0x302D },
+ { 0x3099, 0x309A },
+ { 0xA66F, 0xA672 },
+ { 0xA674, 0xA67D },
+ { 0xA69E, 0xA69F },
+ { 0xA6F0, 0xA6F1 },
+ { 0xA802, 0xA802 },
+ { 0xA806, 0xA806 },
+ { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 },
+ { 0xA82C, 0xA82C },
+ { 0xA8C4, 0xA8C5 },
+ { 0xA8E0, 0xA8F1 },
+ { 0xA8FF, 0xA8FF },
+ { 0xA926, 0xA92D },
+ { 0xA947, 0xA951 },
+ { 0xA980, 0xA982 },
+ { 0xA9B3, 0xA9B3 },
+ { 0xA9B6, 0xA9B9 },
+ { 0xA9BC, 0xA9BD },
+ { 0xA9E5, 0xA9E5 },
+ { 0xAA29, 0xAA2E },
+ { 0xAA31, 0xAA32 },
+ { 0xAA35, 0xAA36 },
+ { 0xAA43, 0xAA43 },
+ { 0xAA4C, 0xAA4C },
+ { 0xAA7C, 0xAA7C },
+ { 0xAAB0, 0xAAB0 },
+ { 0xAAB2, 0xAAB4 },
+ { 0xAAB7, 0xAAB8 },
+ { 0xAABE, 0xAABF },
+ { 0xAAC1, 0xAAC1 },
+ { 0xAAEC, 0xAAED },
+ { 0xAAF6, 0xAAF6 },
+ { 0xABE5, 0xABE5 },
+ { 0xABE8, 0xABE8 },
+ { 0xABED, 0xABED },
+ { 0xFB1E, 0xFB1E },
+ { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE2F },
+ { 0xFEFF, 0xFEFF },
+ { 0xFFF9, 0xFFFB }
+};
+
+static const struct mb_ucsrange mb_ucs_fullwidth[] = {
+ { 0x1100, 0x115F },
+ { 0x231A, 0x231B },
+ { 0x2329, 0x232A },
+ { 0x23E9, 0x23EC },
+ { 0x23F0, 0x23F0 },
+ { 0x23F3, 0x23F3 },
+ { 0x25FD, 0x25FE },
+ { 0x2614, 0x2615 },
+ { 0x2648, 0x2653 },
+ { 0x267F, 0x267F },
+ { 0x2693, 0x2693 },
+ { 0x26A1, 0x26A1 },
+ { 0x26AA, 0x26AB },
+ { 0x26BD, 0x26BE },
+ { 0x26C4, 0x26C5 },
+ { 0x26CE, 0x26CE },
+ { 0x26D4, 0x26D4 },
+ { 0x26EA, 0x26EA },
+ { 0x26F2, 0x26F3 },
+ { 0x26F5, 0x26F5 },
+ { 0x26FA, 0x26FA },
+ { 0x26FD, 0x26FD },
+ { 0x2705, 0x2705 },
+ { 0x270A, 0x270B },
+ { 0x2728, 0x2728 },
+ { 0x274C, 0x274C },
+ { 0x274E, 0x274E },
+ { 0x2753, 0x2755 },
+ { 0x2757, 0x2757 },
+ { 0x2795, 0x2797 },
+ { 0x27B0, 0x27B0 },
+ { 0x27BF, 0x27BF },
+ { 0x2B1B, 0x2B1C },
+ { 0x2B50, 0x2B50 },
+ { 0x2B55, 0x2B55 },
+ { 0x2E80, 0x3029 },
+ { 0x302E, 0x303E },
+ { 0x3040, 0x3098 },
+ { 0x309B, 0xA4CF },
+ { 0xA960, 0xA97F },
+ { 0xAC00, 0xD7A3 },
+ { 0xF900, 0xFAFF },
+ { 0xFE10, 0xFE19 },
+ { 0xFE30, 0xFE6F },
+ { 0xFF01, 0xFF60 },
+ { 0xFFE0, 0xFFE6 }
+};
+
+/* simple binary search in ranges, with bounds optimisation */
+static int
+mb_ucsbsearch(const struct mb_ucsrange arr[], size_t elems, unsigned int val)
+{
+ size_t min = 0, mid, max = elems;
+
+ if (val < arr[min].beg || val > arr[max - 1].end)
+ return (0);
+
+ while (min < max) {
+ mid = (min + max) / 2;
+
+ if (val < arr[mid].beg)
+ max = mid;
+ else if (val > arr[mid].end)
+ min = mid + 1;
+ else
+ return (1);
+ }
+ return (0);
+}
+
+/* Unix column width of a wide character (UCS code point, really) */
+int
+utf_wcwidth(unsigned int wc)
+{
+ /* except NUL, C0/C1 control characters and DEL yield -1 */
+ if (wc < 0x20 || (wc >= 0x7F && wc < 0xA0))
+ return (wc ? -1 : 0);
+
+ /* combining characters use 0 screen columns */
+ if (mb_ucsbsearch(mb_ucs_combining, NELEM(mb_ucs_combining), wc))
+ return (0);
+
+ /* all others use 1 or 2 screen columns */
+ if (mb_ucsbsearch(mb_ucs_fullwidth, NELEM(mb_ucs_fullwidth), wc))
+ return (2);
+ return (1);
+}
+#endif
diff --git a/shells/mksh/files/exprtok.h b/shells/mksh/files/exprtok.h
new file mode 100644
index 00000000000..e98e51f1510
--- /dev/null
+++ b/shells/mksh/files/exprtok.h
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2016, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#if defined(EXPRTOK_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/exprtok.h,v 1.4 2020/04/07 11:56:46 tg Exp $");
+/* see range comment below */
+#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
+#define FN(name,len,prec,enum) /* nothing */
+#define F1(enum) /* nothing */
+#elif defined(EXPRTOK_ENUM)
+#define F0(name,len,prec,enum) enum = 0,
+#define FN(name,len,prec,enum) enum,
+#define F1(enum) enum,
+#define F2(enum) enum,
+#define F9(enum) enum
+#elif defined(EXPRTOK_NAME)
+#define FN(name,len,prec,enum) name,
+#define F1(enum) ""
+#elif defined(EXPRTOK_LEN)
+#define FN(name,len,prec,enum) len,
+#define F1(enum) 0
+#elif defined(EXPRTOK_PREC)
+#define FN(name,len,prec,enum) prec,
+#define F1(enum) P_PRIMARY
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+#ifndef F2
+#define F2(enum) /* nothing */
+#define F9(enum) /* nothing */
+#endif
+
+/* tokens must be ordered so the longest are first (e.g. += before +) */
+
+/* some (long) unary operators */
+F0("++", 2, P_PRIMARY, O_PLUSPLUS) /* before + */
+FN("--", 2, P_PRIMARY, O_MINUSMINUS) /* before - */
+/* binary operators */
+FN("==", 2, P_EQUALITY, O_EQ) /* before = */
+FN("!=", 2, P_EQUALITY, O_NE) /* before ! */
+/* assignments are assumed to be in range O_ASN .. O_BORASN */
+FN("=", 1, P_ASSIGN, O_ASN)
+FN("*=", 2, P_ASSIGN, O_TIMESASN)
+FN("/=", 2, P_ASSIGN, O_DIVASN)
+FN("%=", 2, P_ASSIGN, O_MODASN)
+FN("+=", 2, P_ASSIGN, O_PLUSASN)
+FN("-=", 2, P_ASSIGN, O_MINUSASN)
+#ifndef MKSH_LEGACY_MODE
+FN("^<=", 3, P_ASSIGN, O_ROLASN) /* before ^< */
+FN("^>=", 3, P_ASSIGN, O_RORASN) /* before ^> */
+#endif
+FN("<<=", 3, P_ASSIGN, O_LSHIFTASN)
+FN(">>=", 3, P_ASSIGN, O_RSHIFTASN)
+FN("&=", 2, P_ASSIGN, O_BANDASN)
+FN("^=", 2, P_ASSIGN, O_BXORASN)
+FN("|=", 2, P_ASSIGN, O_BORASN)
+/* binary non-assignment operators */
+#ifndef MKSH_LEGACY_MODE
+FN("^<", 2, P_SHIFT, O_ROL) /* before ^ */
+FN("^>", 2, P_SHIFT, O_ROR) /* before ^ */
+#endif
+FN("<<", 2, P_SHIFT, O_LSHIFT)
+FN(">>", 2, P_SHIFT, O_RSHIFT)
+FN("<=", 2, P_RELATION, O_LE)
+FN(">=", 2, P_RELATION, O_GE)
+FN("<", 1, P_RELATION, O_LT)
+FN(">", 1, P_RELATION, O_GT)
+FN("&&", 2, P_LAND, O_LAND)
+FN("||", 2, P_LOR, O_LOR)
+FN("*", 1, P_MULT, O_TIMES)
+FN("/", 1, P_MULT, O_DIV)
+FN("%", 1, P_MULT, O_MOD)
+FN("+", 1, P_ADD, O_PLUS)
+FN("-", 1, P_ADD, O_MINUS)
+FN("&", 1, P_BAND, O_BAND)
+FN("^", 1, P_BXOR, O_BXOR)
+FN("|", 1, P_BOR, O_BOR)
+FN("?", 1, P_TERN, O_TERN)
+FN(",", 1, P_COMMA, O_COMMA)
+/* things after this aren't used as binary operators */
+/* unary that are not also binaries */
+FN("~", 1, P_PRIMARY, O_BNOT)
+FN("!", 1, P_PRIMARY, O_LNOT)
+/* misc */
+FN("(", 1, P_PRIMARY, OPEN_PAREN)
+FN(")", 1, P_PRIMARY, CLOSE_PAREN)
+FN(":", 1, P_PRIMARY, CTERN)
+/* things that don't appear in the opinfo[] table */
+F1(VAR) /*XXX should be F2 */
+F2(LIT)
+F2(END)
+F9(BAD)
+
+#undef FN
+#undef F0
+#undef F1
+#undef F2
+#undef F9
+#undef EXPRTOK_DEFNS
+#undef EXPRTOK_ENUM
+#undef EXPRTOK_NAME
+#undef EXPRTOK_LEN
+#undef EXPRTOK_PREC
diff --git a/shells/mksh/files/funcs.c b/shells/mksh/files/funcs.c
new file mode 100644
index 00000000000..741b6a6644a
--- /dev/null
+++ b/shells/mksh/files/funcs.c
@@ -0,0 +1,3657 @@
+/* $OpenBSD: c_ksh.c,v 1.37 2015/09/10 22:48:58 nicm Exp $ */
+/* $OpenBSD: c_sh.c,v 1.46 2015/07/20 20:46:24 guenther Exp $ */
+/* $OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto Exp $ */
+/* $OpenBSD: c_ulimit.c,v 1.19 2013/11/28 10:33:37 sobrado Exp $ */
+
+/*-
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ * 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+#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.373 2020/05/16 22:38:21 tg Exp $");
+
+#if HAVE_KILLPG
+/*
+ * use killpg if < -1 since -1 does special things
+ * for some non-killpg-endowed kills
+ */
+#define mksh_kill(p,s) ((p) < -1 ? killpg(-(p), (s)) : kill((p), (s)))
+#else
+/* cross fingers and hope kill is killpg-endowed */
+#define mksh_kill kill
+#endif
+
+/* XXX conditions correct? */
+#if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS)
+#define MKSH_NO_LIMITS 1
+#endif
+
+#ifdef MKSH_NO_LIMITS
+#define c_ulimit c_true
+#endif
+
+#if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
+static int c_suspend(const char **);
+#endif
+
+static int do_whence(const char **, int, bool, bool);
+
+/* getn() that prints error */
+static int
+bi_getn(const char *as, int *ai)
+{
+ int rv;
+
+ if (!(rv = getn(as, ai)))
+ bi_errorf(Tf_sD_s, Tbadnum, as);
+ return (rv);
+}
+
+static int
+c_true(const char **wp MKSH_A_UNUSED)
+{
+ return (0);
+}
+
+static int
+c_false(const char **wp MKSH_A_UNUSED)
+{
+ return (1);
+}
+
+/*
+ * A leading = means assignments before command are kept.
+ * A leading * means a POSIX special builtin.
+ * A leading ^ means declaration utility, - forwarder.
+ */
+const struct builtin mkshbuiltins[] = {
+ {Tsgdot, c_dot},
+ {"*=:", c_true},
+ {Tbracket, c_test},
+ /* no =: AT&T manual wrong */
+ {Talias, c_alias},
+ {Tsgbreak, c_brkcont},
+ {T__builtin, c_builtin},
+ {Tbuiltin, c_builtin},
+ {Tbcat, c_cat},
+ {Tcd, c_cd},
+ /* dash compatibility hack */
+ {"chdir", c_cd},
+ {T_command, c_command},
+ {Tsgcontinue, c_brkcont},
+ {"echo", c_print},
+ {"*=eval", c_eval},
+ {"*=exec", c_exec},
+ {"*=exit", c_exitreturn},
+ {Tdsgexport, c_typeset},
+ {Tfalse, c_false},
+ {"fc", c_fc},
+ {Tgetopts, c_getopts},
+ {Tjobs, c_jobs},
+ {"kill", c_kill},
+ {"let", c_let},
+ {"print", c_print},
+ {"pwd", c_pwd},
+ {Tread, c_read},
+ {Tdsgreadonly, c_typeset},
+ {"!realpath", c_realpath},
+ {"~rename", c_rename},
+ {"*=return", c_exitreturn},
+ {Tsghset, c_set},
+ {"*=#shift", c_shift},
+ {Tgsource, c_dot},
+#if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
+ {Tsuspend, c_suspend},
+#endif
+ {"test", c_test},
+ {"*=times", c_times},
+ {"*=trap", c_trap},
+ {Ttrue, c_true},
+ {Tdgtypeset, c_typeset},
+ {"ulimit", c_ulimit},
+ {"umask", c_umask},
+ {Tunalias, c_unalias},
+ {"*=unset", c_unset},
+ {"wait", c_wait},
+ {"whence", c_whence},
+#ifndef MKSH_UNEMPLOYED
+ {Tbg, c_fgbg},
+ {Tfg, c_fgbg},
+#endif
+#ifndef MKSH_NO_CMDLINE_EDITING
+ {"bind", c_bind},
+#endif
+#if HAVE_MKNOD
+ {"mknod", c_mknod},
+#endif
+#ifdef MKSH_PRINTF_BUILTIN
+ {"~printf", c_printf},
+#endif
+#if HAVE_SELECT
+ {"sleep", c_sleep},
+#endif
+#ifdef __MirBSD__
+ /* alias to "true" for historical reasons */
+ {"domainname", c_true},
+#endif
+#ifdef __OS2__
+ {Textproc, c_true},
+#endif
+ {NULL, (int (*)(const char **))NULL}
+};
+
+struct kill_info {
+ int num_width;
+ int name_width;
+};
+
+const struct t_op u_ops[] = {
+/* 0*/ {"-a", TO_FILAXST },
+ {"-b", TO_FILBDEV },
+ {"-c", TO_FILCDEV },
+ {"-d", TO_FILID },
+ {"-e", TO_FILEXST },
+ {"-f", TO_FILREG },
+ {"-G", TO_FILGID },
+ {"-g", TO_FILSETG },
+ {"-H", TO_FILCDF },
+ {"-h", TO_FILSYM },
+ {"-k", TO_FILSTCK },
+ {"-L", TO_FILSYM },
+/*12*/ {"-n", TO_STNZE },
+ {"-O", TO_FILUID },
+/*14*/ {"-o", TO_OPTION },
+ {"-p", TO_FILFIFO },
+/*16*/ {"-r", TO_FILRD },
+ {"-S", TO_FILSOCK },
+ {"-s", TO_FILGZ },
+ {"-t", TO_FILTT },
+/*20*/ {"-u", TO_FILSETU },
+ {"-v", TO_ISSET },
+ {"-w", TO_FILWR },
+/*23*/ {"-x", TO_FILEX },
+ {"-z", TO_STZER },
+ {"", TO_NONOP }
+};
+cta(u_ops_size, NELEM(u_ops) == 26);
+const struct t_op b_ops[] = {
+ {"=", TO_STEQL },
+ {"==", TO_STEQL },
+ {"!=", TO_STNEQ },
+ {"<", TO_STLT },
+ {">", TO_STGT },
+ {"-eq", TO_INTEQ },
+ {"-ne", TO_INTNE },
+ {"-gt", TO_INTGT },
+ {"-ge", TO_INTGE },
+ {"-lt", TO_INTLT },
+ {"-le", TO_INTLE },
+ {"-ef", TO_FILEQ },
+ {"-nt", TO_FILNT },
+ {"-ot", TO_FILOT },
+ {"", TO_NONOP }
+};
+
+static int test_oexpr(Test_env *, bool);
+static int test_aexpr(Test_env *, bool);
+static int test_nexpr(Test_env *, bool);
+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 void kill_fmt_entry(char *, size_t, unsigned int, const void *);
+static void p_time(struct shf *, bool, long, int, int,
+ const char *, const char *);
+
+int
+c_pwd(const char **wp)
+{
+ int optc;
+ bool physical = tobool(Flag(FPHYSICAL));
+ char *p, *allocd = NULL;
+
+ 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 (wp[0]) {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+ 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())) {
+ bi_errorf(Tf_sD_s, "can't determine current directory",
+ cstrerror(errno));
+ return (1);
+ }
+ shprintf(Tf_sN, p);
+ afree(allocd, ATEMP);
+ return (0);
+}
+
+static const char *s_ptr;
+static int s_get(void);
+static void s_put(int);
+
+int
+c_print(const char **wp)
+{
+ int c;
+ const char *s;
+ char *xp;
+ XString xs;
+ struct {
+ /* storage for columnisation */
+ XPtrV words;
+ /* temporary storage for a wide character */
+ mksh_ari_t wc;
+ /* output file descriptor (if any) */
+ int fd;
+ /* temporary storage for a multibyte character */
+ char ts[4];
+ /* output word separator */
+ char ws;
+ /* output line separator */
+ char ls;
+ /* output a trailing line separator? */
+ bool nl;
+ /* expand backslash sequences? */
+ bool exp;
+ /* columnise output? */
+ bool col;
+ /* print to history instead of file descriptor / stdout? */
+ bool hist;
+ /* print words as wide characters? */
+ bool chars;
+ /* writing to a coprocess (SIGPIPE blocked)? */
+ bool coproc;
+ bool copipe;
+ } po;
+
+ memset(&po, 0, sizeof(po));
+ po.fd = 1;
+ po.ws = ' ';
+ po.ls = '\n';
+ po.nl = true;
+
+ if (wp[0][0] == 'e') {
+ /* "echo" builtin */
+ if (Flag(FPOSIX) ||
+#ifndef MKSH_MIDNIGHTBSD01ASH_COMPAT
+ Flag(FSH) ||
+#endif
+ as_builtin) {
+ /* BSD "echo" cmd, Debian Policy 10.4 compliant */
+ ++wp;
+ bsd_echo:
+ if (*wp && !strcmp(*wp, Tdn)) {
+ po.nl = false;
+ ++wp;
+ }
+ po.exp = false;
+ } else {
+ bool new_exp, new_nl = true;
+
+ /*-
+ * compromise between various historic echos: only
+ * recognise -Een if they appear in arguments with
+ * no illegal options; e.g. echo -nq outputs '-nq'
+ */
+#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
+ /* MidnightBSD /bin/sh needs -e supported but off */
+ if (Flag(FSH))
+ new_exp = false;
+ else
+#endif
+ /* otherwise compromise on -e enabled by default */
+ new_exp = true;
+ goto print_tradparse_beg;
+
+ print_tradparse_arg:
+ if ((s = *wp) && *s++ == '-' && *s) {
+ print_tradparse_ch:
+ switch ((c = *s++)) {
+ case 'E':
+ new_exp = false;
+ goto print_tradparse_ch;
+ case 'e':
+ new_exp = true;
+ goto print_tradparse_ch;
+ case 'n':
+ new_nl = false;
+ goto print_tradparse_ch;
+ case '\0':
+ print_tradparse_beg:
+ po.exp = new_exp;
+ po.nl = new_nl;
+ ++wp;
+ goto print_tradparse_arg;
+ }
+ }
+ }
+ } else {
+ /* "print" builtin */
+ const char *opts = "AcelNnpRrsu,";
+ const char *emsg;
+
+ po.exp = true;
+
+ while ((c = ksh_getopt(wp, &builtin_opt, opts)) != -1)
+ switch (c) {
+ case 'A':
+ po.chars = true;
+ break;
+ case 'c':
+ po.col = true;
+ break;
+ case 'e':
+ po.exp = true;
+ break;
+ case 'l':
+ po.ws = '\n';
+ break;
+ case 'N':
+ po.ws = '\0';
+ po.ls = '\0';
+ break;
+ case 'n':
+ po.nl = false;
+ break;
+ case 'p':
+ if ((po.fd = coproc_getfd(W_OK, &emsg)) < 0) {
+ bi_errorf(Tf_coproc, emsg);
+ return (1);
+ }
+ break;
+ case 'R':
+ /* fake BSD echo but don't reset other flags */
+ wp += builtin_opt.optind;
+ goto bsd_echo;
+ case 'r':
+ po.exp = false;
+ break;
+ case 's':
+ po.hist = true;
+ break;
+ case 'u':
+ if (!*(s = builtin_opt.optarg))
+ po.fd = 0;
+ else if ((po.fd = check_fd(s, W_OK, &emsg)) < 0) {
+ bi_errorf("-u%s: %s", s, emsg);
+ return (1);
+ }
+ break;
+ case '?':
+ return (1);
+ }
+
+ if (!(builtin_opt.info & GI_MINUSMINUS)) {
+ /* treat a lone "-" like "--" */
+ if (wp[builtin_opt.optind] &&
+ ksh_isdash(wp[builtin_opt.optind]))
+ builtin_opt.optind++;
+ }
+ wp += builtin_opt.optind;
+ }
+
+ if (po.col) {
+ if (*wp == NULL)
+ return (0);
+
+ XPinit(po.words, 16);
+ }
+
+ Xinit(xs, xp, 128, ATEMP);
+
+ if (*wp == NULL)
+ goto print_no_arg;
+ print_read_arg:
+ if (po.chars) {
+ while (*wp != NULL) {
+ s = *wp++;
+ if (*s == '\0')
+ break;
+ if (!evaluate(s, &po.wc, KSH_RETURN_ERROR, true))
+ return (1);
+ Xcheck(xs, xp);
+ if (UTFMODE) {
+ po.ts[utf_wctomb(po.ts, po.wc)] = 0;
+ c = 0;
+ do {
+ Xput(xs, xp, po.ts[c]);
+ } while (po.ts[++c]);
+ } else
+ Xput(xs, xp, po.wc & 0xFF);
+ }
+ } else {
+ s = *wp++;
+ while ((c = *s++) != '\0') {
+ Xcheck(xs, xp);
+ if (po.exp && c == '\\') {
+ s_ptr = s;
+ c = unbksl(false, s_get, s_put);
+ s = s_ptr;
+ if (c == -1) {
+ /* rejected by generic function */
+ switch ((c = *s++)) {
+ case 'c':
+ po.nl = false;
+ /* AT&T brain damage */
+ continue;
+ case '\0':
+ --s;
+ c = '\\';
+ break;
+ default:
+ Xput(xs, xp, '\\');
+ }
+ } else if ((unsigned int)c > 0xFF) {
+ /* generic function returned UCS */
+ po.ts[utf_wctomb(po.ts, c - 0x100)] = 0;
+ c = 0;
+ do {
+ Xput(xs, xp, po.ts[c]);
+ } while (po.ts[++c]);
+ continue;
+ }
+ }
+ Xput(xs, xp, c);
+ }
+ }
+ if (po.col) {
+ Xput(xs, xp, '\0');
+ XPput(po.words, Xclose(xs, xp));
+ Xinit(xs, xp, 128, ATEMP);
+ }
+ if (*wp != NULL) {
+ if (!po.col)
+ Xput(xs, xp, po.ws);
+ goto print_read_arg;
+ }
+ if (po.col) {
+ size_t w = XPsize(po.words);
+ struct columnise_opts co;
+
+ XPput(po.words, NULL);
+ co.shf = shf_sopen(NULL, 128, SHF_WR | SHF_DYNAMIC, NULL);
+ co.linesep = po.ls;
+ co.prefcol = co.do_last = false;
+ pr_list(&co, (char **)XPptrv(po.words));
+ while (w--)
+ afree(XPptrv(po.words)[w], ATEMP);
+ XPfree(po.words);
+ w = co.shf->wp - co.shf->buf;
+ XcheckN(xs, xp, w);
+ memcpy(xp, co.shf->buf, w);
+ xp += w;
+ shf_sclose(co.shf);
+ }
+ print_no_arg:
+ if (po.nl)
+ Xput(xs, xp, po.ls);
+
+ c = 0;
+ if (po.hist) {
+ Xput(xs, xp, '\0');
+ histsave(&source->line, Xstring(xs, xp), HIST_STORE, false);
+ } else {
+ size_t len = Xlength(xs, xp);
+
+ /*
+ * 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).
+ */
+ if (coproc.write >= 0 && coproc.write == po.fd) {
+ po.coproc = true;
+ po.copipe = block_pipe();
+ } else
+ po.coproc = po.copipe = false;
+
+ s = Xstring(xs, xp);
+ while (len > 0) {
+ ssize_t nwritten;
+
+ if ((nwritten = write(po.fd, s, len)) < 0) {
+ if (errno == EINTR) {
+ if (po.copipe)
+ restore_pipe();
+ /* give the user a chance to ^C out */
+ intrcheck();
+ /* interrupted, try again */
+ if (po.coproc)
+ po.copipe = block_pipe();
+ continue;
+ }
+ c = 1;
+ break;
+ }
+ s += nwritten;
+ len -= nwritten;
+ }
+ if (po.copipe)
+ restore_pipe();
+ }
+ Xfree(xs, xp);
+
+ return (c);
+}
+
+static int
+s_get(void)
+{
+ return (ord(*s_ptr++));
+}
+
+static void
+s_put(int c MKSH_A_UNUSED)
+{
+ --s_ptr;
+}
+
+int
+c_whence(const char **wp)
+{
+ int optc;
+ bool pflag = false, vflag = false;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, Tpv)) != -1)
+ switch (optc) {
+ case 'p':
+ pflag = true;
+ break;
+ case 'v':
+ vflag = true;
+ break;
+ case '?':
+ return (1);
+ }
+ wp += builtin_opt.optind;
+
+ return (do_whence(wp, pflag ? FC_PATH :
+ FC_BI | FC_FUNC | FC_PATH | FC_WHENCE, vflag, false));
+}
+
+/* note: command without -vV is dealt with in comexec() */
+int
+c_command(const char **wp)
+{
+ int optc, fcflags = FC_BI | FC_FUNC | FC_PATH | FC_WHENCE;
+ bool vflag = false;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, TpVv)) != -1)
+ switch (optc) {
+ case 'p':
+ fcflags |= FC_DEFPATH;
+ break;
+ case 'V':
+ vflag = true;
+ break;
+ case 'v':
+ vflag = false;
+ break;
+ case '?':
+ return (1);
+ }
+ wp += builtin_opt.optind;
+
+ return (do_whence(wp, fcflags, vflag, true));
+}
+
+static int
+do_whence(const char **wp, int fcflags, bool vflag, bool iscommand)
+{
+ uint32_t h;
+ int rv = 0;
+ struct tbl *tp;
+ const char *id;
+
+ while ((vflag || rv == 0) && (id = *wp++) != NULL) {
+ h = hash(id);
+ tp = NULL;
+
+ if (fcflags & FC_WHENCE)
+ tp = ktsearch(&keywords, id, h);
+ if (!tp && (fcflags & FC_WHENCE)) {
+ tp = ktsearch(&aliases, id, h);
+ if (tp && !(tp->flag & ISSET))
+ tp = NULL;
+ }
+ if (!tp)
+ tp = findcom(id, fcflags);
+
+ switch (tp->type) {
+ case CSHELL:
+ case CFUNC:
+ case CKEYWD:
+ shf_puts(id, shl_stdout);
+ break;
+ }
+
+ switch (tp->type) {
+ case CSHELL:
+ if (vflag)
+ shprintf(" is a %sshell %s",
+ (tp->flag & SPEC_BI) ? "special " : "",
+ Tbuiltin);
+ 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)
+ shf_puts(" traced", shl_stdout);
+ if (!(tp->flag & ISSET)) {
+ shf_puts(" undefined", shl_stdout);
+ if (tp->u.fpath)
+ shprintf(" (autoload from %s)",
+ tp->u.fpath);
+ }
+ shf_puts(T_function, shl_stdout);
+ }
+ break;
+ case CEXEC:
+ case CTALIAS:
+ if (tp->flag & ISSET) {
+ if (vflag) {
+ shprintf("%s is ", id);
+ if (tp->type == CTALIAS)
+ shprintf("a tracked %s%s for ",
+ (tp->flag & EXPORT) ?
+ "exported " : "",
+ Talias);
+ }
+ shf_puts(tp->val.s, shl_stdout);
+ } else {
+ if (vflag)
+ shprintf(Tnot_found_s, id);
+ rv = 1;
+ }
+ break;
+ case CALIAS:
+ if (!vflag && iscommand)
+ shprintf(Tf_s_, Talias);
+ if (vflag || iscommand)
+ print_value_quoted(shl_stdout, id);
+ if (vflag)
+ shprintf(" is an %s%s for ",
+ (tp->flag & EXPORT) ? "exported " : "",
+ Talias);
+ else if (iscommand)
+ shf_putc('=', shl_stdout);
+ print_value_quoted(shl_stdout, tp->val.s);
+ break;
+ case CKEYWD:
+ if (vflag)
+ shf_puts(" is a reserved word", shl_stdout);
+ break;
+#ifndef MKSH_SMALL
+ default:
+ bi_errorf(Tunexpected_type, id, Tcommand, tp->type);
+ return (1);
+#endif
+ }
+ if (vflag || !rv)
+ shf_putc('\n', shl_stdout);
+ }
+ return (rv);
+}
+
+bool
+valid_alias_name(const char *cp)
+{
+ switch (ord(*cp)) {
+ case ORD('+'):
+ case ORD('-'):
+ return (false);
+ case ORD('['):
+ if (ord(cp[1]) == ORD('[') && !cp[2])
+ return (false);
+ break;
+ }
+ while (*cp)
+ if (ctype(*cp, C_ALIAS))
+ ++cp;
+ else
+ return (false);
+ return (true);
+}
+
+int
+c_alias(const char **wp)
+{
+ struct table *t = &aliases;
+ int rv = 0, prefix = 0;
+ bool rflag = false, tflag, Uflag = false, pflag = false, chkalias;
+ uint32_t xflag = 0;
+ int optc;
+
+ builtin_opt.flags |= GF_PLUSOPT;
+ while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) {
+ prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
+ switch (optc) {
+ case 'd':
+#ifdef MKSH_NOPWNAM
+ t = NULL; /* fix "alias -dt" */
+#else
+ t = &homedirs;
+#endif
+ break;
+ case 'p':
+ pflag = true;
+ break;
+ case 'r':
+ rflag = true;
+ break;
+ case 't':
+ t = &taliases;
+ break;
+ case 'U':
+ /*
+ * kludge for tracked alias initialization
+ * (don't do a path search, just make an entry)
+ */
+ Uflag = true;
+ break;
+ case 'x':
+ xflag = EXPORT;
+ break;
+ case '?':
+ return (1);
+ }
+ }
+#ifdef MKSH_NOPWNAM
+ if (t == NULL)
+ return (0);
+#endif
+ wp += builtin_opt.optind;
+
+ if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
+ ctype(wp[0][0], C_MINUS | C_PLUS) && wp[0][1] == '\0') {
+ prefix = wp[0][0];
+ wp++;
+ }
+
+ tflag = t == &taliases;
+ chkalias = t == &aliases;
+
+ /* "hash -r" means reset all the tracked aliases.. */
+ if (rflag) {
+ static const char *args[] = {
+ Tunalias, "-ta", NULL
+ };
+
+ if (!tflag || *wp) {
+ shprintf("%s: -r flag can only be used with -t"
+ " and without arguments\n", Talias);
+ return (1);
+ }
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ return (c_unalias(args));
+ }
+
+ if (*wp == NULL) {
+ struct tbl *ap, **p;
+
+ for (p = ktsort(t); (ap = *p++) != NULL; )
+ if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
+ if (pflag)
+ shprintf(Tf_s_, Talias);
+ print_value_quoted(shl_stdout, ap->name);
+ if (prefix != '+') {
+ shf_putc('=', shl_stdout);
+ print_value_quoted(shl_stdout, ap->val.s);
+ }
+ shf_putc('\n', shl_stdout);
+ }
+ }
+
+ for (; *wp != NULL; wp++) {
+ const char *alias = *wp, *val, *newval;
+ char *xalias = NULL;
+ struct tbl *ap;
+ uint32_t h;
+
+ if ((val = cstrchr(alias, '='))) {
+ strndupx(xalias, alias, val++ - alias, ATEMP);
+ alias = xalias;
+ }
+ if (chkalias && !valid_alias_name(alias)) {
+ bi_errorf(Tinvname, alias, Talias);
+ afree(xalias, ATEMP);
+ return (1);
+ }
+ h = hash(alias);
+ if (val == NULL && !tflag && !xflag) {
+ ap = ktsearch(t, alias, h);
+ if (ap != NULL && (ap->flag&ISSET)) {
+ if (pflag)
+ shprintf(Tf_s_, Talias);
+ print_value_quoted(shl_stdout, ap->name);
+ if (prefix != '+') {
+ shf_putc('=', shl_stdout);
+ print_value_quoted(shl_stdout, ap->val.s);
+ }
+ shf_putc('\n', shl_stdout);
+ } else {
+ shprintf(Tf_s_s_sN, alias, Talias, Tnot_found);
+ rv = 1;
+ }
+ continue;
+ }
+ ap = ktenter(t, alias, h);
+ ap->type = tflag ? CTALIAS : CALIAS;
+ /* Are we setting the value or just some flags? */
+ if ((val && !tflag) || (!val && tflag && !Uflag)) {
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree(ap->val.s, APERM);
+ }
+ /* ignore values for -t (AT&T ksh does this) */
+ newval = tflag ?
+ search_path(alias, path, X_OK, NULL) :
+ val;
+ if (newval) {
+ strdupx(ap->val.s, newval, APERM);
+ ap->flag |= ALLOC|ISSET;
+ } else
+ ap->flag &= ~ISSET;
+ }
+ ap->flag |= DEFINED;
+ if (prefix == '+')
+ ap->flag &= ~xflag;
+ else
+ ap->flag |= xflag;
+ afree(xalias, ATEMP);
+ }
+
+ return (rv);
+}
+
+int
+c_unalias(const char **wp)
+{
+ struct table *t = &aliases;
+ struct tbl *ap;
+ int optc, rv = 0;
+ bool all = false;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1)
+ switch (optc) {
+ case 'a':
+ all = true;
+ break;
+ case 'd':
+#ifdef MKSH_NOPWNAM
+ /* fix "unalias -dt" */
+ t = NULL;
+#else
+ t = &homedirs;
+#endif
+ break;
+ case 't':
+ t = &taliases;
+ break;
+ case '?':
+ return (1);
+ }
+#ifdef MKSH_NOPWNAM
+ if (t == NULL)
+ return (0);
+#endif
+ wp += builtin_opt.optind;
+
+ for (; *wp != NULL; wp++) {
+ ap = ktsearch(t, *wp, hash(*wp));
+ if (ap == NULL) {
+ /* POSIX */
+ rv = 1;
+ continue;
+ }
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree(ap->val.s, APERM);
+ }
+ ap->flag &= ~(DEFINED|ISSET|EXPORT);
+ }
+
+ if (all) {
+ struct tstate ts;
+
+ for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) {
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree(ap->val.s, APERM);
+ }
+ ap->flag &= ~(DEFINED|ISSET|EXPORT);
+ }
+ }
+
+ return (rv);
+}
+
+int
+c_let(const char **wp)
+{
+ int rv = 1;
+ mksh_ari_t val;
+
+ if (wp[1] == NULL)
+ /* AT&T ksh does this */
+ bi_errorf(Tno_args);
+ else
+ for (wp++; *wp; wp++)
+ if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
+ /* distinguish error from zero result */
+ rv = 2;
+ break;
+ } else
+ rv = val == 0;
+ return (rv);
+}
+
+int
+c_jobs(const char **wp)
+{
+ int optc, flag = 0, nflag = 0, rv = 0;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1)
+ switch (optc) {
+ case 'l':
+ flag = 1;
+ break;
+ case 'p':
+ flag = 2;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'z':
+ /* debugging: print zombies */
+ nflag = -1;
+ break;
+ case '?':
+ return (1);
+ }
+ wp += builtin_opt.optind;
+ if (!*wp) {
+ if (j_jobs(NULL, flag, nflag))
+ rv = 1;
+ } else {
+ for (; *wp; wp++)
+ if (j_jobs(*wp, flag, nflag))
+ rv = 1;
+ }
+ return (rv);
+}
+
+#ifndef MKSH_UNEMPLOYED
+int
+c_fgbg(const char **wp)
+{
+ bool bg = strcmp(*wp, Tbg) == 0;
+ int rv = 0;
+
+ if (!Flag(FMONITOR)) {
+ bi_errorf("job control not enabled");
+ return (1);
+ }
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ wp += builtin_opt.optind;
+ if (*wp)
+ for (; *wp; wp++)
+ rv = j_resume(*wp, bg);
+ else
+ rv = j_resume("%%", bg);
+ /* fg returns $? of the job unless POSIX */
+ return ((bg | Flag(FPOSIX)) ? 0 : rv);
+}
+#endif
+
+/* format a single kill item */
+static void
+kill_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg)
+{
+ const struct kill_info *ki = (const struct kill_info *)arg;
+
+ i++;
+ shf_snprintf(buf, buflen, "%*u %*s %s",
+ ki->num_width, i,
+ ki->name_width, sigtraps[i].name,
+ sigtraps[i].mess);
+}
+
+int
+c_kill(const char **wp)
+{
+ Trap *t = NULL;
+ const char *p;
+ bool lflag = false;
+ int i, n, rv, sig;
+
+ /* assume old style options if -digits or -UPPERCASE */
+ if ((p = wp[1]) && *p == '-' && ctype(p[1], C_DIGIT | C_UPPER)) {
+ if (!(t = gettrap(p + 1, false, false))) {
+ bi_errorf(Tbad_sig_s, p + 1);
+ return (1);
+ }
+ i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
+ } else {
+ int optc;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1)
+ switch (optc) {
+ case 'l':
+ lflag = true;
+ break;
+ case 's':
+ if (!(t = gettrap(builtin_opt.optarg,
+ true, false))) {
+ bi_errorf(Tbad_sig_s,
+ builtin_opt.optarg);
+ return (1);
+ }
+ break;
+ case '?':
+ return (1);
+ }
+ i = builtin_opt.optind;
+ }
+ if ((lflag && t) || (!wp[i] && !lflag)) {
+#ifndef MKSH_SMALL
+ shf_puts("usage:\tkill [-s signame | -signum | -signame]"
+ " { job | pid | pgrp } ...\n"
+ "\tkill -l [exit_status ...]\n", shl_out);
+#endif
+ bi_errorfz();
+ return (1);
+ }
+
+ if (lflag) {
+ if (wp[i]) {
+ for (; wp[i]; i++) {
+ if (!bi_getn(wp[i], &n))
+ return (1);
+#if (ksh_NSIG <= 128)
+ if (n > 128 && n < 128 + ksh_NSIG)
+ n -= 128;
+#endif
+ if (n > 0 && n < ksh_NSIG)
+ shprintf(Tf_sN, sigtraps[n].name);
+ else
+ shprintf(Tf_dN, n);
+ }
+ } else if (Flag(FPOSIX)) {
+ n = 1;
+ while (n < ksh_NSIG) {
+ shf_puts(sigtraps[n].name, shl_stdout);
+ shf_putc(++n == ksh_NSIG ? '\n' : ' ',
+ shl_stdout);
+ }
+ } else {
+ ssize_t w, mess_cols = 0, mess_octs = 0;
+ int j = ksh_NSIG - 1;
+ struct kill_info ki = { 0, 0 };
+ struct columnise_opts co;
+
+ do {
+ ki.num_width++;
+ } while ((j /= 10));
+
+ for (j = 1; j < ksh_NSIG; j++) {
+ w = strlen(sigtraps[j].name);
+ if (w > ki.name_width)
+ ki.name_width = w;
+ w = strlen(sigtraps[j].mess);
+ if (w > mess_octs)
+ mess_octs = w;
+ w = utf_mbswidth(sigtraps[j].mess);
+ if (w > mess_cols)
+ mess_cols = w;
+ }
+
+ co.shf = shl_stdout;
+ co.linesep = '\n';
+ co.prefcol = co.do_last = true;
+
+ print_columns(&co, (unsigned int)(ksh_NSIG - 1),
+ kill_fmt_entry, (void *)&ki,
+ ki.num_width + 1 + ki.name_width + 1 + mess_octs,
+ ki.num_width + 1 + ki.name_width + 1 + mess_cols);
+ }
+ return (0);
+ }
+ rv = 0;
+ sig = t ? t->signal : SIGTERM;
+ for (; (p = wp[i]); i++) {
+ if (*p == '%') {
+ if (j_kill(p, sig))
+ rv = 1;
+ } else if (!getn(p, &n)) {
+ bi_errorf(Tf_sD_s, p,
+ "arguments must be jobs or process IDs");
+ rv = 1;
+ } else {
+ if (mksh_kill(n, sig) < 0) {
+ bi_errorf(Tf_sD_s, p, cstrerror(errno));
+ rv = 1;
+ }
+ }
+ }
+ return (rv);
+}
+
+void
+getopts_reset(int val)
+{
+ if (val >= 1) {
+ ksh_getopt_reset(&user_opt, GF_NONAME |
+ (Flag(FPOSIX) ? 0 : GF_PLUSOPT));
+ user_opt.optind = user_opt.uoptind = val;
+ }
+}
+
+int
+c_getopts(const char **wp)
+{
+ int argc, optc, rv;
+ const char *opts, *var;
+ char buf[3];
+ struct tbl *vq, *voptarg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ wp += builtin_opt.optind;
+
+ opts = *wp++;
+ if (!opts) {
+ bi_errorf(Tf_sD_s, "options", Tno_args);
+ return (1);
+ }
+
+ var = *wp++;
+ if (!var) {
+ bi_errorf(Tf_sD_s, Tname, Tno_args);
+ return (1);
+ }
+ if (!*var || *skip_varname(var, true)) {
+ bi_errorf(Tf_sD_s, var, Tnot_ident);
+ return (1);
+ }
+
+ if (e->loc->next == NULL) {
+ internal_warningf(Tf_sD_s, Tgetopts, Tno_args);
+ return (1);
+ }
+ /* Which arguments are we parsing... */
+ if (*wp == NULL)
+ wp = e->loc->next->argv;
+ else
+ *--wp = e->loc->next->argv[0];
+
+ /* Check that our saved state won't cause a core dump... */
+ for (argc = 0; wp[argc]; argc++)
+ ;
+ if (user_opt.optind > argc ||
+ (user_opt.p != 0 &&
+ user_opt.p > strlen(wp[user_opt.optind - 1]))) {
+ bi_errorf("arguments changed since last call");
+ return (1);
+ }
+
+ user_opt.optarg = NULL;
+ optc = ksh_getopt(wp, &user_opt, opts);
+
+ if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
+ buf[0] = '+';
+ buf[1] = optc;
+ buf[2] = '\0';
+ } else {
+ /*
+ * 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;
+ buf[1] = '\0';
+ }
+
+ /* AT&T ksh93 in fact does change OPTIND for unknown options too */
+ user_opt.uoptind = user_opt.optind;
+
+ voptarg = global("OPTARG");
+ /* 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);
+ if (user_opt.optarg == NULL)
+ unset(voptarg, 1);
+ else
+ /* this can't fail (haing cleared readonly/integer) */
+ setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
+
+ rv = 0;
+
+ vq = global(var);
+ /* Error message already printed (integer, readonly) */
+ if (!setstr(vq, buf, KSH_RETURN_ERROR))
+ rv = 2;
+ if (Flag(FEXPORT))
+ typeset(var, EXPORT, 0, 0, 0);
+
+ return (optc < 0 ? 1 : rv);
+}
+
+#ifndef MKSH_NO_CMDLINE_EDITING
+int
+c_bind(const char **wp)
+{
+ int optc, rv = 0;
+#ifndef MKSH_SMALL
+ bool macro = false;
+#endif
+
+ if (x_bind_check()) {
+ bi_errorf("can't bind, not a tty");
+ return (1);
+ }
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1)
+ switch (optc) {
+ case 'l':
+ return (x_bind_list());
+#ifndef MKSH_SMALL
+ case 'm':
+ macro = true;
+ break;
+#endif
+ default:
+ return (1);
+ }
+ wp += builtin_opt.optind;
+
+ if (*wp == NULL)
+ return (x_bind_showall());
+
+ do {
+ rv |= x_bind(*wp SMALLP(macro));
+ } while (*++wp);
+
+ return (rv);
+}
+#endif
+
+int
+c_shift(const char **wp)
+{
+ int n;
+ mksh_ari_t val;
+ const char *arg;
+ struct block *l = e->loc;
+
+ if ((l->flags & BF_RESETSPEC)) {
+ /* prevent pollution */
+ l->flags &= ~BF_RESETSPEC;
+ /* operate on parent environment */
+ l = l->next;
+ }
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ arg = wp[builtin_opt.optind];
+
+ if (!arg)
+ n = 1;
+ else if (!evaluate(arg, &val, KSH_RETURN_ERROR, false)) {
+ /* error already printed */
+ bi_errorfz();
+ return (1);
+ } else if (!(n = val)) {
+ /* nothing to do */
+ return (0);
+ } else if (n < 0) {
+ bi_errorf(Tf_sD_s, Tbadnum, arg);
+ return (1);
+ }
+
+ if (l->argc < n) {
+ bi_errorf("nothing to shift");
+ return (1);
+ }
+ l->argv[n] = l->argv[0];
+ l->argv += n;
+ l->argc -= n;
+ return (0);
+}
+
+int
+c_umask(const char **wp)
+{
+ int i, optc;
+ const char *cp;
+ bool symbolic = false;
+ mode_t old_umask;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1)
+ switch (optc) {
+ case 'S':
+ symbolic = true;
+ break;
+ case '?':
+ return (1);
+ }
+ cp = wp[builtin_opt.optind];
+ if (cp == NULL) {
+ old_umask = umask((mode_t)0);
+ umask(old_umask);
+ if (symbolic) {
+ char buf[18], *p;
+ int j;
+
+ old_umask = ~old_umask;
+ p = buf;
+ for (i = 0; i < 3; i++) {
+ *p++ = Tugo[i];
+ *p++ = '=';
+ for (j = 0; j < 3; j++)
+ if (old_umask & (1 << (8 - (3*i + j))))
+ *p++ = "rwx"[j];
+ *p++ = ',';
+ }
+ p[-1] = '\0';
+ shprintf(Tf_sN, buf);
+ } else
+ shprintf("%#3.3o\n", (unsigned int)old_umask);
+ } else {
+ mode_t new_umask;
+
+ if (ctype(*cp, C_DIGIT)) {
+ new_umask = 0;
+ while (ctype(*cp, C_OCTAL)) {
+ new_umask = new_umask * 8 + ksh_numdig(*cp);
+ ++cp;
+ }
+ if (*cp) {
+ bi_errorf(Tbadnum);
+ return (1);
+ }
+ } else {
+ /* symbolic format */
+ int positions, new_val;
+ char op;
+
+ old_umask = umask((mode_t)0);
+ /* in case of error */
+ umask(old_umask);
+ old_umask = ~old_umask;
+ new_umask = old_umask;
+ positions = 0;
+ while (*cp) {
+ while (*cp && vstrchr(Taugo, *cp))
+ switch (*cp++) {
+ case 'a':
+ positions |= 0111;
+ break;
+ case 'u':
+ positions |= 0100;
+ break;
+ case 'g':
+ positions |= 0010;
+ break;
+ case 'o':
+ positions |= 0001;
+ break;
+ }
+ if (!positions)
+ /* default is a */
+ positions = 0111;
+ if (!ctype((op = *cp), C_EQUAL | C_MINUS | C_PLUS))
+ break;
+ cp++;
+ new_val = 0;
+ while (*cp && vstrchr("rwxugoXs", *cp))
+ switch (*cp++) {
+ case 'r': new_val |= 04; break;
+ case 'w': new_val |= 02; break;
+ case 'x': new_val |= 01; break;
+ case 'u':
+ new_val |= old_umask >> 6;
+ break;
+ case 'g':
+ new_val |= old_umask >> 3;
+ break;
+ case 'o':
+ new_val |= old_umask >> 0;
+ break;
+ case 'X':
+ if (old_umask & 0111)
+ new_val |= 01;
+ break;
+ case 's':
+ /* ignored */
+ break;
+ }
+ new_val = (new_val & 07) * positions;
+ switch (op) {
+ case '-':
+ new_umask &= ~new_val;
+ break;
+ case '=':
+ new_umask = new_val |
+ (new_umask & ~(positions * 07));
+ break;
+ case '+':
+ new_umask |= new_val;
+ }
+ if (*cp == ',') {
+ positions = 0;
+ cp++;
+ } else if (!ctype(*cp, C_EQUAL | C_MINUS | C_PLUS))
+ break;
+ }
+ if (*cp) {
+ bi_errorf("bad mask");
+ return (1);
+ }
+ new_umask = ~new_umask;
+ }
+ umask(new_umask);
+ }
+ return (0);
+}
+
+int
+c_dot(const char **wp)
+{
+ const char *file, *cp, **argv;
+ int argc, rv, errcode;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+
+ if ((cp = wp[builtin_opt.optind]) == NULL) {
+ bi_errorf(Tno_args);
+ return (1);
+ }
+ file = search_path(cp, path, R_OK, &errcode);
+ if (!file && errcode == ENOENT && wp[0][0] == 's' &&
+ search_access(cp, R_OK) == 0)
+ file = cp;
+ if (!file) {
+ bi_errorf(Tf_sD_s, cp, cstrerror(errcode));
+ return (1);
+ }
+
+ /* Set positional parameters? */
+ if (wp[builtin_opt.optind + 1]) {
+ argv = wp + builtin_opt.optind;
+ /* preserve $0 */
+ argv[0] = e->loc->argv[0];
+ for (argc = 0; argv[argc + 1]; argc++)
+ ;
+ } else {
+ argc = 0;
+ argv = NULL;
+ }
+ /* SUSv4: OR with a high value never written otherwise */
+ exstat |= 0x4000;
+ if ((rv = include(file, argc, argv, false)) < 0) {
+ /* should not happen */
+ bi_errorf(Tf_sD_s, cp, cstrerror(errno));
+ return (1);
+ }
+ if (exstat & 0x4000)
+ /* detect old exstat, use 0 in that case */
+ rv = 0;
+ return (rv);
+}
+
+int
+c_wait(const char **wp)
+{
+ int rv = 0, sig;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ wp += builtin_opt.optind;
+ if (*wp == NULL) {
+ while (waitfor(NULL, &sig) >= 0)
+ ;
+ rv = sig;
+ } else {
+ for (; *wp; wp++)
+ rv = waitfor(*wp, &sig);
+ if (rv < 0)
+ /* magic exit code: bad job-id */
+ rv = sig ? sig : 127;
+ }
+ return (rv);
+}
+
+int
+c_read(const char **wp)
+{
+#define is_ifsws(c) (ctype((c), C_IFS) && ctype((c), C_IFSWS))
+ int c, fd = 0, rv = 0;
+ bool savehist = false, intoarray = false, aschars = false;
+ bool rawmode = false, expanding = false;
+ bool lastparmmode = false, lastparmused = false;
+ enum { LINES, BYTES, UPTO, READALL } readmode = LINES;
+ char delim = '\n';
+ size_t bytesleft = 128, bytesread;
+ struct tbl *vp /* FU gcc */ = NULL, *vq = NULL;
+ char *cp, *allocd = NULL, *xp;
+ const char *ccp;
+ XString xs;
+ size_t xsave = 0;
+ mksh_ttyst tios;
+ bool restore_tios = false;
+ /* to catch read -aN2 foo[i] */
+ bool subarray = 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
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+ int saved_mode;
+ int saved_errno;
+#endif
+
+ while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1)
+ switch (c) {
+ 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 = readmode == BYTES ? READALL : UPTO;
+ bytesleft = 1024;
+ } else
+ bytesleft = (unsigned int)c;
+ break;
+ case 'p':
+ if ((fd = coproc_getfd(R_OK, &ccp)) < 0) {
+ bi_errorf(Tf_coproc, 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(Tf_sD_s_qs, Tsynerr, cstrerror(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(Tf_sD_sD_s, Tdu, builtin_opt.optarg, ccp);
+ return (2);
+ }
+ break;
+ case '?':
+ return (2);
+ }
+ wp += builtin_opt.optind;
+ if (*wp == NULL)
+ *--wp = TREPLY;
+
+ if (intoarray && wp[1] != NULL) {
+ bi_errorf(Ttoo_many_args);
+ return (2);
+ }
+
+ 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
+ * 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 Korn Shell book).
+ */
+ shf_puts(ccp + 1, shl_out);
+ shf_flush(shl_out);
+ }
+ }
+
+ Xinit(xs, xp, bytesleft, ATEMP);
+
+ if (readmode == LINES)
+ bytesleft = 1;
+ else if (isatty(fd)) {
+ x_mkraw(fd, &tios, true);
+ restore_tios = true;
+ }
+
+#if HAVE_SELECT
+ if (hastimeout) {
+ mksh_TIME(tvlim);
+ timeradd(&tvlim, &tv, &tvlim);
+ }
+#endif
+
+ c_read_readloop:
+#if HAVE_SELECT
+ if (hastimeout) {
+ fd_set fdset;
+
+ FD_ZERO(&fdset);
+ FD_SET((unsigned int)fd, &fdset);
+ mksh_TIME(tv);
+ timersub(&tvlim, &tv, &tv);
+ if (tv.tv_sec < 0) {
+ /* timeout expired globally */
+ rv = 3;
+ goto c_read_out;
+ }
+
+ switch (select(fd + 1, &fdset, NULL, NULL, &tv)) {
+ case 1:
+ break;
+ case 0:
+ /* timeout expired for this call */
+ bytesread = 0;
+ rv = 3;
+ goto c_read_readdone;
+ default:
+ bi_errorf(Tf_sD_s, Tselect, cstrerror(errno));
+ rv = 2;
+ goto c_read_out;
+ }
+ }
+#endif
+
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+ saved_mode = setmode(fd, O_TEXT);
+#endif
+ if ((bytesread = blocking_read(fd, xp, bytesleft)) == (size_t)-1) {
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+ saved_errno = errno;
+ setmode(fd, saved_mode);
+ errno = saved_errno;
+#endif
+ if (errno == EINTR) {
+ /* check whether the signal would normally kill */
+ if (!fatal_trap_check()) {
+ /* no, just ignore the signal */
+ goto c_read_readloop;
+ }
+ /* pretend the read was killed */
+ } else {
+ /* unexpected error */
+ bi_errorf(Tf_s, cstrerror(errno));
+ }
+ rv = 2;
+ goto c_read_out;
+ }
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+ setmode(fd, saved_mode);
+#endif
+
+ 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;
+ /* may be partial read: $? = 1, but content */
+ goto c_read_readdone;
+ }
+ xp += bytesread;
+ if ((bytesleft -= bytesread) == 0)
+ goto c_read_readdone;
+ break;
+ case LINES:
+ if (bytesread == 0) {
+ /* end of file reached */
+ rv = 1;
+ goto c_read_readdone;
+ }
+ if ((c = *xp) == '\0' && !aschars && delim != '\0') {
+ /* skip any read NULs unless delimiter */
+ break;
+ }
+ if (expanding) {
+ expanding = false;
+ if (c == delim) {
+ if (Flag(FTALKING_I) && isatty(fd)) {
+ /*
+ * set prompt in case this is
+ * called from .profile or $ENV
+ */
+ set_prompt(PS2, NULL);
+ pprompt(prompt, 0);
+ }
+ /* drop the backslash */
+ --xp;
+ /* and the delimiter */
+ break;
+ }
+ } else if (c == delim) {
+ goto c_read_readdone;
+ } else if (!rawmode && c == '\\') {
+ expanding = true;
+ }
+ Xcheck(xs, xp);
+ ++xp;
+ break;
+ }
+ goto c_read_readloop;
+
+ c_read_readdone:
+ bytesread = Xlength(xs, xp);
+ Xput(xs, xp, '\0');
+
+ /*-
+ * state: we finished reading the input and NUL terminated it
+ * Xstring(xs, xp) -> xp-1 = input string without trailing delim
+ * rv = 3 if timeout, 1 if EOF, 0 otherwise (errors handled already)
+ */
+
+ if (rv) {
+ /* clean up coprocess if needed, on EOF/error/timeout */
+ coproc_read_close(fd);
+ if (readmode == READALL && (rv == 1 || (rv == 3 && bytesread)))
+ /* EOF is no error here */
+ rv = 0;
+ }
+
+ if (savehist)
+ histsave(&source->line, Xstring(xs, xp), HIST_STORE, false);
+
+ ccp = cp = Xclose(xs, xp);
+ expanding = false;
+ XinitN(xs, 128, ATEMP);
+ if (intoarray) {
+ vp = global(*wp);
+ subarray = last_lookup_was_array;
+ if (vp->flag & RDONLY) {
+ c_read_splitro:
+ bi_errorf(Tf_ro, *wp);
+ c_read_spliterr:
+ rv = 2;
+ afree(cp, ATEMP);
+ goto c_read_out;
+ }
+ /* counter for array index */
+ c = subarray ? arrayindex(vp) : 0;
+ /* exporting an array is currently pointless */
+ unset(vp, subarray ? 0 : 1);
+ }
+ 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)
+ lastparmmode = true;
+
+ 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 */
+ expanding = false;
+ while (bytesread) {
+ char ch;
+
+ ch = *ccp;
+ if (!ctype(ch, C_IFS))
+ break;
+ if (lastparmmode && !expanding && !rawmode && ch == '\\') {
+ expanding = true;
+ } else {
+ Xcheck(xs, xp);
+ Xput(xs, xp, ch);
+ }
+ ++ccp;
+ --bytesread;
+ if (expanding)
+ continue;
+ 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 (lastparmmode && bytesread) {
+ lastparmused = true;
+ goto c_read_splitlast;
+ }
+ /* get rid of the delimiter unless we pack the rest */
+ if (!lastparmused)
+ xp = Xrestpos(xs, xp, xsave);
+ c_read_gotword:
+ Xput(xs, xp, '\0');
+ if (intoarray) {
+ if (subarray) {
+ /* array element passed, accept first read */
+ if (vq) {
+ bi_errorf("nested arrays not yet supported");
+ goto c_read_spliterr;
+ }
+ vq = vp;
+ if (c)
+ /* [0] doesn't */
+ vq->flag |= AINDEX;
+ } else
+ 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(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 (intoarray || *++wp != NULL)
+ goto c_read_splitloop;
+
+ c_read_splitdone:
+ /* free up */
+ afree(cp, ATEMP);
+
+ c_read_out:
+ afree(allocd, ATEMP);
+ Xfree(xs, xp);
+ if (restore_tios)
+ mksh_tcset(fd, &tios);
+ return (rv == 3 ? ksh_sigmask(SIGALRM) : rv);
+#undef is_ifsws
+}
+
+int
+c_eval(const char **wp)
+{
+ struct source *s, *saves = source;
+ int rv;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ s = pushs(SWORDS, ATEMP);
+ s->u.strv = wp + builtin_opt.optind;
+ s->line = current_lineno;
+
+ /*-
+ * The following code handles the case where the command is
+ * empty due to failed command substitution, for example by
+ * eval "$(false)"
+ * This has historically returned 1 by AT&T ksh88. In this
+ * case, shell() will not set or change exstat because the
+ * compiled tree is empty, so it will use the value we pass
+ * from subst_exstat, which is cleared in execute(), so it
+ * should have been 0 if there were no substitutions.
+ *
+ * POSIX however says we don't do this, even though it is
+ * traditionally done. AT&T ksh93 agrees with POSIX, so we
+ * do. The following is an excerpt from SUSv4 [1003.2-2008]:
+ *
+ * 2.9.1: Simple Commands
+ * ... If there is a command name, execution shall
+ * continue as described in 2.9.1.1 [Command Search
+ * and Execution]. If there is no command name, but
+ * the command contained a command substitution, the
+ * command shall complete with the exit status of the
+ * last command substitution performed.
+ * 2.9.1.1: Command Search and Execution
+ * (1) a. If the command name matches the name of a
+ * special built-in utility, that special built-in
+ * utility shall be invoked.
+ * 2.14.5: eval
+ * If there are no arguments, or only null arguments,
+ * eval shall return a zero exit status; ...
+ */
+ /* AT&T ksh88: use subst_exstat */
+ /* exstat = subst_exstat; */
+ /* SUSv4: OR with a high value never written otherwise */
+ exstat |= 0x4000;
+
+ rv = shell(s, 2);
+ source = saves;
+ afree(s, ATEMP);
+ if (exstat & 0x4000)
+ /* detect old exstat, use 0 in that case */
+ rv = 0;
+ return (rv);
+}
+
+int
+c_trap(const char **wp)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG;
+ const char *s;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return (1);
+ wp += builtin_opt.optind;
+
+ if (*wp == NULL) {
+ do {
+ if (p->trap) {
+ shf_puts("trap -- ", shl_stdout);
+ print_value_quoted(shl_stdout, p->trap);
+ shprintf(Tf__sN, p->name);
+ }
+ ++p;
+ } while (i--);
+ return (0);
+ }
+
+ if (getn(*wp, &i)) {
+ /* first argument is a signal number, reset them all */
+ s = NULL;
+ } else {
+ /* first argument must be a command, then */
+ s = *wp++;
+ /* reset traps? */
+ if (ksh_isdash(s))
+ s = NULL;
+ }
+
+ /* set/clear the traps */
+ i = 0;
+ while (*wp)
+ if (!(p = gettrap(*wp++, true, true))) {
+ warningf(true, Tbad_sig_ss, builtin_argv0, wp[-1]);
+ i = 1;
+ } else
+ settrap(p, s);
+ return (i);
+}
+
+int
+c_exitreturn(const char **wp)
+{
+ int n, how = LEXIT;
+
+ if (wp[1]) {
+ if (wp[2])
+ goto c_exitreturn_err;
+ exstat = bi_getn(wp[1], &n) ? (n & 0xFF) : 1;
+ } 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
+ * work right (POSIX)
+ */
+ for (ep = e; ep; ep = ep->oenv)
+ if (STOP_RETURN(ep->type)) {
+ how = LRETURN;
+ break;
+ }
+ }
+
+ if (how == LEXIT && !really_exit && j_stopped_running()) {
+ really_exit = true;
+ how = LSHELL;
+ }
+
+ /* get rid of any I/O redirections */
+ quitenv(NULL);
+ unwind(how);
+ /* NOTREACHED */
+
+ c_exitreturn_err:
+ bi_errorf(Ttoo_many_args);
+ return (1);
+}
+
+int
+c_brkcont(const char **wp)
+{
+ unsigned int quit;
+ int n;
+ struct env *ep, *last_ep = NULL;
+ const char *arg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ goto c_brkcont_err;
+ arg = wp[builtin_opt.optind];
+
+ if (!arg)
+ n = 1;
+ else if (!bi_getn(arg, &n))
+ goto c_brkcont_err;
+ if (n <= 0) {
+ /* AT&T ksh does this for non-interactive shells only - weird */
+ bi_errorf("%s: bad value", arg);
+ goto c_brkcont_err;
+ }
+ quit = (unsigned int)n;
+
+ /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */
+ for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv)
+ if (ep->type == E_LOOP) {
+ if (--quit == 0)
+ break;
+ ep->flags |= EF_BRKCONT_PASS;
+ last_ep = ep;
+ }
+
+ if (quit) {
+ /*
+ * 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 ((unsigned int)n == quit) {
+ warningf(true, Tf_cant_s, wp[0], wp[0]);
+ return (0);
+ }
+ /*
+ * 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.
+ */
+ if (last_ep)
+ last_ep->flags &= ~EF_BRKCONT_PASS;
+ warningf(true, "%s: can only %s %u level(s)",
+ wp[0], wp[0], (unsigned int)n - quit);
+ }
+
+ unwind(*wp[0] == 'b' ? LBREAK : LCONTIN);
+ /* NOTREACHED */
+
+ c_brkcont_err:
+ return (1);
+}
+
+int
+c_set(const char **wp)
+{
+ int argi;
+ bool setargs;
+ struct block *l = e->loc;
+
+ if ((l->flags & BF_RESETSPEC)) {
+ /* prevent pollution */
+ l->flags &= ~BF_RESETSPEC;
+ /* operate on parent environment */
+ l = l->next;
+ }
+
+ if (wp[1] == NULL) {
+ static const char *args[] = { Tset, "-", NULL };
+ return (c_typeset(args));
+ }
+
+ if ((argi = parse_args(wp, OF_SET, &setargs)) < 0)
+ return (2);
+ /* set $# and $* */
+ if (setargs) {
+ const char **owp;
+
+ wp += argi - 1;
+ owp = wp;
+ /* save $0 */
+ wp[0] = l->argv[0];
+ while (*++wp != NULL)
+ strdupx(*wp, *wp, &l->area);
+ l->argc = wp - owp - 1;
+ l->argv = alloc2(l->argc + 2, sizeof(char *), &l->area);
+ for (wp = l->argv; (*wp++ = *owp++) != NULL; )
+ ;
+ }
+ /*-
+ * POSIX says set exit status is 0, but old scripts that use
+ * getopt(1) use the construct
+ * set -- $(getopt ab:c "$@")
+ * which assumes the exit value set will be that of the $()
+ * (subst_exstat is cleared in execute() so that it will be 0
+ * if there are no command substitutions).
+ */
+#ifdef MKSH_LEGACY_MODE
+ /* traditional behaviour, unless set -o posix */
+ return (Flag(FPOSIX) ? 0 : subst_exstat);
+#else
+ /* conformant behaviour, unless set -o sh +o posix */
+ return (Flag(FSH) && !Flag(FPOSIX) ? subst_exstat : 0);
+#endif
+}
+
+int
+c_unset(const char **wp)
+{
+ const char *id;
+ int optc, rv = 0;
+ bool unset_var = true;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
+ switch (optc) {
+ case 'f':
+ unset_var = false;
+ break;
+ case 'v':
+ unset_var = true;
+ break;
+ case '?':
+ /*XXX not reached due to GF_ERROR */
+ return (2);
+ }
+ wp += builtin_opt.optind;
+ for (; (id = *wp) != NULL; wp++)
+ if (unset_var) {
+ /* unset variable */
+ struct tbl *vp;
+ char *cp = NULL;
+ size_t n;
+
+ n = strlen(id);
+ if (n > 3 && ord(id[n - 3]) == ORD('[') &&
+ ord(id[n - 2]) == ORD('*') &&
+ ord(id[n - 1]) == ORD(']')) {
+ strndupx(cp, id, n - 3, ATEMP);
+ id = cp;
+ optc = 3;
+ } else
+ optc = vstrchr(id, '[') ? 0 : 1;
+
+ vp = global(id);
+ afree(cp, ATEMP);
+
+ if ((vp->flag&RDONLY)) {
+ warningf(true, Tf_ro, vp->name);
+ rv = 1;
+ } else
+ unset(vp, optc);
+ } else
+ /* unset function */
+ define(id, NULL);
+ return (rv);
+}
+
+static void
+p_time(struct shf *shf, bool posix, long tv_sec, int tv_usec, int width,
+ const char *prefix, const char *suffix)
+{
+ tv_usec /= 10000;
+ if (posix)
+ shf_fprintf(shf, "%s%*ld.%02d%s", prefix, width,
+ tv_sec, tv_usec, suffix);
+ else
+ shf_fprintf(shf, "%s%*ldm%02d.%02ds%s", prefix, width,
+ tv_sec / 60, (int)(tv_sec % 60), tv_usec, suffix);
+}
+
+int
+c_times(const char **wp MKSH_A_UNUSED)
+{
+ struct rusage usage;
+
+ getrusage(RUSAGE_SELF, &usage);
+ p_time(shl_stdout, false, usage.ru_utime.tv_sec,
+ usage.ru_utime.tv_usec, 0, null, T1space);
+ p_time(shl_stdout, false, usage.ru_stime.tv_sec,
+ usage.ru_stime.tv_usec, 0, null, "\n");
+
+ getrusage(RUSAGE_CHILDREN, &usage);
+ p_time(shl_stdout, false, usage.ru_utime.tv_sec,
+ usage.ru_utime.tv_usec, 0, null, T1space);
+ p_time(shl_stdout, false, usage.ru_stime.tv_sec,
+ usage.ru_stime.tv_usec, 0, null, "\n");
+
+ return (0);
+}
+
+/*
+ * time pipeline (really a statement, not a built-in command)
+ */
+int
+timex(struct op *t, int f, volatile int *xerrok)
+{
+#define TF_NOARGS BIT(0)
+#define TF_NOREAL BIT(1) /* don't report real time */
+#define TF_POSIX BIT(2) /* report in POSIX format */
+ int rv = 0, tf = 0;
+ struct rusage ru0, ru1, cru0, cru1;
+ struct timeval usrtime, systime, tv0, tv1;
+
+ mksh_TIME(tv0);
+ getrusage(RUSAGE_SELF, &ru0);
+ getrusage(RUSAGE_CHILDREN, &cru0);
+ if (t->left) {
+ /*
+ * Two ways of getting cpu usage of a command: just use t0
+ * and t1 (which will get cpu usage from other jobs that
+ * finish while we are executing t->left), or get the
+ * cpu usage of t->left. AT&T ksh does the former, while
+ * pdksh tries to do the later (the j_usrtime hack doesn't
+ * really work as it only counts the last job).
+ */
+ timerclear(&j_usrtime);
+ timerclear(&j_systime);
+ rv = execute(t->left, f | XTIME, xerrok);
+ if (t->left->type == TCOM)
+ tf |= t->left->str[0];
+ mksh_TIME(tv1);
+ getrusage(RUSAGE_SELF, &ru1);
+ getrusage(RUSAGE_CHILDREN, &cru1);
+ } else
+ tf = TF_NOARGS;
+
+ 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);
+ } else {
+ timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime);
+ timeradd(&usrtime, &j_usrtime, &usrtime);
+ timersub(&ru1.ru_stime, &ru0.ru_stime, &systime);
+ timeradd(&systime, &j_systime, &systime);
+ }
+
+ if (!(tf & TF_NOREAL)) {
+ timersub(&tv1, &tv0, &tv1);
+ if (tf & TF_POSIX)
+ p_time(shl_out, true, tv1.tv_sec, tv1.tv_usec,
+ 5, Treal_sp1, "\n");
+ else
+ p_time(shl_out, false, tv1.tv_sec, tv1.tv_usec,
+ 5, null, Treal_sp2);
+ }
+ if (tf & TF_POSIX)
+ p_time(shl_out, true, usrtime.tv_sec, usrtime.tv_usec,
+ 5, Tuser_sp1, "\n");
+ else
+ p_time(shl_out, false, usrtime.tv_sec, usrtime.tv_usec,
+ 5, null, Tuser_sp2);
+ if (tf & TF_POSIX)
+ p_time(shl_out, true, systime.tv_sec, systime.tv_usec,
+ 5, "sys ", "\n");
+ else
+ p_time(shl_out, false, systime.tv_sec, systime.tv_usec,
+ 5, null, " system\n");
+ shf_flush(shl_out);
+
+ return (rv);
+}
+
+void
+timex_hook(struct op *t, char **volatile *app)
+{
+ char **wp = *app;
+ int optc, i, j;
+ Getopt opt;
+
+ ksh_getopt_reset(&opt, 0);
+ /* 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(Tf_optfoo, Ttime, Tcolsp,
+ opt.optarg[0], Tunknown_option);
+ case ':':
+ errorf(Tf_optfoo, Ttime, Tcolsp,
+ opt.optarg[0], Treq_arg);
+ }
+ /* Copy command words down over options. */
+ if (opt.optind != 0) {
+ for (i = 0; i < opt.optind; i++)
+ afree(wp[i], ATEMP);
+ for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++)
+ ;
+ }
+ if (!wp[0])
+ t->str[0] |= TF_NOARGS;
+ *app = wp;
+}
+
+/* exec with no args - args case is taken care of in comexec() */
+int
+c_exec(const char **wp MKSH_A_UNUSED)
+{
+ int i;
+
+ /* make sure redirects stay in place */
+ if (e->savefd != NULL) {
+ for (i = 0; i < NUFILE; i++) {
+ if (e->savefd[i] > 0)
+ close(e->savefd[i]);
+ /*
+ * keep all file descriptors > 2 private for ksh,
+ * but not for POSIX or legacy/kludge sh
+ */
+ if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 &&
+ e->savefd[i])
+ fcntl(i, F_SETFD, FD_CLOEXEC);
+ }
+ e->savefd = NULL;
+ }
+ return (0);
+}
+
+#if HAVE_MKNOD && !defined(__OS2__)
+int
+c_mknod(const char **wp)
+{
+ int argc, optc, rv = 0;
+ bool ismkfifo = false;
+ const char **argv;
+ void *set = NULL;
+ mode_t mode = 0, oldmode = 0;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "m:")) != -1) {
+ switch (optc) {
+ case 'm':
+ set = setmode(builtin_opt.optarg);
+ if (set == NULL) {
+ bi_errorf("invalid file mode");
+ return (1);
+ }
+ mode = getmode(set, (mode_t)(DEFFILEMODE));
+ free_ossetmode(set);
+ break;
+ default:
+ goto c_mknod_usage;
+ }
+ }
+ argv = &wp[builtin_opt.optind];
+ if (argv[0] == NULL)
+ goto c_mknod_usage;
+ for (argc = 0; argv[argc]; argc++)
+ ;
+ if (argc == 2 && argv[1][0] == 'p')
+ ismkfifo = true;
+ else if (argc != 4 || (argv[1][0] != 'b' && argv[1][0] != 'c'))
+ goto c_mknod_usage;
+
+ if (set != NULL)
+ oldmode = umask((mode_t)0);
+ else
+ mode = DEFFILEMODE;
+
+ mode |= (argv[1][0] == 'b') ? S_IFBLK :
+ (argv[1][0] == 'c') ? S_IFCHR : 0;
+
+ if (!ismkfifo) {
+ unsigned long majnum, minnum;
+ dev_t dv;
+ char *c;
+
+ majnum = strtoul(argv[2], &c, 0);
+ if ((c == argv[2]) || (*c != '\0')) {
+ bi_errorf(Tf_nonnum, "device", "major", argv[2]);
+ goto c_mknod_err;
+ }
+ minnum = strtoul(argv[3], &c, 0);
+ if ((c == argv[3]) || (*c != '\0')) {
+ bi_errorf(Tf_nonnum, "device", "minor", argv[3]);
+ goto c_mknod_err;
+ }
+ dv = makedev(majnum, minnum);
+ if ((unsigned long)(major(dv)) != majnum) {
+ bi_errorf(Tf_toolarge, "device", "major", majnum);
+ goto c_mknod_err;
+ }
+ if ((unsigned long)(minor(dv)) != minnum) {
+ bi_errorf(Tf_toolarge, "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(Tf_sD_s, argv[0], cstrerror(errno));
+ c_mknod_err:
+ rv = 1;
+ }
+
+ if (set)
+ umask(oldmode);
+ return (rv);
+ c_mknod_usage:
+ bi_errorf("usage: mknod [-m mode] name %s", "b|c major minor");
+ bi_errorf("usage: mknod [-m mode] name %s", "p");
+ return (1);
+}
+#endif
+
+/*-
+ * test(1) roughly accepts the following grammar:
+ * oexpr ::= aexpr | aexpr "-o" oexpr ;
+ * aexpr ::= nexpr | nexpr "-a" aexpr ;
+ * nexpr ::= primary | "!" nexpr ;
+ * primary ::= unary-operator operand
+ * | operand binary-operator operand
+ * | operand
+ * | "(" oexpr ")"
+ * ;
+ *
+ * unary-operator ::= "-a"|"-b"|"-c"|"-d"|"-e"|"-f"|"-G"|"-g"|"-H"|"-h"|
+ * "-k"|"-L"|"-n"|"-O"|"-o"|"-p"|"-r"|"-S"|"-s"|"-t"|
+ * "-u"|"-v"|"-w"|"-x"|"-z";
+ *
+ * binary-operator ::= "="|"=="|"!="|"<"|">"|"-eq"|"-ne"|"-gt"|"-ge"|
+ * "-lt"|"-le"|"-ef"|"-nt"|"-ot";
+ *
+ * operand ::= <anything>
+ */
+
+/* POSIX says > 1 for errors */
+#define T_ERR_EXIT 2
+
+int
+c_test(const char **wp)
+{
+ int argc, rv, invert = 0;
+ Test_env te;
+ Test_op op;
+ Test_meta tm;
+ const char *lhs, **swp;
+
+ te.flags = 0;
+ te.isa = ptest_isa;
+ te.getopnd = ptest_getopnd;
+ te.eval = test_eval;
+ te.error = ptest_error;
+
+ for (argc = 0; wp[argc]; argc++)
+ ;
+
+ if (strcmp(wp[0], Tbracket) == 0) {
+ if (strcmp(wp[--argc], "]") != 0) {
+ bi_errorf("missing ]");
+ return (T_ERR_EXIT);
+ }
+ }
+
+ te.pos.wp = wp + 1;
+ te.wp_end = wp + argc;
+
+ /*
+ * Attempt to conform to POSIX special cases. This is pretty
+ * dumb code straight-forward from the 2008 spec, but unlike
+ * the old pdksh code doesn't live from so many assumptions.
+ * It does, though, inline some calls to '(*te.funcname)()'.
+ */
+ switch (argc - 1) {
+ case 0:
+ return (1);
+ case 1:
+ ptest_one:
+ op = TO_STNZE;
+ goto ptest_unary;
+ case 2:
+ ptest_two:
+ if (ptest_isa(&te, TM_NOT)) {
+ ++invert;
+ goto ptest_one;
+ }
+ if ((op = ptest_isa(&te, TM_UNOP))) {
+ ptest_unary:
+ rv = test_eval(&te, op, *te.pos.wp++, NULL, true);
+ ptest_out:
+ if (te.flags & TEF_ERROR)
+ return (T_ERR_EXIT);
+ return ((invert & 1) ? rv : !rv);
+ }
+ /* let the parser deal with anything else */
+ break;
+ case 3:
+ ptest_three:
+ swp = te.pos.wp;
+ /* use inside knowledge of ptest_getopnd inlined below */
+ lhs = *te.pos.wp++;
+ if ((op = ptest_isa(&te, TM_BINOP))) {
+ /* test lhs op rhs */
+ rv = test_eval(&te, op, lhs, *te.pos.wp++, true);
+ goto ptest_out;
+ }
+ if (ptest_isa(&te, tm = TM_AND) || ptest_isa(&te, tm = TM_OR)) {
+ /* XSI */
+ argc = test_eval(&te, TO_STNZE, lhs, NULL, true);
+ rv = test_eval(&te, TO_STNZE, *te.pos.wp++, NULL, true);
+ if (tm == TM_AND)
+ rv = argc && rv;
+ else
+ rv = argc || rv;
+ goto ptest_out;
+ }
+ /* back up to lhs */
+ te.pos.wp = swp;
+ if (ptest_isa(&te, TM_NOT)) {
+ ++invert;
+ goto ptest_two;
+ }
+ if (ptest_isa(&te, TM_OPAREN)) {
+ swp = te.pos.wp;
+ /* skip operand, without evaluation */
+ te.pos.wp++;
+ /* check for closing parenthesis */
+ op = ptest_isa(&te, TM_CPAREN);
+ /* back up to operand */
+ te.pos.wp = swp;
+ /* if there was a closing paren, handle it */
+ if (op)
+ goto ptest_one;
+ /* backing up is done before calling the parser */
+ }
+ /* let the parser deal with it */
+ break;
+ case 4:
+ if (ptest_isa(&te, TM_NOT)) {
+ ++invert;
+ goto ptest_three;
+ }
+ if (ptest_isa(&te, TM_OPAREN)) {
+ swp = te.pos.wp;
+ /* skip two operands, without evaluation */
+ te.pos.wp++;
+ te.pos.wp++;
+ /* check for closing parenthesis */
+ op = ptest_isa(&te, TM_CPAREN);
+ /* back up to first operand */
+ te.pos.wp = swp;
+ /* if there was a closing paren, handle it */
+ if (op)
+ goto ptest_two;
+ /* backing up is done before calling the parser */
+ }
+ /* defer this to the parser */
+ break;
+ }
+
+ /* "The results are unspecified." */
+ te.pos.wp = wp + 1;
+ return (test_parse(&te));
+}
+
+/*
+ * Generic test routines.
+ */
+
+Test_op
+test_isop(Test_meta meta, const char *s)
+{
+ char sc1;
+ const struct t_op *tbl;
+
+ tbl = meta == TM_UNOP ? u_ops : b_ops;
+ if (*s) {
+ sc1 = s[1];
+ for (; tbl->op_text[0]; tbl++)
+ if (sc1 == tbl->op_text[1] && !strcmp(s, tbl->op_text))
+ return (tbl->op_num);
+ }
+ return (TO_NONOP);
+}
+
+#ifdef __OS2__
+#define test_access(name,mode) access_ex(access, (name), (mode))
+#define test_stat(name,buffer) stat_ex(stat, (name), (buffer))
+#define test_lstat(name,buffer) stat_ex(lstat, (name), (buffer))
+#else
+#define test_access(name,mode) access((name), (mode))
+#define test_stat(name,buffer) stat((name), (buffer))
+#define test_lstat(name,buffer) lstat((name), (buffer))
+#endif
+
+#if HAVE_ST_MTIM
+#undef st_mtimensec
+#define st_mtimensec st_mtim.tv_nsec
+#endif
+
+static int
+mtimecmp(const struct stat *sb1, const struct stat *sb2)
+{
+ if (sb1->st_mtime < sb2->st_mtime)
+ return (-1);
+ if (sb1->st_mtime > sb2->st_mtime)
+ return (1);
+#if (HAVE_ST_MTIMENSEC || HAVE_ST_MTIM)
+ if (sb1->st_mtimensec < sb2->st_mtimensec)
+ return (-1);
+ if (sb1->st_mtimensec > sb2->st_mtimensec)
+ return (1);
+#endif
+ return (0);
+}
+
+int
+test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+ bool do_eval)
+{
+ int i, s;
+ size_t k;
+ struct stat b1, b2;
+ mksh_ari_t v1, v2;
+ struct tbl *vp;
+
+ if (!do_eval)
+ return (0);
+
+#ifdef DEBUG
+ switch (op) {
+ /* Binary operators */
+ case TO_STEQL:
+ case TO_STNEQ:
+ case TO_STLT:
+ case TO_STGT:
+ case TO_INTEQ:
+ case TO_INTNE:
+ case TO_INTGT:
+ case TO_INTGE:
+ case TO_INTLT:
+ case TO_INTLE:
+ case TO_FILEQ:
+ case TO_FILNT:
+ case TO_FILOT:
+ /* consistency check, but does not happen in practice */
+ if (!opnd2) {
+ te->flags |= TEF_ERROR;
+ return (1);
+ }
+ break;
+ default:
+ /* for completeness of switch */
+ break;
+ }
+#endif
+
+ switch (op) {
+
+ /*
+ * Unary Operators
+ */
+
+ /* -n */
+ case TO_STNZE:
+ return (*opnd1 != '\0');
+
+ /* -z */
+ case TO_STZER:
+ return (*opnd1 == '\0');
+
+ /* -v */
+ case TO_ISSET:
+ return ((vp = isglobal(opnd1, false)) && (vp->flag & ISSET));
+
+ /* -o */
+ case TO_OPTION:
+ if ((i = *opnd1) == '!' || i == '?')
+ opnd1++;
+ if ((k = option(opnd1)) == (size_t)-1)
+ return (0);
+ return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k));
+
+ /* -r */
+ case TO_FILRD:
+ /* LINTED use of access */
+ return (test_access(opnd1, R_OK) == 0);
+
+ /* -w */
+ case TO_FILWR:
+ /* LINTED use of access */
+ return (test_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 (test_stat(opnd1, &b1) == 0);
+
+ /* -f */
+ case TO_FILREG:
+ return (test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
+
+ /* -d */
+ case TO_FILID:
+ return (test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
+
+ /* -c */
+ case TO_FILCDEV:
+ return (test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
+
+ /* -b */
+ case TO_FILBDEV:
+ return (test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
+
+ /* -p */
+ case TO_FILFIFO:
+ return (test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
+
+ /* -h or -L */
+ case TO_FILSYM:
+#ifdef MKSH__NO_SYMLINK
+ return (0);
+#else
+ return (test_lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
+#endif
+
+ /* -S */
+ case TO_FILSOCK:
+ return (test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
+
+ /* -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 = (test_stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode));
+ afree(nv, ATEMP);
+ return (i);
+ }
+#else
+ return (0);
+#endif
+
+ /* -u */
+ case TO_FILSETU:
+ return (test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISUID) == S_ISUID);
+
+ /* -g */
+ case TO_FILSETG:
+ return (test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISGID) == S_ISGID);
+
+ /* -k */
+ case TO_FILSTCK:
+#ifdef S_ISVTX
+ return (test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISVTX) == S_ISVTX);
+#else
+ return (0);
+#endif
+
+ /* -s */
+ case TO_FILGZ:
+ return (test_stat(opnd1, &b1) == 0 &&
+ (off_t)b1.st_size > (off_t)0);
+
+ /* -t */
+ case TO_FILTT:
+ if (opnd1 && !bi_getn(opnd1, &i)) {
+ te->flags |= TEF_ERROR;
+ i = 0;
+ } else
+ i = isatty(opnd1 ? i : 0);
+ return (i);
+
+ /* -O */
+ case TO_FILUID:
+ return (test_stat(opnd1, &b1) == 0 &&
+ (uid_t)b1.st_uid == ksheuid);
+
+ /* -G */
+ case TO_FILGID:
+ return (test_stat(opnd1, &b1) == 0 &&
+ (gid_t)b1.st_gid == kshegid);
+
+ /*
+ * Binary Operators
+ */
+
+ /* =, == */
+ case TO_STEQL:
+ if (te->flags & TEF_DBRACKET) {
+ if ((i = gmatchx(opnd1, opnd2, false)))
+ record_match(opnd1);
+ return (i);
+ }
+ return (strcmp(opnd1, opnd2) == 0);
+
+ /* != */
+ case TO_STNEQ:
+ if (te->flags & TEF_DBRACKET) {
+ if ((i = gmatchx(opnd1, opnd2, false)))
+ record_match(opnd1);
+ return (!i);
+ }
+ return (strcmp(opnd1, opnd2) != 0);
+
+ /* < */
+ case TO_STLT:
+ return (strcmp(opnd1, opnd2) < 0);
+
+ /* > */
+ case TO_STGT:
+ return (strcmp(opnd1, opnd2) > 0);
+
+ /* -nt */
+ case TO_FILNT:
+ /*
+ * ksh88/ksh93 succeed if file2 can't be stated
+ * (subtly different from 'does not exist').
+ */
+ return (test_stat(opnd1, &b1) == 0 &&
+ (((s = test_stat(opnd2, &b2)) == 0 &&
+ mtimecmp(&b1, &b2) > 0) || s < 0));
+
+ /* -ot */
+ case TO_FILOT:
+ /*
+ * ksh88/ksh93 succeed if file1 can't be stated
+ * (subtly different from 'does not exist').
+ */
+ return (test_stat(opnd2, &b2) == 0 &&
+ (((s = test_stat(opnd1, &b1)) == 0 &&
+ mtimecmp(&b1, &b2) < 0) || s < 0));
+
+ /* -ef */
+ case TO_FILEQ:
+ return (test_stat(opnd1, &b1) == 0 &&
+ test_stat(opnd2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
+
+ /* all other cases */
+ case TO_NONOP:
+ case TO_NONNULL:
+ /* throw the error */
+ break;
+
+ /* -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 (op) {
+ case TO_INTEQ:
+ return (v1 == v2);
+ case TO_INTNE:
+ return (v1 != v2);
+ case TO_INTGE:
+ return (v1 >= v2);
+ case TO_INTGT:
+ return (v1 > v2);
+ case TO_INTLE:
+ return (v1 <= v2);
+ case TO_INTLT:
+ return (v1 < v2);
+ default:
+ /* NOTREACHED */
+ break;
+ }
+ /* NOTREACHED */
+ }
+ (*te->error)(te, 0, "internal error: unknown op");
+ return (1);
+}
+
+int
+test_parse(Test_env *te)
+{
+ int rv;
+
+ rv = test_oexpr(te, 1);
+
+ if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
+ (*te->error)(te, 0, "unexpected operator/operand");
+
+ return ((te->flags & TEF_ERROR) ? T_ERR_EXIT : !rv);
+}
+
+static int
+test_oexpr(Test_env *te, bool do_eval)
+{
+ int rv;
+
+ if ((rv = test_aexpr(te, do_eval)))
+ do_eval = false;
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
+ return (test_oexpr(te, do_eval) || rv);
+ return (rv);
+}
+
+static int
+test_aexpr(Test_env *te, bool do_eval)
+{
+ int rv;
+
+ if (!(rv = test_nexpr(te, do_eval)))
+ do_eval = false;
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
+ return (test_aexpr(te, do_eval) && rv);
+ return (rv);
+}
+
+static int
+test_nexpr(Test_env *te, bool do_eval)
+{
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
+ return (!test_nexpr(te, do_eval));
+ return (test_primary(te, do_eval));
+}
+
+static int
+test_primary(Test_env *te, bool do_eval)
+{
+ const char *opnd1, *opnd2;
+ int rv;
+ Test_op op;
+
+ if (te->flags & TEF_ERROR)
+ return (0);
+ if ((*te->isa)(te, TM_OPAREN)) {
+ rv = test_oexpr(te, do_eval);
+ if (te->flags & TEF_ERROR)
+ return (0);
+ if (!(*te->isa)(te, TM_CPAREN)) {
+ (*te->error)(te, 0, "missing )");
+ return (0);
+ }
+ return (rv);
+ }
+ /*
+ * Binary should have precedence over unary in this case
+ * so that something like test \( -f = -f \) is accepted
+ */
+ if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end &&
+ !test_isop(TM_BINOP, te->pos.wp[1]))) {
+ if ((op = (*te->isa)(te, TM_UNOP))) {
+ /* unary expression */
+ opnd1 = (*te->getopnd)(te, op, do_eval);
+ if (!opnd1) {
+ (*te->error)(te, -1, Tno_args);
+ return (0);
+ }
+
+ return ((*te->eval)(te, op, opnd1, NULL, do_eval));
+ }
+ }
+ opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
+ if (!opnd1) {
+ (*te->error)(te, 0, "expression expected");
+ return (0);
+ }
+ if ((op = (*te->isa)(te, TM_BINOP))) {
+ /* binary expression */
+ opnd2 = (*te->getopnd)(te, op, do_eval);
+ if (!opnd2) {
+ (*te->error)(te, -1, "missing second argument");
+ return (0);
+ }
+
+ return ((*te->eval)(te, op, opnd1, opnd2, do_eval));
+ }
+ return ((*te->eval)(te, TO_STNZE, opnd1, NULL, do_eval));
+}
+
+/*
+ * Plain test (test and [ .. ]) specific routines.
+ */
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+ptest_isa(Test_env *te, Test_meta meta)
+{
+ /* Order important - indexed by Test_meta values */
+ static const char * const tokens[] = {
+ Tdo, Tda, "!", "(", ")"
+ };
+ Test_op rv;
+
+ if (te->pos.wp >= te->wp_end)
+ return (meta == TM_END ? TO_NONNULL : TO_NONOP);
+
+ if (meta == TM_UNOP || meta == TM_BINOP)
+ rv = test_isop(meta, *te->pos.wp);
+ else if (meta == TM_END)
+ rv = TO_NONOP;
+ else
+ rv = !strcmp(*te->pos.wp, tokens[(int)meta]) ?
+ TO_NONNULL : TO_NONOP;
+
+ /* Accept the token? */
+ if (rv != TO_NONOP)
+ te->pos.wp++;
+
+ return (rv);
+}
+
+static const char *
+ptest_getopnd(Test_env *te, Test_op op, bool do_eval MKSH_A_UNUSED)
+{
+ if (te->pos.wp >= te->wp_end)
+ return (op == TO_FILTT ? "1" : NULL);
+ return (*te->pos.wp++);
+}
+
+static void
+ptest_error(Test_env *te, int ofs, const char *msg)
+{
+ const char *op;
+
+ te->flags |= TEF_ERROR;
+ if ((op = te->pos.wp + ofs >= te->wp_end ? NULL : te->pos.wp[ofs]))
+ bi_errorf(Tf_sD_s, op, msg);
+ else
+ bi_errorf(Tf_s, msg);
+}
+
+#ifndef MKSH_NO_LIMITS
+#define SOFT 0x1
+#define HARD 0x2
+
+/* Magic to divine the 'm' and 'v' limits */
+
+#ifdef RLIMIT_AS
+#if !defined(RLIMIT_VMEM) || (RLIMIT_VMEM == RLIMIT_AS) || \
+ !defined(RLIMIT_RSS) || (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_V_IS_AS
+#elif defined(RLIMIT_VMEM)
+#if !defined(RLIMIT_RSS) || (RLIMIT_RSS == RLIMIT_AS)
+#define ULIMIT_V_IS_AS
+#else
+#define ULIMIT_V_IS_VMEM
+#endif
+#endif
+#endif
+
+#ifdef RLIMIT_RSS
+#ifdef ULIMIT_V_IS_VMEM
+#define ULIMIT_M_IS_RSS
+#elif defined(RLIMIT_VMEM) && (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_M_IS_VMEM
+#else
+#define ULIMIT_M_IS_RSS
+#endif
+#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS) && !defined(__APPLE__)
+#undef ULIMIT_M_IS_RSS
+#endif
+#endif
+
+#if !defined(RLIMIT_AS) && !defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_VMEM)
+#define ULIMIT_V_IS_VMEM
+#endif
+
+#if !defined(ULIMIT_V_IS_VMEM) && defined(RLIMIT_VMEM) && \
+ (!defined(RLIMIT_RSS) || (defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)))
+#define ULIMIT_M_IS_VMEM
+#endif
+
+#if defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_AS) && \
+ (RLIMIT_VMEM == RLIMIT_AS)
+#undef ULIMIT_M_IS_VMEM
+#endif
+
+#if defined(ULIMIT_M_IS_RSS) && defined(ULIMIT_M_IS_VMEM)
+# error nonsensical m ulimit
+#endif
+
+#if defined(ULIMIT_V_IS_VMEM) && defined(ULIMIT_V_IS_AS)
+# error nonsensical v ulimit
+#endif
+
+struct limits {
+ /* limit resource */
+ int resource;
+ /* multiply by to get rlim_{cur,max} values */
+ unsigned int factor;
+ /* getopts char */
+ char optchar;
+ /* limit name */
+ char name[1];
+};
+
+#define RLIMITS_DEFNS
+#define FN(lname,lid,lfac,lopt) \
+ static const struct { \
+ int resource; \
+ unsigned int factor; \
+ char optchar; \
+ char name[sizeof(lname)]; \
+ } rlimits_ ## lid = { \
+ lid, lfac, lopt, lname \
+ };
+#include "rlimits.gen"
+
+static void print_ulimit(const struct limits *, int);
+static int set_ulimit(const struct limits *, const char *, int);
+
+static const struct limits * const rlimits[] = {
+#define RLIMITS_ITEMS
+#include "rlimits.gen"
+};
+
+static const char rlimits_opts[] =
+#define RLIMITS_OPTCS
+#include "rlimits.gen"
+ ;
+
+int
+c_ulimit(const char **wp)
+{
+ size_t i = 0;
+ int how = SOFT | HARD, optc, what = 'f';
+ bool all = false;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, rlimits_opts)) != -1)
+ switch (optc) {
+ case 'H':
+ how = HARD;
+ break;
+ case 'S':
+ how = SOFT;
+ break;
+ case 'a':
+ all = true;
+ break;
+ case '?':
+ bi_errorf("usage: ulimit [-%s] [value]", rlimits_opts);
+ return (1);
+ default:
+ what = optc;
+ }
+
+ while (i < NELEM(rlimits)) {
+ if (rlimits[i]->optchar == what)
+ goto found;
+ ++i;
+ }
+ internal_warningf("ulimit: %c", what);
+ return (1);
+ found:
+ if (wp[builtin_opt.optind]) {
+ if (all || wp[builtin_opt.optind + 1]) {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+ return (set_ulimit(rlimits[i], wp[builtin_opt.optind], how));
+ }
+ if (!all)
+ print_ulimit(rlimits[i], how);
+ else for (i = 0; i < NELEM(rlimits); ++i) {
+ shprintf("-%c: %-20s ", rlimits[i]->optchar, rlimits[i]->name);
+ print_ulimit(rlimits[i], how);
+ }
+ return (0);
+}
+
+static int
+set_ulimit(const struct limits *l, const char *v, int how)
+{
+ rlim_t val = (rlim_t)0;
+ struct rlimit limit;
+
+ if (strcmp(v, "unlimited") == 0)
+ val = (rlim_t)RLIM_INFINITY;
+ else {
+ mksh_uari_t rval;
+
+ if (!evaluate(v, (mksh_ari_t *)&rval, KSH_RETURN_ERROR, false))
+ return (1);
+ /*
+ * Avoid problems caused by typos that evaluate misses due
+ * to evaluating unset parameters to 0...
+ * If this causes problems, will have to add parameter to
+ * evaluate() to control if unset params are 0 or an error.
+ */
+ if (!rval && !ctype(v[0], C_DIGIT)) {
+ bi_errorf("invalid %s limit: %s", l->name, v);
+ return (1);
+ }
+ val = (rlim_t)((rlim_t)rval * l->factor);
+ }
+
+ if (getrlimit(l->resource, &limit) < 0) {
+#ifndef MKSH_SMALL
+ bi_errorf("limit %s could not be read, contact the mksh developers: %s",
+ l->name, cstrerror(errno));
+#endif
+ /* some can't be read */
+ limit.rlim_cur = RLIM_INFINITY;
+ limit.rlim_max = RLIM_INFINITY;
+ }
+ if (how & SOFT)
+ limit.rlim_cur = val;
+ if (how & HARD)
+ limit.rlim_max = val;
+ if (!setrlimit(l->resource, &limit))
+ return (0);
+ if (errno == EPERM)
+ bi_errorf("%s exceeds allowable %s limit", v, l->name);
+ else
+ bi_errorf("bad %s limit: %s", l->name, cstrerror(errno));
+ return (1);
+}
+
+static void
+print_ulimit(const struct limits *l, int how)
+{
+ rlim_t val = (rlim_t)0;
+ struct rlimit limit;
+
+ if (getrlimit(l->resource, &limit)) {
+ shf_puts("unknown\n", shl_stdout);
+ return;
+ }
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+ if (val == (rlim_t)RLIM_INFINITY)
+ shf_puts("unlimited\n", shl_stdout);
+ else
+ shprintf("%lu\n", (unsigned long)(val / l->factor));
+}
+#endif
+
+int
+c_rename(const char **wp)
+{
+ int rv = 1;
+
+ /* 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(Tf_sD_s, "failed", cstrerror(rv));
+ }
+
+ return (rv);
+}
+
+int
+c_realpath(const char **wp)
+{
+ int rv = 1;
+ char *buf;
+
+ /* skip argv[0] */
+ ++wp;
+ if (wp[0] && !strcmp(wp[0], "--"))
+ /* skip "--" (options separator) */
+ ++wp;
+
+ /* 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(Tf_sD_s, wp[0], cstrerror(rv));
+ if ((unsigned int)rv > 255)
+ rv = 255;
+ } else {
+ shprintf(Tf_sN, 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;
+ bool opipe;
+#define MKSH_CAT_BUFSIZ 4096
+
+ /* parse options: POSIX demands we support "-u" as no-op */
+ while ((rv = ksh_getopt(wp, &builtin_opt, Tu)) != -1) {
+ switch (rv) {
+ case 'u':
+ /* we already operate unbuffered */
+ break;
+ default:
+ bi_errorf(Tsynerr);
+ return (1);
+ }
+ }
+ wp += builtin_opt.optind;
+ rv = 0;
+
+ if ((buf = malloc_osfunc(MKSH_CAT_BUFSIZ)) == NULL) {
+ bi_errorf(Toomem, (size_t)MKSH_CAT_BUFSIZ);
+ return (1);
+ }
+
+ /* catch SIGPIPE */
+ opipe = block_pipe();
+
+ do {
+ if (*wp) {
+ fn = *wp++;
+ if (ksh_isdash(fn))
+ fd = STDIN_FILENO;
+ else if ((fd = binopen2(fn, O_RDONLY)) < 0) {
+ bi_errorf(Tf_sD_s, fn, cstrerror(errno));
+ rv = 1;
+ continue;
+ }
+ }
+ while (/* CONSTCOND */ 1) {
+ if ((n = blocking_read(fd, (cp = buf),
+ MKSH_CAT_BUFSIZ)) == -1) {
+ if (errno == EINTR) {
+ if (opipe)
+ restore_pipe();
+ /* give the user a chance to ^C out */
+ intrcheck();
+ /* interrupted, try again */
+ opipe = block_pipe();
+ continue;
+ }
+ /* an error occured during reading */
+ bi_errorf(Tf_sD_s, fn, cstrerror(errno));
+ rv = 1;
+ break;
+ } else if (n == 0)
+ /* end of file reached */
+ break;
+ while (n) {
+ if (intrsig)
+ goto has_intrsig;
+ if ((w = write(STDOUT_FILENO, cp, n)) != -1) {
+ n -= w;
+ cp += w;
+ continue;
+ }
+ if (errno == EINTR) {
+ has_intrsig:
+ if (opipe)
+ restore_pipe();
+ /* give the user a chance to ^C out */
+ intrcheck();
+ /* interrupted, try again */
+ opipe = block_pipe();
+ continue;
+ }
+ if (errno == EPIPE) {
+ /* fake receiving signal */
+ rv = ksh_sigmask(SIGPIPE);
+ } else {
+ /* an error occured during writing */
+ bi_errorf(Tf_sD_s, "<stdout>",
+ cstrerror(errno));
+ rv = 1;
+ }
+ if (fd != STDIN_FILENO)
+ close(fd);
+ goto out;
+ }
+ }
+ if (fd != STDIN_FILENO)
+ close(fd);
+ } while (*wp);
+
+ out:
+ if (opipe)
+ restore_pipe();
+ 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(Tf_sD_s_qs, Tsynerr, cstrerror(errno), wp[0]);
+ else {
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigset_t omask, bmask;
+
+ /* block a number of signals from interrupting us, though */
+ (void)sigemptyset(&bmask);
+ (void)sigaddset(&bmask, SIGPIPE);
+ (void)sigaddset(&bmask, SIGCHLD);
+#ifdef SIGWINCH
+ (void)sigaddset(&bmask, SIGWINCH);
+#endif
+#ifdef SIGINFO
+ (void)sigaddset(&bmask, SIGINFO);
+#endif
+#ifdef SIGUSR1
+ (void)sigaddset(&bmask, SIGUSR1);
+#endif
+#ifdef SIGUSR2
+ (void)sigaddset(&bmask, SIGUSR2);
+#endif
+ sigprocmask(SIG_BLOCK, &bmask, &omask);
+#endif
+ if (select(1, 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(Tf_sD_s, Tselect, cstrerror(errno));
+#ifndef MKSH_NOPROSPECTOFWORK
+ /* this will re-schedule signal delivery */
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ }
+ return (rv);
+}
+#endif
+
+#if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
+static int
+c_suspend(const char **wp)
+{
+ if (wp[1] != NULL) {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+ if (Flag(FLOGIN)) {
+ /* Can't suspend an orphaned process group. */
+ if (getpgid(kshppid) == getpgid(0) ||
+ getsid(kshppid) != getsid(0)) {
+ bi_errorf("can't suspend a login shell");
+ return (1);
+ }
+ }
+ j_suspend();
+ return (0);
+}
+#endif
diff --git a/shells/mksh/files/histrap.c b/shells/mksh/files/histrap.c
new file mode 100644
index 00000000000..84211c61fbb
--- /dev/null
+++ b/shells/mksh/files/histrap.c
@@ -0,0 +1,1630 @@
+/* $OpenBSD: history.c,v 1.41 2015/09/01 13:12:31 tedu Exp $ */
+/* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2014, 2015, 2016, 2017, 2018, 2019
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+#if HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.169 2019/09/16 21:10:33 tg Exp $");
+
+Trap sigtraps[ksh_NSIG + 1];
+static struct sigaction Sigact_ign;
+
+#if HAVE_PERSISTENT_HISTORY
+static int histload(Source *, unsigned char *, size_t);
+static int writehistline(int, int, const char *);
+static void writehistfile(int, const char *);
+#endif
+
+static int hist_execute(char *, Area *);
+static char **hist_get(const char *, bool, bool);
+static char **hist_get_oldest(void);
+
+static bool hstarted; /* set after hist_init() called */
+static Source *hist_source;
+
+#if HAVE_PERSISTENT_HISTORY
+/*XXX imake style */
+#if defined(__linux)
+#define caddr_cast(x) ((void *)(x))
+#else
+#define caddr_cast(x) ((caddr_t)(x))
+#endif
+
+/* several OEs do not have these constants */
+#ifndef MAP_FAILED
+#define MAP_FAILED caddr_cast(-1)
+#endif
+
+/* some OEs need the default mapping type specified */
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+
+/* current history file: name, fd, size */
+static char *hname;
+static int histfd = -1;
+static off_t histfsize;
+#endif
+
+/* HISTSIZE default: size of saved history, persistent or standard */
+#ifdef MKSH_SMALL
+#define MKSH_DEFHISTSIZE 255
+#else
+#define MKSH_DEFHISTSIZE 2047
+#endif
+/* maximum considered size of persistent history file */
+#define MKSH_MAXHISTFSIZE ((off_t)1048576 * 96)
+
+/* hidden option */
+#define HIST_DISCARD 5
+
+int
+c_fc(const char **wp)
+{
+ struct shf *shf;
+ struct temp *tf;
+ bool gflag = false, lflag = false, nflag = false, rflag = false,
+ sflag = false;
+ int optc;
+ const char *p, *first = NULL, *last = NULL;
+ char **hfirst, **hlast, **hp, *editor = NULL;
+
+ if (!Flag(FTALKING_I)) {
+ 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, Tspdollaru, 4);
+ }
+ break;
+
+ /* non-AT&T ksh */
+ case 'g':
+ gflag = true;
+ break;
+
+ case 'l':
+ lflag = true;
+ break;
+
+ case 'n':
+ nflag = true;
+ break;
+
+ case 'r':
+ rflag = true;
+ break;
+
+ /* 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':
+ p = shf_smprintf("-%c%s",
+ optc, builtin_opt.optarg);
+ if (!first)
+ first = p;
+ else if (!last)
+ last = p;
+ else {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+ break;
+
+ case '?':
+ return (1);
+ }
+ wp += builtin_opt.optind;
+
+ /* Substitute and execute command */
+ if (sflag) {
+ char *pat = NULL, *rep = NULL, *line;
+
+ if (editor || lflag || nflag || rflag) {
+ bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
+ return (1);
+ }
+
+ /* Check for pattern replacement argument */
+ if (*wp && **wp && (p = cstrchr(*wp + 1, '='))) {
+ strdupx(pat, *wp, ATEMP);
+ rep = pat + (p - *wp);
+ *rep++ = '\0';
+ wp++;
+ }
+ /* Check for search prefix */
+ if (!first && (first = *wp))
+ wp++;
+ if (last || *wp) {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+
+ hp = first ? hist_get(first, false, false) :
+ hist_get_newest(false);
+ if (!hp)
+ return (1);
+ /* hist_replace */
+ if (!pat)
+ strdupx(line, *hp, ATEMP);
+ else {
+ char *s, *s1;
+ size_t len, pat_len, rep_len;
+ XString xs;
+ char *xp;
+ bool any_subst = false;
+
+ pat_len = strlen(pat);
+ rep_len = strlen(rep);
+ Xinit(xs, xp, 128, ATEMP);
+ for (s = *hp; (s1 = strstr(s, pat)) &&
+ (!any_subst || gflag); s = s1 + pat_len) {
+ any_subst = true;
+ len = s1 - s;
+ XcheckN(xs, xp, len + rep_len);
+ /*; first part */
+ memcpy(xp, s, len);
+ xp += len;
+ /* replacement */
+ memcpy(xp, rep, rep_len);
+ xp += rep_len;
+ }
+ if (!any_subst) {
+ bi_errorf(Tbadsubst);
+ return (1);
+ }
+ len = strlen(s) + 1;
+ XcheckN(xs, xp, len);
+ memcpy(xp, s, len);
+ xp += len;
+ line = Xclose(xs, xp);
+ }
+ return (hist_execute(line, ATEMP));
+ }
+
+ if (editor && (lflag || nflag)) {
+ bi_errorf("can't use -l, -n with -e");
+ return (1);
+ }
+
+ if (!first && (first = *wp))
+ wp++;
+ if (!last && (last = *wp))
+ wp++;
+ if (*wp) {
+ bi_errorf(Ttoo_many_args);
+ return (1);
+ }
+ if (!first) {
+ hfirst = lflag ? hist_get("-16", true, true) :
+ hist_get_newest(false);
+ if (!hfirst)
+ return (1);
+ /* 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.
+ */
+ hfirst = hist_get(first, tobool(lflag || last), lflag);
+ if (!hfirst)
+ return (1);
+ hlast = last ? hist_get(last, true, lflag) :
+ (lflag ? hist_get_newest(false) : hfirst);
+ if (!hlast)
+ return (1);
+ }
+ if (hfirst > hlast) {
+ char **temp;
+
+ temp = hfirst; hfirst = hlast; hlast = temp;
+ /* POSIX */
+ rflag = !rflag;
+ }
+
+ /* List history */
+ if (lflag) {
+ char *s, *t;
+
+ for (hp = rflag ? hlast : hfirst;
+ hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
+ if (!nflag)
+ shf_fprintf(shl_stdout, Tf_lu,
+ (unsigned long)hist_source->line -
+ (unsigned long)(histptr - hp));
+ shf_putc('\t', shl_stdout);
+ /* print multi-line commands correctly */
+ s = *hp;
+ while ((t = strchr(s, '\n'))) {
+ *t = '\0';
+ shf_fprintf(shl_stdout, "%s\n\t", s);
+ *t++ = '\n';
+ s = t;
+ }
+ shf_fprintf(shl_stdout, Tf_sN, s);
+ }
+ shf_flush(shl_stdout);
+ return (0);
+ }
+
+ /* Run editor on selected lines, then run resulting commands */
+
+ tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
+ if (!(shf = tf->shf)) {
+ bi_errorf(Tf_temp, Tcreate, tf->tffn, cstrerror(errno));
+ return (1);
+ }
+ for (hp = rflag ? hlast : hfirst;
+ hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
+ shf_fprintf(shf, Tf_sN, *hp);
+ if (shf_close(shf) == -1) {
+ bi_errorf(Tf_temp, Twrite, tf->tffn, cstrerror(errno));
+ return (1);
+ }
+
+ /* Ignore setstr errors here (arbitrary) */
+ setstr(local("_", false), tf->tffn, KSH_RETURN_ERROR);
+
+ if ((optc = command(editor ? editor : TFCEDIT_dollaru, 0)))
+ return (optc);
+
+ {
+ struct stat statb;
+ XString xs;
+ char *xp;
+ ssize_t n;
+
+ if (!(shf = shf_open(tf->tffn, O_RDONLY, 0, 0))) {
+ bi_errorf(Tf_temp, Topen, tf->tffn, cstrerror(errno));
+ return (1);
+ }
+
+ if (stat(tf->tffn, &statb) < 0)
+ n = 128;
+ else if ((off_t)statb.st_size > MKSH_MAXHISTFSIZE) {
+ bi_errorf(Tf_toolarge, Thistory,
+ Tfile, (unsigned long)statb.st_size);
+ goto errout;
+ } else
+ n = (size_t)statb.st_size + 1;
+ Xinit(xs, xp, n, hist_source->areap);
+ while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+ xp += n;
+ if (Xnleft(xs, xp) <= 0)
+ XcheckN(xs, xp, Xlength(xs, xp));
+ }
+ if (n < 0) {
+ bi_errorf(Tf_temp, Tread, tf->tffn,
+ cstrerror(shf_errno(shf)));
+ errout:
+ shf_close(shf);
+ return (1);
+ }
+ shf_close(shf);
+ *xp = '\0';
+ strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
+ return (hist_execute(Xstring(xs, xp), hist_source->areap));
+ }
+}
+
+/* save cmd in history, execute cmd (cmd gets afree’d) */
+static int
+hist_execute(char *cmd, Area *areap)
+{
+ static int last_line = -1;
+
+ /* Back up over last histsave */
+ if (histptr >= history && last_line != hist_source->line) {
+ hist_source->line--;
+ afree(*histptr, APERM);
+ histptr--;
+ last_line = hist_source->line;
+ }
+
+ histsave(&hist_source->line, cmd, HIST_STORE, true);
+ /* now *histptr == cmd without all trailing newlines */
+ afree(cmd, areap);
+ cmd = *histptr;
+ /* pdksh says POSIX doesn’t say this is done, testsuite needs it */
+ shellf(Tf_sN, cmd);
+
+ /*-
+ * Commands are executed here instead of pushing them onto the
+ * input 'cause POSIX says the redirection and variable assignments
+ * in
+ * X=y fc -e - 42 2> /dev/null
+ * are to effect the repeated commands environment.
+ */
+ return (command(cmd, 0));
+}
+
+/*
+ * get pointer to history given pattern
+ * pattern is a number or string
+ */
+static char **
+hist_get(const char *str, bool approx, bool allow_cur)
+{
+ char **hp = NULL;
+ int n;
+
+ if (getn(str, &n)) {
+ hp = histptr + (n < 0 ? n : (n - hist_source->line));
+ if ((size_t)hp < (size_t)history) {
+ if (approx)
+ hp = hist_get_oldest();
+ else {
+ bi_errorf(Tf_sD_s, str, Tnot_in_history);
+ hp = NULL;
+ }
+ } else if ((size_t)hp > (size_t)histptr) {
+ if (approx)
+ hp = hist_get_newest(allow_cur);
+ else {
+ bi_errorf(Tf_sD_s, str, Tnot_in_history);
+ hp = NULL;
+ }
+ } else if (!allow_cur && hp == histptr) {
+ bi_errorf(Tf_sD_s, str, "invalid range");
+ hp = NULL;
+ }
+ } else {
+ bool anchored = *str == '?' ? (++str, false) : true;
+
+ /* the -1 is to avoid the current fc command */
+ if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
+ bi_errorf(Tf_sD_s, str, Tnot_in_history);
+ else
+ hp = &history[n];
+ }
+ return (hp);
+}
+
+/* Return a pointer to the newest command in the history */
+char **
+hist_get_newest(bool allow_cur)
+{
+ if (histptr < history || (!allow_cur && histptr == history)) {
+ bi_errorf("no history (yet)");
+ return (NULL);
+ }
+ return (allow_cur ? histptr : histptr - 1);
+}
+
+/* Return a pointer to the oldest command in the history */
+static char **
+hist_get_oldest(void)
+{
+ if (histptr <= history) {
+ bi_errorf("no history (yet)");
+ return (NULL);
+ }
+ return (history);
+}
+
+#if !defined(MKSH_NO_CMDLINE_EDITING) && !MKSH_S_NOVI
+/* current position in history[] */
+static char **current;
+
+/*
+ * Return the current position.
+ */
+char **
+histpos(void)
+{
+ return (current);
+}
+
+int
+histnum(int n)
+{
+ int last = histptr - history;
+
+ if (n < 0 || n >= last) {
+ current = histptr;
+ return (last);
+ } else {
+ current = &history[n];
+ return (n);
+ }
+}
+#endif
+
+/*
+ * This will become unnecessary if hist_get is modified to allow
+ * searching from positions other than the end, and in either
+ * direction.
+ */
+int
+findhist(int start, int fwd, const char *str, bool anchored)
+{
+ char **hp;
+ int maxhist = histptr - history;
+ int incr = fwd ? 1 : -1;
+ size_t len = strlen(str);
+
+ if (start < 0 || start >= maxhist)
+ start = maxhist;
+
+ hp = &history[start];
+ for (; hp >= history && hp <= histptr; hp += incr)
+ if ((anchored && strncmp(*hp, str, len) == 0) ||
+ (!anchored && strstr(*hp, str)))
+ return (hp - history);
+
+ return (-1);
+}
+
+/*
+ * set history; this means reallocating the dataspace
+ */
+void
+sethistsize(mksh_ari_t n)
+{
+ if (n > 65535)
+ n = 65535;
+ if (n > 0 && n != histsize) {
+ int cursize = histptr - history;
+
+ /* save most recent history */
+ if (n < cursize) {
+ memmove(history, histptr - n + 1, n * sizeof(char *));
+ cursize = n - 1;
+ }
+
+ history = aresize2(history, n, sizeof(char *), APERM);
+
+ histsize = n;
+ histptr = history + cursize;
+ }
+}
+
+#if HAVE_PERSISTENT_HISTORY
+/*
+ * set history file; this can mean reloading/resetting/starting
+ * history file maintenance
+ */
+void
+sethistfile(const char *name)
+{
+ /* if not started then nothing to do */
+ if (hstarted == false)
+ return;
+
+ /* if the name is the same as the name we have */
+ if (hname && name && !strcmp(hname, name))
+ return;
+
+ /*
+ * it's a new name - possibly
+ */
+ if (histfd != -1) {
+ /* yes the file is open */
+ (void)close(histfd);
+ histfd = -1;
+ histfsize = 0;
+ afree(hname, APERM);
+ hname = NULL;
+ /* let's reset the history */
+ histsave(NULL, NULL, HIST_DISCARD, true);
+ histptr = history - 1;
+ hist_source->line = 0;
+ }
+
+ if (name)
+ hist_init(hist_source);
+}
+#endif
+
+/*
+ * initialise the history vector
+ */
+void
+init_histvec(void)
+{
+ if (history == (char **)NULL) {
+ histsize = MKSH_DEFHISTSIZE;
+ history = alloc2(histsize, sizeof(char *), APERM);
+ histptr = history - 1;
+ }
+}
+
+
+/*
+ * It turns out that there is a lot of ghastly hackery here
+ */
+
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+/* do not save command in history but possibly sync */
+bool
+histsync(void)
+{
+ bool changed = false;
+
+ /* called by histsave(), may not HIST_DISCARD, caller should flush */
+
+ if (histfd != -1) {
+ int lno = hist_source->line;
+
+ hist_source->line++;
+ writehistfile(0, NULL);
+ hist_source->line--;
+
+ if (lno != hist_source->line)
+ changed = true;
+ }
+
+ return (changed);
+}
+#endif
+
+/*
+ * save command in history
+ */
+void
+histsave(int *lnp, const char *cmd, int svmode, bool ignoredups)
+{
+ static char *enqueued = NULL;
+ char **hp, *c;
+ const char *ccp;
+
+ if (svmode == HIST_DISCARD) {
+ afree(enqueued, APERM);
+ enqueued = NULL;
+ return;
+ }
+
+ if (svmode == HIST_APPEND) {
+ if (!enqueued)
+ svmode = HIST_STORE;
+ } else if (enqueued) {
+ c = enqueued;
+ enqueued = NULL;
+ --*lnp;
+ histsave(lnp, c, HIST_STORE, true);
+ afree(c, APERM);
+ }
+
+ if (svmode == HIST_FLUSH)
+ return;
+
+ ccp = strnul(cmd);
+ while (ccp > cmd && ccp[-1] == '\n')
+ --ccp;
+ strndupx(c, cmd, ccp - cmd, APERM);
+
+ if (svmode != HIST_APPEND) {
+ if (ignoredups &&
+ histptr >= history &&
+ !strcmp(c, *histptr)
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+ && !histsync()
+#endif
+ ) {
+ afree(c, APERM);
+ return;
+ }
+ ++*lnp;
+ }
+
+#if HAVE_PERSISTENT_HISTORY
+ if (svmode == HIST_STORE && histfd != -1)
+ writehistfile(*lnp, c);
+#endif
+
+ if (svmode == HIST_QUEUE || svmode == HIST_APPEND) {
+ size_t nenq, ncmd;
+
+ if (!enqueued) {
+ if (*c)
+ enqueued = c;
+ else
+ afree(c, APERM);
+ return;
+ }
+
+ nenq = strlen(enqueued);
+ ncmd = strlen(c);
+ enqueued = aresize(enqueued, nenq + 1 + ncmd + 1, APERM);
+ enqueued[nenq] = '\n';
+ memcpy(enqueued + nenq + 1, c, ncmd + 1);
+ afree(c, APERM);
+ return;
+ }
+
+ hp = histptr;
+
+ if (++hp >= history + histsize) {
+ /* remove oldest command */
+ afree(*history, APERM);
+ for (hp = history; hp < history + histsize - 1; hp++)
+ hp[0] = hp[1];
+ }
+ *hp = c;
+ histptr = hp;
+}
+
+/*
+ * Write history data to a file nominated by HISTFILE;
+ * if HISTFILE is unset then history still happens, but
+ * the data is not written to a file. All copies of ksh
+ * looking at the file will maintain the same history.
+ * This is ksh behaviour.
+ *
+ * This stuff uses mmap()
+ *
+ * This stuff is so totally broken it must eventually be
+ * redesigned, without mmap, better checks, support for
+ * larger files, etc. and handle partially corrupted files
+ */
+
+/*-
+ * Open a history file
+ * Format is:
+ * Bytes 1, 2:
+ * HMAGIC - just to check that we are dealing with the correct object
+ * Then follows a number of stored commands
+ * Each command is
+ * <command byte><command number(4 octets, big endian)><bytes><NUL>
+ */
+#define HMAGIC1 0xAB
+#define HMAGIC2 0xCD
+#define COMMAND 0xFF
+
+#if HAVE_PERSISTENT_HISTORY
+static const unsigned char sprinkle[2] = { HMAGIC1, HMAGIC2 };
+
+static int
+hist_persist_back(int srcfd)
+{
+ off_t tot, mis;
+ ssize_t n, w;
+ char *buf, *cp;
+ int rv = 0;
+#define MKSH_HS_BUFSIZ 4096
+
+ if ((tot = lseek(srcfd, (off_t)0, SEEK_END)) < 0 ||
+ lseek(srcfd, (off_t)0, SEEK_SET) < 0 ||
+ lseek(histfd, (off_t)0, SEEK_SET) < 0)
+ return (1);
+
+ if ((buf = malloc_osfunc(MKSH_HS_BUFSIZ)) == NULL)
+ return (1);
+
+ mis = tot;
+ while (mis > 0) {
+ if ((n = blocking_read(srcfd, (cp = buf),
+ MKSH_HS_BUFSIZ)) == -1) {
+ if (errno == EINTR) {
+ intrcheck();
+ continue;
+ }
+ goto copy_error;
+ }
+ mis -= n;
+ while (n) {
+ if (intrsig)
+ goto has_intrsig;
+ if ((w = write(histfd, cp, n)) != -1) {
+ n -= w;
+ cp += w;
+ continue;
+ }
+ if (errno == EINTR) {
+ has_intrsig:
+ intrcheck();
+ continue;
+ }
+ goto copy_error;
+ }
+ }
+ if (ftruncate(histfd, tot)) {
+ copy_error:
+ rv = 1;
+ }
+ free_osfunc(buf);
+ return (rv);
+}
+
+static void
+hist_persist_init(void)
+{
+ unsigned char *base;
+ int lines, fd;
+ enum { hist_init_first, hist_init_retry, hist_use_it } hs;
+
+ if (((hname = str_val(global("HISTFILE"))) == NULL) || !*hname) {
+ hname = NULL;
+ return;
+ }
+ strdupx(hname, hname, APERM);
+ hs = hist_init_first;
+
+ retry:
+ /* we have a file and are interactive */
+ if ((fd = binopen3(hname, O_RDWR | O_CREAT | O_APPEND, 0600)) < 0)
+ return;
+ if ((histfd = savefd(fd)) < 0)
+ return;
+ if (histfd != fd)
+ close(fd);
+
+ mksh_lockfd(histfd);
+
+ histfsize = lseek(histfd, (off_t)0, SEEK_END);
+ if (histfsize > MKSH_MAXHISTFSIZE) {
+ /* we ignore too large files but still append to them */
+ goto hist_init_tail;
+ } else if (histfsize > 2) {
+ /* we have some data, check its validity */
+ base = (void *)mmap(NULL, (size_t)histfsize, PROT_READ,
+ MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
+ if (base == (unsigned char *)MAP_FAILED)
+ goto hist_init_fail;
+ if (base[0] != HMAGIC1 || base[1] != HMAGIC2) {
+ munmap(caddr_cast(base), (size_t)histfsize);
+ goto hist_init_fail;
+ }
+ /* load _all_ data */
+ lines = histload(hist_source, base + 2, (size_t)histfsize - 2);
+ munmap(caddr_cast(base), (size_t)histfsize);
+ /* check if the file needs to be truncated */
+ if (lines > histsize && histptr >= history) {
+ /* you're fucked up with the current code, trust me */
+ char *nhname, **hp;
+ struct stat sb;
+
+ /* create temporary file */
+ nhname = shf_smprintf("%s.%d", hname, (int)procpid);
+ if ((fd = binopen3(nhname, O_RDWR | O_CREAT | O_TRUNC |
+ O_EXCL, 0600)) < 0) {
+ /* just don't truncate then, meh. */
+ hs = hist_use_it;
+ goto hist_trunc_dont;
+ }
+ if (fstat(histfd, &sb) >= 0 &&
+ chown(nhname, sb.st_uid, sb.st_gid)) {
+ /* abort the truncation then, meh. */
+ goto hist_trunc_abort;
+ }
+ /* we definitively want some magic in that file */
+ if (write(fd, sprinkle, 2) != 2)
+ goto hist_trunc_abort;
+ /* and of course the entries */
+ hp = history;
+ while (hp < histptr) {
+ if (!writehistline(fd,
+ hist_source->line - (histptr - hp), *hp))
+ goto hist_trunc_abort;
+ ++hp;
+ }
+ /* now transfer back */
+ if (!hist_persist_back(fd)) {
+ /* success! */
+ hs = hist_use_it;
+ }
+ hist_trunc_abort:
+ /* remove temporary file */
+ close(fd);
+ fd = -1;
+ unlink(nhname);
+ /* use whatever is in the file now */
+ hist_trunc_dont:
+ afree(nhname, ATEMP);
+ if (hs == hist_use_it)
+ goto hist_trunc_done;
+ goto hist_init_fail;
+ }
+ } else if (histfsize != 0) {
+ /* negative or too small... */
+ hist_init_fail:
+ /* ... or mmap failed or illegal */
+ hist_finish();
+ /* nuke the bogus file then retry, at most once */
+ if (!unlink(hname) && hs != hist_init_retry) {
+ hs = hist_init_retry;
+ goto retry;
+ }
+ if (hs != hist_init_retry)
+ bi_errorf(Tf_cant_ss_s,
+ "unlink HISTFILE", hname, cstrerror(errno));
+ histfsize = 0;
+ return;
+ } else {
+ /* size 0, add magic to the history file */
+ if (write(histfd, sprinkle, 2) != 2) {
+ hist_finish();
+ return;
+ }
+ }
+ hist_trunc_done:
+ if ((histfsize = lseek(histfd, (off_t)0, SEEK_END)) < 0)
+ goto hist_init_fail;
+ hist_init_tail:
+ mksh_unlkfd(histfd);
+}
+#endif
+
+void
+hist_init(Source *s)
+{
+ histsave(NULL, NULL, HIST_DISCARD, true);
+
+ if (Flag(FTALKING) == 0)
+ return;
+
+ hstarted = true;
+ hist_source = s;
+
+#if HAVE_PERSISTENT_HISTORY
+ hist_persist_init();
+#endif
+}
+
+#if HAVE_PERSISTENT_HISTORY
+/*
+ * load the history structure from the stored data
+ */
+static int
+histload(Source *s, unsigned char *base, size_t bytes)
+{
+ int lno = 0, lines = 0;
+ unsigned char *cp;
+
+ histload_loop:
+ /* !bytes check as some systems (older FreeBSDs) have buggy memchr */
+ if (!bytes || (cp = memchr(base, COMMAND, bytes)) == NULL)
+ return (lines);
+ /* advance base pointer past COMMAND byte */
+ bytes -= ++cp - base;
+ base = cp;
+ /* if there is no full string left, don't bother with the rest */
+ if (bytes < 5 || (cp = memchr(base + 4, '\0', bytes - 4)) == NULL)
+ return (lines);
+ /* load the stored line number */
+ lno = ((base[0] & 0xFF) << 24) | ((base[1] & 0xFF) << 16) |
+ ((base[2] & 0xFF) << 8) | (base[3] & 0xFF);
+ /* store away the found line (@base[4]) */
+ ++lines;
+ if (histptr >= history && lno - 1 != s->line) {
+ /* a replacement? */
+ char **hp;
+
+ if (lno >= s->line - (histptr - history) && lno <= s->line) {
+ hp = &histptr[lno - s->line];
+ afree(*hp, APERM);
+ strdupx(*hp, (char *)(base + 4), APERM);
+ }
+ } else {
+ s->line = lno--;
+ histsave(&lno, (char *)(base + 4), HIST_NOTE, false);
+ }
+ /* advance base pointer past NUL */
+ bytes -= ++cp - base;
+ base = cp;
+ /* repeat until no more */
+ goto histload_loop;
+}
+
+/*
+ * write a command to the end of the history file
+ *
+ * This *MAY* seem easy but it's also necessary to check
+ * that the history file has not changed in size.
+ * If it has - then some other shell has written to it and
+ * we should (re)read those commands to update our history
+ */
+static void
+writehistfile(int lno, const char *cmd)
+{
+ off_t sizenow;
+ size_t bytes;
+ unsigned char *base, *news;
+
+ mksh_lockfd(histfd);
+ if ((sizenow = lseek(histfd, (off_t)0, SEEK_END)) < 0)
+ goto bad;
+ else if (sizenow < histfsize) {
+ /* the file has shrunk; trust it just appending the new data */
+ /* well, for now, anyway… since mksh strdups all into memory */
+ /* we can use a nicer approach some time later… */
+ ;
+ } else if (
+ /* ignore changes when the file is too large */
+ sizenow <= MKSH_MAXHISTFSIZE
+ &&
+ /* the size has changed, we need to do read updates */
+ sizenow > histfsize
+ ) {
+ /* both sizenow and histfsize are <= MKSH_MAXHISTFSIZE */
+ bytes = (size_t)(sizenow - histfsize);
+ 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 + (size_t)histfsize;
+ if (*news == COMMAND) {
+ hist_source->line--;
+ histload(hist_source, news, bytes);
+ hist_source->line++;
+ lno = hist_source->line;
+ } else
+ bytes = 0;
+ munmap(caddr_cast(base), (size_t)sizenow);
+ if (!bytes)
+ goto bad;
+ }
+ if (cmd && !writehistline(histfd, lno, cmd)) {
+ bad:
+ hist_finish();
+ return;
+ }
+ if ((histfsize = lseek(histfd, (off_t)0, SEEK_END)) < 0)
+ goto bad;
+ mksh_unlkfd(histfd);
+}
+
+static int
+writehistline(int fd, int lno, const char *cmd)
+{
+ ssize_t n;
+ unsigned char hdr[5];
+
+ hdr[0] = COMMAND;
+ hdr[1] = (lno >> 24) & 0xFF;
+ hdr[2] = (lno >> 16) & 0xFF;
+ hdr[3] = (lno >> 8) & 0xFF;
+ hdr[4] = lno & 0xFF;
+ n = strlen(cmd) + 1;
+ return (write(fd, hdr, 5) == 5 && write(fd, cmd, n) == n);
+}
+
+void
+hist_finish(void)
+{
+ if (histfd >= 0) {
+ mksh_unlkfd(histfd);
+ (void)close(histfd);
+ }
+ histfd = -1;
+}
+#endif
+
+
+#if !HAVE_SYS_SIGNAME
+static const struct mksh_sigpair {
+ const char * const name;
+ int nr;
+} mksh_sigpairs[] = {
+#include "signames.inc"
+ { NULL, 0 }
+};
+#endif
+
+#if HAVE_SYS_SIGLIST
+#if !HAVE_SYS_SIGLIST_DECL
+extern const char * const sys_siglist[];
+#endif
+#endif
+
+void
+inittraps(void)
+{
+ int i;
+ const char *cs;
+#if !HAVE_SYS_SIGNAME
+ const struct mksh_sigpair *pair;
+#endif
+
+ trap_exstat = -1;
+
+ /* populate sigtraps based on sys_signame and sys_siglist */
+ for (i = 1; i < ksh_NSIG; i++) {
+ sigtraps[i].signal = i;
+#if HAVE_SYS_SIGNAME
+ cs = sys_signame[i];
+#else
+ pair = mksh_sigpairs;
+ while ((pair->nr != i) && (pair->name != NULL))
+ ++pair;
+ cs = pair->name;
+#endif
+ if ((cs == NULL) ||
+ (cs[0] == '\0'))
+ sigtraps[i].name = null;
+ else {
+ char *s;
+
+ /* this is not optimal, what about SIGSIG1? */
+ if (ksh_eq(cs[0], 'S', 's') &&
+ ksh_eq(cs[1], 'I', 'i') &&
+ ksh_eq(cs[2], 'G', 'g') &&
+ cs[3] != '\0') {
+ /* skip leading "SIG" */
+ cs += 3;
+ }
+ strdupx(s, cs, APERM);
+ sigtraps[i].name = s;
+ while ((*s = ksh_toupper(*s)))
+ ++s;
+ /* check for reserved names */
+ if (!strcmp(sigtraps[i].name, "EXIT") ||
+ !strcmp(sigtraps[i].name, "ERR")) {
+#ifndef MKSH_SMALL
+ internal_warningf(Tinvname, sigtraps[i].name,
+ "signal");
+#endif
+ sigtraps[i].name = null;
+ }
+ }
+ if (sigtraps[i].name == null)
+ sigtraps[i].name = shf_smprintf(Tf_d, i);
+#if HAVE_SYS_SIGLIST
+ sigtraps[i].mess = sys_siglist[i];
+#elif HAVE_STRSIGNAL
+ sigtraps[i].mess = strsignal(i);
+#else
+ sigtraps[i].mess = NULL;
+#endif
+ if ((sigtraps[i].mess == NULL) ||
+ (sigtraps[i].mess[0] == '\0'))
+ sigtraps[i].mess = shf_smprintf(Tf_sd,
+ "Signal", i);
+ }
+ sigtraps[ksh_SIGEXIT].signal = ksh_SIGEXIT;
+ sigtraps[ksh_SIGEXIT].name = "EXIT";
+ sigtraps[ksh_SIGEXIT].mess = "Exit trap";
+ sigtraps[ksh_SIGERR].signal = ksh_SIGERR;
+ sigtraps[ksh_SIGERR].name = "ERR";
+ sigtraps[ksh_SIGERR].mess = "Error handler";
+
+ (void)sigemptyset(&Sigact_ign.sa_mask);
+ Sigact_ign.sa_flags = 0; /* interruptible */
+ Sigact_ign.sa_handler = SIG_IGN;
+
+ sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+ sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+ /* SIGTERM is not fatal for interactive */
+ sigtraps[SIGTERM].flags |= TF_DFL_INTR;
+ sigtraps[SIGHUP].flags |= TF_FATAL;
+ sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
+
+ /* these are always caught so we can clean up any temporary files. */
+ setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
+}
+
+static void alarm_catcher(int sig);
+
+void
+alarm_init(void)
+{
+ sigtraps[SIGALRM].flags |= TF_SHELL_USES;
+ setsig(&sigtraps[SIGALRM], alarm_catcher,
+ SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+}
+
+/* ARGSUSED */
+static void
+alarm_catcher(int sig MKSH_A_UNUSED)
+{
+ /* this runs inside interrupt context, with errno saved */
+
+ if (ksh_tmout_state == TMOUT_READING) {
+ int left = alarm(0);
+
+ if (left == 0) {
+ ksh_tmout_state = TMOUT_LEAVING;
+ intrsig = 1;
+ } else
+ alarm(left);
+ }
+}
+
+Trap *
+gettrap(const char *cs, bool igncase, bool allsigs)
+{
+ int i;
+ Trap *p;
+ char *as;
+
+ /* signal number (1..ksh_NSIG) or 0? */
+
+ if (ctype(*cs, C_DIGIT))
+ return ((getn(cs, &i) && 0 <= i && i < ksh_NSIG) ?
+ (&sigtraps[i]) : NULL);
+
+ /* do a lookup by name then */
+
+ /* this breaks SIGSIG1, but we do that above anyway */
+ if (ksh_eq(cs[0], 'S', 's') &&
+ ksh_eq(cs[1], 'I', 'i') &&
+ ksh_eq(cs[2], 'G', 'g') &&
+ cs[3] != '\0') {
+ /* skip leading "SIG" */
+ cs += 3;
+ }
+ if (igncase) {
+ char *s;
+
+ strdupx(as, cs, ATEMP);
+ cs = s = as;
+ while ((*s = ksh_toupper(*s)))
+ ++s;
+ } else
+ as = NULL;
+
+ /* this is idiotic, we really want a hashtable here */
+
+ p = sigtraps;
+ i = ksh_NSIG + 1;
+ do {
+ if (!strcmp(p->name, cs))
+ goto found;
+ ++p;
+ } while (--i);
+ goto notfound;
+
+ found:
+ if (!allsigs) {
+ if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR) {
+ notfound:
+ p = NULL;
+ }
+ }
+ afree(as, ATEMP);
+ return (p);
+}
+
+/*
+ * trap signal handler
+ */
+void
+trapsig(int i)
+{
+ Trap *p = &sigtraps[i];
+ int eno = errno;
+
+ trap = p->set = 1;
+ if (p->flags & TF_DFL_INTR)
+ intrsig = 1;
+ if ((p->flags & TF_FATAL) && !p->trap) {
+ fatal_trap = 1;
+ intrsig = 1;
+ }
+ if (p->shtrap)
+ (*p->shtrap)(i);
+ errno = eno;
+}
+
+/*
+ * called when we want to allow the user to ^C out of something - won't
+ * work if user has trapped SIGINT.
+ */
+void
+intrcheck(void)
+{
+ if (intrsig)
+ runtraps(TF_DFL_INTR|TF_FATAL);
+}
+
+/*
+ * called after EINTR to check if a signal with normally causes process
+ * termination has been received.
+ */
+int
+fatal_trap_check(void)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG + 1;
+
+ /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
+ do {
+ if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
+ /* return value is used as an exit code */
+ return (ksh_sigmask(p->signal));
+ ++p;
+ } while (--i);
+ return (0);
+}
+
+/*
+ * Returns the signal number of any pending traps: ie, a signal which has
+ * occurred for which a trap has been set or for which the TF_DFL_INTR flag
+ * is set.
+ */
+int
+trap_pending(void)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG + 1;
+
+ do {
+ if (p->set && ((p->trap && p->trap[0]) ||
+ ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
+ return (p->signal);
+ ++p;
+ } while (--i);
+ return (0);
+}
+
+/*
+ * run any pending traps. If intr is set, only run traps that
+ * can interrupt commands.
+ */
+void
+runtraps(int flag)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG + 1;
+
+ if (ksh_tmout_state == TMOUT_LEAVING) {
+ ksh_tmout_state = TMOUT_EXECUTING;
+ warningf(false, "timed out waiting for input");
+ unwind(LEXIT);
+ } else
+ /*
+ * XXX: this means the alarm will have no effect if a trap
+ * is caught after the alarm() was started...not good.
+ */
+ ksh_tmout_state = TMOUT_EXECUTING;
+ if (!flag)
+ trap = 0;
+ if (flag & TF_DFL_INTR)
+ intrsig = 0;
+ if (flag & TF_FATAL)
+ fatal_trap = 0;
+ ++trap_nested;
+ do {
+ if (p->set && (!flag ||
+ ((p->flags & flag) && p->trap == NULL)))
+ runtrap(p, false);
+ ++p;
+ } while (--i);
+ if (!--trap_nested)
+ runtrap(NULL, true);
+}
+
+void
+runtrap(Trap *p, bool is_last)
+{
+ 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 (p->flags & (TF_FATAL | TF_DFL_INTR)) {
+ exstat = (int)(128U + (unsigned)i);
+ if ((unsigned)exstat > 255U)
+ exstat = 255;
+ }
+ /* e.g. SIGHUP */
+ if (p->flags & TF_FATAL)
+ unwind(LLEAVE);
+ /* e.g. SIGINT, SIGQUIT, SIGTERM, etc. */
+ if (p->flags & TF_DFL_INTR)
+ unwind(LINTR);
+ goto donetrap;
+ }
+ 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;
+ }
+ if (trap_exstat == -1)
+ trap_exstat = exstat & 0xFF;
+ /*
+ * 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);
+ if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
+ if (p->flags & TF_CHANGED)
+ /* don't clear TF_CHANGED */
+ afree(trapstr, APERM);
+ else
+ 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) */
+void
+cleartraps(void)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG + 1;
+
+ trap = 0;
+ intrsig = 0;
+ fatal_trap = 0;
+
+ do {
+ p->set = 0;
+ if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
+ settrap(p, NULL);
+ ++p;
+ } while (--i);
+}
+
+/* restore signals just before an exec(2) */
+void
+restoresigs(void)
+{
+ Trap *p = sigtraps;
+ int i = ksh_NSIG + 1;
+
+ do {
+ if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
+ setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
+ SS_RESTORE_CURR|SS_FORCE);
+ ++p;
+ } while (--i);
+}
+
+void
+settrap(Trap *p, const char *s)
+{
+ sig_t f;
+
+ afree(p->trap, APERM);
+ /* handles s == NULL */
+ strdupx(p->trap, s, APERM);
+ p->flags |= TF_CHANGED;
+ f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
+
+ p->flags |= TF_USER_SET;
+ if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
+ f = trapsig;
+ else if (p->flags & TF_SHELL_USES) {
+ if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
+ /* do what user wants at exec time */
+ p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+ if (f == SIG_IGN)
+ p->flags |= TF_EXEC_IGN;
+ else
+ p->flags |= TF_EXEC_DFL;
+ }
+
+ /*
+ * assumes handler already set to what shell wants it
+ * (normally trapsig, but could be j_sigchld() or SIG_IGN)
+ */
+ return;
+ }
+
+ /* todo: should we let user know signal is ignored? how? */
+ setsig(p, f, SS_RESTORE_CURR|SS_USER);
+}
+
+/*
+ * called by c_print() when writing to a co-process to ensure
+ * SIGPIPE won't kill shell (unless user catches it and exits)
+ */
+bool
+block_pipe(void)
+{
+ bool restore_dfl = false;
+ Trap *p = &sigtraps[SIGPIPE];
+
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+ setsig(p, SIG_IGN, SS_RESTORE_CURR);
+ if (p->flags & TF_ORIG_DFL)
+ restore_dfl = true;
+ } else if (p->cursig == SIG_DFL) {
+ setsig(p, SIG_IGN, SS_RESTORE_CURR);
+ /* restore to SIG_DFL */
+ restore_dfl = true;
+ }
+ return (restore_dfl);
+}
+
+/* called by c_print() to undo whatever block_pipe() did */
+void
+restore_pipe(void)
+{
+ setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
+}
+
+/*
+ * Set action for a signal. Action may not be set if original
+ * action was SIG_IGN, depending on the value of flags and FTALKING.
+ */
+int
+setsig(Trap *p, sig_t f, int flags)
+{
+ struct sigaction sigact;
+
+ if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR)
+ return (1);
+
+ memset(&sigact, 0, sizeof(sigact));
+
+ /*
+ * First time setting this signal? If so, get and note the current
+ * setting.
+ */
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+ sigaction(p->signal, &Sigact_ign, &sigact);
+ p->flags |= sigact.sa_handler == SIG_IGN ?
+ TF_ORIG_IGN : TF_ORIG_DFL;
+ p->cursig = SIG_IGN;
+ }
+
+ /*-
+ * Generally, an ignored signal stays ignored, except if
+ * - the user of an interactive shell wants to change it
+ * - the shell wants for force a change
+ */
+ if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
+ (!(flags & SS_USER) || !Flag(FTALKING)))
+ return (0);
+
+ setexecsig(p, flags & SS_RESTORE_MASK);
+
+ /*
+ * This is here 'cause there should be a way of clearing
+ * shtraps, but don't know if this is a sane way of doing
+ * it. At the moment, all users of shtrap are lifetime
+ * users (SIGALRM, SIGCHLD, SIGWINCH).
+ */
+ if (!(flags & SS_USER))
+ p->shtrap = (sig_t)NULL;
+ if (flags & SS_SHTRAP) {
+ p->shtrap = f;
+ f = trapsig;
+ }
+
+ if (p->cursig != f) {
+ p->cursig = f;
+ (void)sigemptyset(&sigact.sa_mask);
+ /* interruptible */
+ sigact.sa_flags = 0;
+ sigact.sa_handler = f;
+ sigaction(p->signal, &sigact, NULL);
+ }
+
+ return (1);
+}
+
+/* control what signal is set to before an exec() */
+void
+setexecsig(Trap *p, int restore)
+{
+ /* XXX debugging */
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
+ internal_errorf("setexecsig: unset signal %d(%s)",
+ p->signal, p->name);
+
+ /* 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 */
+ break;
+ case SS_RESTORE_ORIG:
+ p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
+ break;
+ case SS_RESTORE_DFL:
+ p->flags |= TF_EXEC_DFL;
+ break;
+ case SS_RESTORE_IGN:
+ p->flags |= TF_EXEC_IGN;
+ break;
+ }
+}
+
+#if HAVE_PERSISTENT_HISTORY || defined(DF)
+/*
+ * File descriptor locking and unlocking functions.
+ * Could use some error handling, but hey, this is only
+ * advisory locking anyway, will often not work over NFS,
+ * and you are SOL if this fails...
+ */
+
+void
+mksh_lockfd(int fd)
+{
+#if defined(__OpenBSD__)
+ /* flock is not interrupted by signals */
+ (void)flock(fd, LOCK_EX);
+#elif HAVE_FLOCK
+ int rv;
+
+ /* e.g. on Linux */
+ do {
+ rv = flock(fd, LOCK_EX);
+ } while (rv == 1 && errno == EINTR);
+#elif HAVE_LOCK_FCNTL
+ int rv;
+ struct flock lks;
+
+ memset(&lks, 0, sizeof(lks));
+ lks.l_type = F_WRLCK;
+ do {
+ rv = fcntl(fd, F_SETLKW, &lks);
+ } while (rv == 1 && errno == EINTR);
+#endif
+}
+
+/* designed to not define mksh_unlkfd if none triggered */
+#if HAVE_FLOCK
+void
+mksh_unlkfd(int fd)
+{
+ (void)flock(fd, LOCK_UN);
+}
+#elif HAVE_LOCK_FCNTL
+void
+mksh_unlkfd(int fd)
+{
+ struct flock lks;
+
+ memset(&lks, 0, sizeof(lks));
+ lks.l_type = F_UNLCK;
+ (void)fcntl(fd, F_SETLKW, &lks);
+}
+#endif
+#endif
diff --git a/shells/mksh/files/jehanne.c b/shells/mksh/files/jehanne.c
new file mode 100644
index 00000000000..ba0eb0f40b4
--- /dev/null
+++ b/shells/mksh/files/jehanne.c
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2017
+ * Giacomo Tesio <giacomo@tesio.it>
+ *
+ * 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.
+ *-
+ * Initialisation code for the Jehanne operating system (a Plan 9 de-
+ * rivative, using GCC)
+ */
+
+static const char __rcsid[] __attribute__((__used__)) =
+ "$MirOS: src/bin/mksh/jehanne.c,v 1.1 2017/12/22 16:30:00 tg Exp $";
+
+#include <u.h>
+#include <lib9.h>
+#include <posix.h>
+
+void
+__application_newlib_init(int argc, char *argv[])
+{
+ rfork(RFFDG | RFREND | RFNOTEG);
+ libposix_emulate_SIGCHLD();
+}
diff --git a/shells/mksh/files/jobs.c b/shells/mksh/files/jobs.c
new file mode 100644
index 00000000000..866326427cc
--- /dev/null
+++ b/shells/mksh/files/jobs.c
@@ -0,0 +1,1962 @@
+/* $OpenBSD: jobs.c,v 1.43 2015/09/10 22:48:58 nicm Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
+ * 2012, 2013, 2014, 2015, 2016, 2018, 2019
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.128 2019/12/11 19:46:20 tg Exp $");
+
+#if HAVE_KILLPG
+#define mksh_killpg killpg
+#else
+/* cross fingers and hope kill is killpg-endowed */
+#define mksh_killpg(p,s) kill(-(p), (s))
+#endif
+
+/* Order important! */
+#define PRUNNING 0
+#define PEXITED 1
+#define PSIGNALLED 2
+#define PSTOPPED 3
+
+typedef struct proc Proc;
+/* to take alignment into consideration */
+struct proc_dummy {
+ Proc *next;
+ pid_t pid;
+ int state;
+ int status;
+ char command[128];
+};
+/* real structure */
+struct proc {
+ /* next process in pipeline (if any) */
+ Proc *next;
+ /* process id of this Unix process in the job */
+ pid_t pid;
+ /* one of the four P… above */
+ int state;
+ /* wait status */
+ int status;
+ /* process command string from vistree */
+ char command[256 - (ALLOC_OVERHEAD +
+ offsetof(struct proc_dummy, command[0]))];
+};
+
+/* Notify/print flag - j_print() argument */
+#define JP_SHORT 1 /* print signals processes were killed by */
+#define JP_MEDIUM 2 /* print [job-num] -/+ command */
+#define JP_LONG 3 /* print [job-num] -/+ pid command */
+#define JP_PGRP 4 /* print pgrp */
+
+/* put_job() flags */
+#define PJ_ON_FRONT 0 /* at very front */
+#define PJ_PAST_STOPPED 1 /* just past any stopped jobs */
+
+/* Job.flags values */
+#define JF_STARTED 0x001 /* set when all processes in job are started */
+#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */
+#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->ttystat is valid */
+#define JF_CHANGED 0x040 /* process has changed state */
+#define JF_KNOWN 0x080 /* $! referenced */
+#define JF_ZOMBIE 0x100 /* known, unwaited process */
+#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_noityf()) */
+#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */
+#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */
+
+typedef struct job Job;
+struct job {
+ ALLOC_ITEM alloc_INT; /* internal, do not touch */
+ Job *next; /* next job in list */
+ Proc *proc_list; /* process list */
+ Proc *last_proc; /* last process in list */
+ struct timeval systime; /* system time used by job */
+ struct timeval usrtime; /* user time used by job */
+ pid_t pgrp; /* process group of job */
+ pid_t ppid; /* pid of process that forked job */
+ int job; /* job number: %n */
+ int flags; /* see JF_* */
+ volatile int state; /* job state */
+ int status; /* exit status of last process */
+ int age; /* number of jobs started */
+ Coproc_id coproc_id; /* 0 or id of coprocess output pipe */
+#ifndef MKSH_UNEMPLOYED
+ mksh_ttyst ttystat; /* saved tty state for stopped jobs */
+ pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */
+#endif
+};
+
+/* Flags for j_waitj() */
+#define JW_NONE 0x00
+#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_NOSUCH 0 /* no such job */
+#define JL_AMBIG 1 /* %foo or %?foo is ambiguous */
+#define JL_INVALID 2 /* non-pid, non-% job id */
+
+static const char * const lookup_msgs[] = {
+ "no such job",
+ "ambiguous",
+ "argument must be %job or process id"
+};
+
+static Job *job_list; /* job list */
+static Job *last_job;
+static Job *async_job;
+static pid_t async_pid;
+
+static int nzombie; /* # of zombies owned by this process */
+static int njobs; /* # of jobs started */
+
+#ifndef CHILD_MAX
+#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;
+static bool ttypgrp_ok; /* set if can use tty pgrps */
+static pid_t restore_ttypgrp = -1;
+static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU };
+#endif
+
+static void j_set_async(Job *);
+static void j_startjob(Job *);
+static int j_waitj(Job *, int, const char *);
+static void j_sigchld(int);
+static void j_print(Job *, int, struct shf *);
+static Job *j_lookup(const char *, int *);
+static Job *new_job(void);
+static Proc *new_proc(void);
+static void check_job(Job *);
+static void put_job(Job *, int);
+static void remove_job(Job *, const char *);
+static int kill_job(Job *, int);
+
+static void tty_init_talking(void);
+static void tty_init_state(void);
+
+/* initialise job control */
+void
+j_init(void)
+{
+#ifndef MKSH_NOPROSPECTOFWORK
+ (void)sigemptyset(&sm_default);
+ sigprocmask(SIG_SETMASK, &sm_default, NULL);
+
+ (void)sigemptyset(&sm_sigchld);
+ (void)sigaddset(&sm_sigchld, SIGCHLD);
+
+ 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 (Flag(FMONITOR) == 127)
+ Flag(FMONITOR) = Flag(FTALKING);
+
+ /*
+ * shl_j is used to do asynchronous notification (used in
+ * an interrupt handler, so need a distinct shf)
+ */
+ shl_j = shf_fdopen(2, SHF_WR, NULL);
+
+ if (Flag(FMONITOR) || Flag(FTALKING)) {
+ int i;
+
+ /*
+ * the TF_SHELL_USES test is a kludge that lets us know if
+ * if the signals have been changed by the shell.
+ */
+ for (i = NELEM(tt_sigs); --i >= 0; ) {
+ sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES;
+ /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ }
+ }
+
+ /* j_change() calls tty_init_talking() and tty_init_state() */
+ if (Flag(FMONITOR))
+ j_change();
+ else
+#endif
+ if (Flag(FTALKING)) {
+ tty_init_talking();
+ tty_init_state();
+ }
+}
+
+static int
+proc_errorlevel(Proc *p)
+{
+ switch (p->state) {
+ case PEXITED:
+ return ((WEXITSTATUS(p->status)) & 255);
+ case PSIGNALLED:
+ return (ksh_sigmask(WTERMSIG(p->status)));
+ default:
+ return (0);
+ }
+}
+
+#if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
+/* suspend the shell */
+void
+j_suspend(void)
+{
+ struct sigaction sa, osa;
+
+ /* Restore tty and pgrp. */
+ if (ttypgrp_ok) {
+ if (tty_hasstate)
+ mksh_tcset(tty_fd, &tty_state);
+ if (restore_ttypgrp >= 0) {
+ if (tcsetpgrp(tty_fd, restore_ttypgrp) < 0) {
+ warningf(false, Tf_ssfaileds,
+ Tj_suspend, "tcsetpgrp", cstrerror(errno));
+ } else if (setpgid(0, restore_ttypgrp) < 0) {
+ warningf(false, Tf_ssfaileds,
+ Tj_suspend, "setpgid", cstrerror(errno));
+ }
+ }
+ }
+
+ /* Suspend the shell. */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGTSTP, &sa, &osa);
+ kill(0, SIGTSTP);
+
+ /* Back from suspend, reset signals, pgrp and tty. */
+ sigaction(SIGTSTP, &osa, NULL);
+ if (ttypgrp_ok) {
+ if (restore_ttypgrp >= 0) {
+ if (setpgid(0, kshpid) < 0) {
+ warningf(false, Tf_ssfaileds,
+ Tj_suspend, "setpgid", cstrerror(errno));
+ ttypgrp_ok = false;
+ } else if (tcsetpgrp(tty_fd, kshpid) < 0) {
+ warningf(false, Tf_ssfaileds,
+ Tj_suspend, "tcsetpgrp", cstrerror(errno));
+ ttypgrp_ok = false;
+ }
+ }
+ tty_init_state();
+ }
+}
+#endif
+
+/* job cleanup before shell exit */
+void
+j_exit(void)
+{
+ /* kill stopped, and possibly running, jobs */
+ Job *j;
+ bool killed = false;
+
+ for (j = job_list; j != NULL; j = j->next) {
+ if (j->ppid == procpid &&
+ (j->state == PSTOPPED ||
+ (j->state == PRUNNING &&
+ ((j->flags & JF_FG) ||
+ (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) {
+ killed = true;
+ if (j->pgrp == 0)
+ kill_job(j, SIGHUP);
+ else
+ mksh_killpg(j->pgrp, SIGHUP);
+#ifndef MKSH_UNEMPLOYED
+ if (j->state == PSTOPPED) {
+ if (j->pgrp == 0)
+ kill_job(j, SIGCONT);
+ else
+ mksh_killpg(j->pgrp, SIGCONT);
+ }
+#endif
+ }
+ }
+ if (killed)
+ sleep(1);
+ j_notify();
+
+#ifndef MKSH_UNEMPLOYED
+ if (kshpid == procpid && restore_ttypgrp >= 0) {
+ /*
+ * Need to restore the tty pgrp to what it was when the
+ * shell started up, so that the process that started us
+ * will be able to access the tty when we are done.
+ * Also need to restore our process group in case we are
+ * about to do an exec so that both our parent and the
+ * process we are to become will be able to access the tty.
+ */
+ tcsetpgrp(tty_fd, restore_ttypgrp);
+ setpgid(0, restore_ttypgrp);
+ }
+ if (Flag(FMONITOR)) {
+ Flag(FMONITOR) = 0;
+ j_change();
+ }
+#endif
+}
+
+#ifndef MKSH_UNEMPLOYED
+/* turn job control on or off according to Flag(FMONITOR) */
+void
+j_change(void)
+{
+ int i;
+
+ if (Flag(FMONITOR)) {
+ bool use_tty = Flag(FTALKING);
+
+ /* don't call mksh_tcget until we own the tty process group */
+ if (use_tty)
+ tty_init_talking();
+
+ /* no controlling tty, no SIGT* */
+ if ((ttypgrp_ok = (use_tty && tty_fd >= 0 && tty_devtty))) {
+ setsig(&sigtraps[SIGTTIN], SIG_DFL,
+ SS_RESTORE_ORIG|SS_FORCE);
+ /* wait to be given tty (POSIX.1, B.2, job control) */
+ while (/* CONSTCOND */ 1) {
+ pid_t ttypgrp;
+
+ if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
+ warningf(false, Tf_ssfaileds,
+ "j_init", "tcgetpgrp",
+ cstrerror(errno));
+ ttypgrp_ok = false;
+ break;
+ }
+ if (ttypgrp == kshpgrp)
+ break;
+ kill(0, SIGTTIN);
+ }
+ }
+ for (i = NELEM(tt_sigs); --i >= 0; )
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_DFL|SS_FORCE);
+ if (ttypgrp_ok && kshpgrp != kshpid) {
+ if (setpgid(0, kshpid) < 0) {
+ warningf(false, Tf_ssfaileds,
+ "j_init", "setpgid", cstrerror(errno));
+ ttypgrp_ok = false;
+ } else {
+ if (tcsetpgrp(tty_fd, kshpid) < 0) {
+ warningf(false, Tf_ssfaileds,
+ "j_init", "tcsetpgrp",
+ cstrerror(errno));
+ ttypgrp_ok = false;
+ } else
+ restore_ttypgrp = kshpgrp;
+ kshpgrp = kshpid;
+ }
+ }
+#ifndef MKSH_DISABLE_TTY_WARNING
+ if (use_tty && !ttypgrp_ok)
+ warningf(false, Tf_sD_s, "warning",
+ "won't have full job control");
+#endif
+ } else {
+ ttypgrp_ok = false;
+ if (Flag(FTALKING))
+ for (i = NELEM(tt_sigs); --i >= 0; )
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ else
+ for (i = NELEM(tt_sigs); --i >= 0; ) {
+ if (sigtraps[tt_sigs[i]].flags &
+ (TF_ORIG_IGN | TF_ORIG_DFL))
+ setsig(&sigtraps[tt_sigs[i]],
+ (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ?
+ SIG_IGN : SIG_DFL,
+ SS_RESTORE_ORIG|SS_FORCE);
+ }
+ }
+ tty_init_state();
+}
+#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 eno;
+
+ errno = 0;
+ /* this is gonna annoy users; complain to your distro, people! */
+ if (nice(ness) == -1 && (eno = errno) != 0)
+ warningf(false, Tf_sD_s, "bgnice", cstrerror(eno));
+#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)
+{
+ /* for pipelines */
+ static Proc *last_proc;
+
+ int rv = 0, forksleep, jwflags = JW_NONE;
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigset_t omask;
+#endif
+ Proc *p;
+ Job *j;
+ pid_t cldpid;
+
+ if (flags & XPIPEST) {
+ flags &= ~XPIPEST;
+ jwflags |= JW_PIPEST;
+ }
+
+ if (flags & XEXEC)
+ /*
+ * Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
+ * (also done in another execute() below)
+ */
+ 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
+
+ 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",
+ (int)procpid);
+ j = last_job;
+ if (last_proc)
+ last_proc->next = p;
+ last_proc = p;
+ } else {
+ /* 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.
+ */
+ j->flags = (flags & XXCOM) ? JF_XXCOM :
+ ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
+ 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);
+ }
+
+ vistree(p->command, sizeof(p->command), t);
+
+ /* create child process */
+ forksleep = 1;
+ while ((cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+ if (intrsig)
+ /* allow user to ^C out... */
+ break;
+ sleep(forksleep);
+ forksleep <<= 1;
+ }
+ /* ensure $RANDOM changes between parent and child */
+ rndset((unsigned long)cldpid);
+ /* fork failed? */
+ if (cldpid < 0) {
+ kill_job(j, SIGKILL);
+ remove_job(j, "fork failed");
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ errorf("can't fork - try again");
+ }
+ p->pid = cldpid ? cldpid : (procpid = getpid());
+
+#ifndef MKSH_UNEMPLOYED
+ /* job control set up */
+ if (Flag(FMONITOR) && !(flags&XXCOM)) {
+ 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
+ * condition
+ */
+ setpgid(p->pid, j->pgrp);
+ if (ttypgrp_ok && dotty && !(flags & XBGND))
+ tcsetpgrp(tty_fd, j->pgrp);
+ }
+#endif
+
+ /* used to close pipe input fd */
+ if (close_fd >= 0 && (((flags & XPCLOSE) && cldpid) ||
+ ((flags & XCCLOSE) && !cldpid)))
+ close(close_fd);
+ if (!cldpid) {
+ /* child */
+
+ /* Do this before restoring signal */
+ if (flags & XCOPROC)
+ coproc_cleanup(false);
+ cleanup_parents_env();
+#ifndef MKSH_UNEMPLOYED
+ /*
+ * If FMONITOR or FTALKING is set, these signals are ignored,
+ * if neither FMONITOR nor FTALKING are set, the signals have
+ * their inherited values.
+ */
+ if (Flag(FMONITOR) && !(flags & XXCOM)) {
+ for (forksleep = NELEM(tt_sigs); --forksleep >= 0; )
+ setsig(&sigtraps[tt_sigs[forksleep]], SIG_DFL,
+ SS_RESTORE_DFL|SS_FORCE);
+ }
+#endif
+#if HAVE_NICE
+ if (Flag(FBGNICE) && (flags & XBGND))
+ ksh_nice(4);
+#endif
+ if ((flags & XBGND)
+#ifndef MKSH_UNEMPLOYED
+ && !Flag(FMONITOR)
+#endif
+ ) {
+ setsig(&sigtraps[SIGINT], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ setsig(&sigtraps[SIGQUIT], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ if ((!(flags & (XPIPEI | XCOPROC))) &&
+ ((forksleep = open("/dev/null", 0)) > 0)) {
+ (void)ksh_dup2(forksleep, 0, true);
+ close(forksleep);
+ }
+ }
+ /* in case of $(jobs) command */
+ remove_job(j, "child");
+#ifndef MKSH_NOPROSPECTOFWORK
+ /* remove_job needs SIGCHLD blocked still */
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ nzombie = 0;
+#ifndef MKSH_UNEMPLOYED
+ ttypgrp_ok = false;
+ Flag(FMONITOR) = 0;
+#endif
+ Flag(FTALKING) = 0;
+ cleartraps();
+ /* no return */
+ execute(t, (flags & XERROK) | XEXEC, NULL);
+#ifndef MKSH_SMALL
+ if (t->type == TPIPE)
+ unwind(LLEAVE);
+ internal_warningf("%s: execute() returned", "exchild");
+ fptreef(shl_out, 8, "%s: tried to execute {\n\t%T\n}\n",
+ "exchild", t);
+ shf_flush(shl_out);
+#endif
+ unwind(LLEAVE);
+ /* NOTREACHED */
+ }
+
+ /* shell (parent) stuff */
+ if (!(flags & XPIPEO)) {
+ /* last process in a job */
+ j_startjob(j);
+ if (flags & XCOPROC) {
+ j->coproc_id = coproc.id;
+ /* n jobs using co-process output */
+ coproc.njobs++;
+ /* j using co-process input */
+ coproc.job = (void *)j;
+ }
+ if (flags & XBGND) {
+ j_set_async(j);
+ if (Flag(FTALKING)) {
+ shf_fprintf(shl_out, "[%d]", j->job);
+ for (p = j->proc_list; p; p = p->next)
+ shf_fprintf(shl_out, Tf__d,
+ (int)p->pid);
+ shf_putchar('\n', shl_out);
+ shf_flush(shl_out);
+ }
+ } else
+ rv = j_waitj(j, jwflags, "jw:last proc");
+ }
+
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+
+ return (rv);
+}
+
+/* start the last job: only used for $(command) jobs */
+void
+startlast(void)
+{
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
+
+ /* 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;
+#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, Tf_sD_s, "waitlast", "no last job");
+ else
+ internal_warningf(Tf_sD_s, "waitlast", Tnot_started);
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ /* not so arbitrary, non-zero value */
+ return (125);
+ }
+
+ rv = j_waitj(j, JW_NONE, "waitlast");
+
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+
+ return (rv);
+}
+
+/* wait for child, interruptable. */
+int
+waitfor(const char *cp, int *sigp)
+{
+ 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;
+
+ if (cp == NULL) {
+ /*
+ * wait for an unspecified job - always returns 0, so
+ * don't have to worry about exited/signaled jobs
+ */
+ for (j = job_list; j; j = j->next)
+ /* AT&T ksh will wait for stopped jobs - we don't */
+ 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(Tf_sD_s, cp, lookup_msgs[ecode]);
+ return (-1);
+ }
+
+ /* 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 */
+ *sigp = ksh_sigmask(-rv);
+
+ return (rv);
+}
+
+/* kill (built-in) a job */
+int
+j_kill(const char *cp, int sig)
+{
+ 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(Tf_sD_s, cp, lookup_msgs[ecode]);
+ return (1);
+ }
+
+ if (j->pgrp == 0) {
+ /* started when !Flag(FMONITOR) */
+ if (kill_job(j, sig) < 0) {
+ bi_errorf(Tf_sD_s, cp, cstrerror(errno));
+ rv = 1;
+ }
+ } else {
+#ifndef MKSH_UNEMPLOYED
+ if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP))
+ mksh_killpg(j->pgrp, SIGCONT);
+#endif
+ if (mksh_killpg(j->pgrp, sig) < 0) {
+ bi_errorf(Tf_sD_s, cp, cstrerror(errno));
+ rv = 1;
+ }
+ }
+
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+
+ return (rv);
+}
+
+#ifndef MKSH_UNEMPLOYED
+/* fg and bg built-ins: called only if Flag(FMONITOR) set */
+int
+j_resume(const char *cp, int bg)
+{
+ Job *j;
+ Proc *p;
+ int ecode, rv = 0;
+ bool running;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if ((j = j_lookup(cp, &ecode)) == NULL) {
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ bi_errorf(Tf_sD_s, cp, lookup_msgs[ecode]);
+ return (1);
+ }
+
+ if (j->pgrp == 0) {
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ bi_errorf("job not job-controlled");
+ return (1);
+ }
+
+ if (bg)
+ shprintf("[%d] ", j->job);
+
+ running = false;
+ for (p = j->proc_list; p != NULL; p = p->next) {
+ if (p->state == PSTOPPED) {
+ p->state = PRUNNING;
+ p->status = 0;
+ running = true;
+ }
+ shf_puts(p->command, shl_stdout);
+ if (p->next)
+ shf_puts("| ", shl_stdout);
+ }
+ shf_putc('\n', shl_stdout);
+ shf_flush(shl_stdout);
+ if (running)
+ j->state = PRUNNING;
+
+ put_job(j, PJ_PAST_STOPPED);
+ if (bg)
+ j_set_async(j);
+ else {
+ /* attach tty to job */
+ if (j->state == PRUNNING) {
+ if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+ mksh_tcset(tty_fd, &j->ttystat);
+ /* See comment in j_waitj regarding saved_ttypgrp. */
+ if (ttypgrp_ok &&
+ tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ?
+ j->saved_ttypgrp : j->pgrp) < 0) {
+ rv = errno;
+ if (j->flags & JF_SAVEDTTY)
+ mksh_tcset(tty_fd, &tty_state);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ bi_errorf(Tf_ldfailed,
+ "fg: 1st", "tcsetpgrp", tty_fd,
+ (long)((j->flags & JF_SAVEDTTYPGRP) ?
+ j->saved_ttypgrp : j->pgrp),
+ cstrerror(rv));
+ return (1);
+ }
+ }
+ j->flags |= JF_FG;
+ j->flags &= ~JF_KNOWN;
+ if (j == async_job)
+ async_job = NULL;
+ }
+
+ if (j->state == PRUNNING && mksh_killpg(j->pgrp, SIGCONT) < 0) {
+ int eno = errno;
+
+ if (!bg) {
+ j->flags &= ~JF_FG;
+ if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+ mksh_tcset(tty_fd, &tty_state);
+ if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0)
+ warningf(true, Tf_ldfailed,
+ "fg: 2nd", "tcsetpgrp", tty_fd,
+ (long)kshpgrp, cstrerror(errno));
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ bi_errorf(Tf_s_sD_s, "can't continue job",
+ cp, cstrerror(eno));
+ return (1);
+ }
+ if (!bg) {
+ if (ttypgrp_ok) {
+ j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP);
+ }
+ rv = j_waitj(j, JW_NONE, "jw:resume");
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ return (rv);
+}
+#endif
+
+/* are there any running or stopped jobs ? */
+int
+j_stopped_running(void)
+{
+ Job *j;
+ int which = 0;
+
+ for (j = job_list; j != NULL; j = j->next) {
+#ifndef MKSH_UNEMPLOYED
+ if (j->ppid == procpid && j->state == PSTOPPED)
+ which |= 1;
+#endif
+ if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid &&
+ j->ppid == procpid && j->state == PRUNNING)
+ which |= 2;
+ }
+ if (which) {
+ shellf("You have %s%s%s jobs\n",
+ which & 1 ? "stopped" : "",
+ which == 3 ? " and " : "",
+ which & 2 ? "running" : "");
+ return (1);
+ }
+
+ return (0);
+}
+
+
+/* list jobs for jobs built-in */
+int
+j_jobs(const char *cp, int slp,
+ /* 0: short, 1: long, 2: pgrp */
+ int nflag)
+{
+ 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 */
+ nflag = 0;
+ zflag = 1;
+ }
+ if (cp) {
+ int ecode;
+
+ if ((j = j_lookup(cp, &ecode)) == NULL) {
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ bi_errorf(Tf_sD_s, cp, lookup_msgs[ecode]);
+ return (1);
+ }
+ } else
+ j = job_list;
+ how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP);
+ for (; j; j = j->next) {
+ if ((!(j->flags & JF_ZOMBIE) || zflag) &&
+ (!nflag || (j->flags & JF_CHANGED))) {
+ j_print(j, how, shl_stdout);
+ if (j->state == PEXITED || j->state == PSIGNALLED)
+ j->flags |= JF_REMOVE;
+ }
+ if (cp)
+ break;
+ }
+ /* Remove jobs after printing so there won't be multiple + or - jobs */
+ for (j = job_list; j; j = tmp) {
+ tmp = j->next;
+ if (j->flags & JF_REMOVE)
+ remove_job(j, Tjobs);
+ }
+#ifndef MKSH_NOPROSPECTOFWORK
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ return (0);
+}
+
+/* list jobs for top-level notification */
+void
+j_notify(void)
+{
+ 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
+ * multiple +/- jobs.
+ */
+ if (j->state == PEXITED || j->state == PSIGNALLED)
+ j->flags |= JF_REMOVE;
+ }
+ for (j = job_list; j; j = tmp) {
+ tmp = j->next;
+ if (j->flags & JF_REMOVE) {
+ if (j == async_job || (j->flags & JF_KNOWN)) {
+ j->flags = (j->flags & ~JF_REMOVE) | JF_ZOMBIE;
+ j->job = -1;
+ nzombie++;
+ } else
+ 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);
+}
+
+/*
+ * Make j the last async process
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_set_async(Job *j)
+{
+ Job *jl, *oldest;
+
+ if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
+ remove_job(async_job, "async");
+ if (!(j->flags & JF_STARTED)) {
+ internal_warningf(Tf_sD_s, "j_async", Tjob_not_started);
+ return;
+ }
+ async_job = j;
+ async_pid = j->last_proc->pid;
+ while (nzombie > CHILD_MAX) {
+ oldest = NULL;
+ for (jl = job_list; jl; jl = jl->next)
+ if (jl != async_job && (jl->flags & JF_ZOMBIE) &&
+ (!oldest || jl->age < oldest->age))
+ oldest = jl;
+ if (!oldest) {
+ /* XXX debugging */
+ if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
+ internal_warningf("%s: bad nzombie (%d)",
+ "j_async", nzombie);
+ nzombie = 0;
+ }
+ break;
+ }
+ remove_job(oldest, "zombie");
+ }
+}
+
+/*
+ * Start a job: set STARTED, check for held signals and set j->last_proc
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_startjob(Job *j)
+{
+ Proc *p;
+
+ j->flags |= JF_STARTED;
+ for (p = j->proc_list; p->next; p = p->next)
+ ;
+ 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
+}
+
+/*
+ * wait for job to complete or change state
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+j_waitj(Job *j,
+ /* see JW_* */
+ int flags,
+ const char *where)
+{
+ Proc *p;
+ int rv;
+#ifdef MKSH_NO_SIGSUSPEND
+ sigset_t omask;
+#endif
+
+ /*
+ * No auto-notify on the job we are waiting on.
+ */
+ j->flags |= JF_WAITING;
+ if (flags & JW_ASYNCNOTIFY)
+ j->flags |= JF_W_ASYNCNOTIFY;
+
+#ifndef MKSH_UNEMPLOYED
+ if (!Flag(FMONITOR))
+#endif
+ flags |= JW_STOPPEDWAIT;
+
+ while (j->state == PRUNNING ||
+ ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) {
+#ifndef MKSH_NOPROSPECTOFWORK
+#ifdef MKSH_NO_SIGSUSPEND
+ sigprocmask(SIG_SETMASK, &sm_default, &omask);
+ pause();
+ /* note that handlers may run here so they need to know */
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#else
+ sigsuspend(&sm_default);
+#endif
+#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);
+ /* not reached... */
+ j->flags |= oldf;
+ }
+ if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
+ j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+ return (-rv);
+ }
+ }
+ j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+
+ if (j->flags & JF_FG) {
+ j->flags &= ~JF_FG;
+#ifndef MKSH_UNEMPLOYED
+ if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) {
+ /*
+ * Save the tty's current pgrp so it can be restored
+ * when the job is foregrounded. This is to
+ * deal with things like the GNU su which does
+ * a fork/exec instead of an exec (the fork means
+ * the execed shell gets a different pid from its
+ * pgrp, so naturally it sets its pgrp and gets hosed
+ * when it gets foregrounded by the parent shell which
+ * has restored the tty's pgrp to that of the su
+ * process).
+ */
+ if (j->state == PSTOPPED &&
+ (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
+ j->flags |= JF_SAVEDTTYPGRP;
+ if (tcsetpgrp(tty_fd, kshpgrp) < 0)
+ warningf(true, Tf_ldfailed,
+ "j_waitj:", "tcsetpgrp", tty_fd,
+ (long)kshpgrp, cstrerror(errno));
+ if (j->state == PSTOPPED) {
+ j->flags |= JF_SAVEDTTY;
+ mksh_tcget(tty_fd, &j->ttystat);
+ }
+ }
+#endif
+ if (tty_hasstate) {
+ /*
+ * Only restore tty settings if job was originally
+ * started in the foreground. Problems can be
+ * caused by things like 'more foobar &' which will
+ * typically get and save the shell's vi/emacs tty
+ * settings before setting up the tty for itself;
+ * when more exits, it restores the 'original'
+ * settings, and things go down hill from there...
+ */
+ if (j->state == PEXITED && j->status == 0 &&
+ (j->flags & JF_USETTYMODE)) {
+ mksh_tcget(tty_fd, &tty_state);
+ } else {
+ mksh_tcset(tty_fd, &tty_state);
+ /*-
+ * Don't use tty mode if job is stopped and
+ * later restarted and exits. Consider
+ * the sequence:
+ * vi foo (stopped)
+ * ...
+ * stty something
+ * ...
+ * fg (vi; ZZ)
+ * mode should be that of the stty, not what
+ * was before the vi started.
+ */
+ if (j->state == PSTOPPED)
+ j->flags &= ~JF_USETTYMODE;
+ }
+ }
+#ifndef MKSH_UNEMPLOYED
+ /*
+ * If it looks like user hit ^C to kill a job, pretend we got
+ * one too to break out of for loops, etc. (AT&T ksh does this
+ * even when not monitoring, but this doesn't make sense since
+ * a tty generated ^C goes to the whole process group)
+ */
+ if (Flag(FMONITOR) && j->state == PSIGNALLED &&
+ WIFSIGNALED(j->last_proc->status)) {
+ int termsig;
+
+ if ((termsig = WTERMSIG(j->last_proc->status)) > 0 &&
+ termsig < ksh_NSIG &&
+ (sigtraps[termsig].flags & TF_TTY_INTR))
+ trapsig(termsig);
+ }
+#endif
+ }
+
+ j_usrtime = j->usrtime;
+ j_systime = j->systime;
+ rv = j->status;
+
+ if (!(p = j->proc_list)) {
+ ; /* nothing */
+ } else if (flags & JW_PIPEST) {
+ uint32_t num = 0;
+ 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);
+ if (Flag(FPIPEFAIL) && vp->val.i)
+ rv = vp->val.i;
+ p = p->next;
+ }
+ } else if (Flag(FPIPEFAIL)) {
+ do {
+ const int i = proc_errorlevel(p);
+
+ if (i)
+ rv = i;
+ } while ((p = p->next));
+ }
+
+ if (!(flags & JW_ASYNCNOTIFY)
+#ifndef MKSH_UNEMPLOYED
+ && (!Flag(FMONITOR) || j->state != PSTOPPED)
+#endif
+ ) {
+ j_print(j, JP_SHORT, shl_out);
+ shf_flush(shl_out);
+ }
+ if (j->state != PSTOPPED
+#ifndef MKSH_UNEMPLOYED
+ && (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY))
+#endif
+ )
+ remove_job(j, where);
+
+ return (rv);
+}
+
+/*
+ * SIGCHLD handler to reap children and update job states
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+/* ARGSUSED */
+static void
+j_sigchld(int sig MKSH_A_UNUSED)
+{
+ int saved_errno = errno;
+ Job *j;
+ Proc *p = NULL;
+ pid_t pid;
+ int status;
+ struct rusage ru0, ru1;
+#ifdef MKSH_NO_SIGSUSPEND
+ sigset_t omask;
+
+ /* this handler can run while SIGCHLD is not blocked, so block it now */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
+
+#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
+ * before all the processes in a pipe line are started (so the
+ * setpgid() won't fail)
+ */
+ for (j = job_list; j; j = j->next)
+ if (j->ppid == procpid && !(j->flags & JF_STARTED)) {
+ held_sigchld = 1;
+ goto j_sigchld_out;
+ }
+#endif
+
+ getrusage(RUSAGE_CHILDREN, &ru0);
+ do {
+#ifndef MKSH_NOPROSPECTOFWORK
+ pid = waitpid(-1, &status, (WNOHANG |
+#if defined(WCONTINUED) && defined(WIFCONTINUED)
+ WCONTINUED |
+#endif
+ WUNTRACED));
+#else
+ pid = wait(&status);
+#endif
+
+ /*
+ * return if this would block (0) or no children
+ * or interrupted (-1)
+ */
+ if (pid <= 0)
+ goto j_sigchld_out;
+
+ getrusage(RUSAGE_CHILDREN, &ru1);
+
+ /* find job and process structures for this pid */
+ for (j = job_list; j != NULL; j = j->next)
+ for (p = j->proc_list; p != NULL; p = p->next)
+ if (p->pid == pid)
+ goto found;
+ found:
+ if (j == NULL) {
+ /* Can occur if process has kids, then execs shell
+ warningf(true, "bad process waited for (pid = %d)",
+ pid);
+ */
+ ru0 = ru1;
+ continue;
+ }
+
+ timeradd(&j->usrtime, &ru1.ru_utime, &j->usrtime);
+ timersub(&j->usrtime, &ru0.ru_utime, &j->usrtime);
+ timeradd(&j->systime, &ru1.ru_stime, &j->systime);
+ timersub(&j->systime, &ru0.ru_stime, &j->systime);
+ ru0 = ru1;
+ p->status = status;
+#ifndef MKSH_UNEMPLOYED
+ if (WIFSTOPPED(status))
+ p->state = PSTOPPED;
+ else
+#if defined(WCONTINUED) && defined(WIFCONTINUED)
+ if (WIFCONTINUED(status)) {
+ p->state = j->state = PRUNNING;
+ /* skip check_job(), no-op in this case */
+ continue;
+ } else
+#endif
+#endif
+ if (WIFSIGNALED(status))
+ p->state = PSIGNALLED;
+ else
+ p->state = PEXITED;
+
+ /* check to see if entire job is done */
+ check_job(j);
+ }
+#ifndef MKSH_NOPROSPECTOFWORK
+ while (/* CONSTCOND */ 1);
+#else
+ while (/* CONSTCOND */ 0);
+#endif
+
+ j_sigchld_out:
+#ifdef MKSH_NO_SIGSUSPEND
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+ errno = saved_errno;
+}
+
+/*
+ * Called only when a process in j has exited/stopped (ie, called only
+ * from j_sigchld()). If no processes are running, the job status
+ * and state are updated, asynchronous job notification is done and,
+ * if unneeded, the job is removed.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+check_job(Job *j)
+{
+ int jstate;
+ Proc *p;
+
+ /* XXX debugging (nasty - interrupt routine using shl_out) */
+ if (!(j->flags & JF_STARTED)) {
+ internal_warningf("check_job: job started (flags 0x%X)",
+ (unsigned int)j->flags);
+ return;
+ }
+
+ jstate = PRUNNING;
+ for (p=j->proc_list; p != NULL; p = p->next) {
+ if (p->state == PRUNNING)
+ /* some processes still running */
+ return;
+ if (p->state > jstate)
+ jstate = p->state;
+ }
+ j->state = jstate;
+ j->status = proc_errorlevel(j->last_proc);
+
+ /*
+ * Note when co-process dies: can't be done in j_wait() nor
+ * remove_job() since neither may be called for non-interactive
+ * shells.
+ */
+ if (j->state == PEXITED || j->state == PSIGNALLED) {
+ /*
+ * No need to keep co-process input any more
+ * (at least, this is what ksh93d thinks)
+ */
+ if (coproc.job == j) {
+ coproc.job = NULL;
+ /*
+ * XXX would be nice to get the closes out of here
+ * so they aren't done in the signal handler.
+ * Would mean a check in coproc_getfd() to
+ * do "if job == 0 && write >= 0, close write".
+ */
+ coproc_write_close(coproc.write);
+ }
+ /* Do we need to keep the output? */
+ if (j->coproc_id && j->coproc_id == coproc.id &&
+ --coproc.njobs == 0)
+ coproc_readw_close(coproc.read);
+ }
+
+ j->flags |= JF_CHANGED;
+#ifndef MKSH_UNEMPLOYED
+ if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) {
+ /*
+ * Only put stopped jobs at the front to avoid confusing
+ * the user (don't want finished jobs effecting %+ or %-)
+ */
+ if (j->state == PSTOPPED)
+ put_job(j, PJ_ON_FRONT);
+ if (Flag(FNOTIFY) &&
+ (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) {
+ /* Look for the real file descriptor 2 */
+ {
+ struct env *ep;
+ int fd = 2;
+
+ for (ep = e; ep; ep = ep->oenv)
+ if (ep->savefd && ep->savefd[2])
+ fd = ep->savefd[2];
+ shf_reopen(fd, SHF_WR, shl_j);
+ }
+ /*
+ * Can't call j_notify() as it removes jobs. The job
+ * must stay in the job list as j_waitj() may be
+ * running with this job.
+ */
+ j_print(j, JP_MEDIUM, shl_j);
+ shf_flush(shl_j);
+ if (!(j->flags & JF_WAITING) && j->state != PSTOPPED)
+ remove_job(j, "notify");
+ }
+ }
+#endif
+ if (
+#ifndef MKSH_UNEMPLOYED
+ !Flag(FMONITOR) &&
+#endif
+ !(j->flags & (JF_WAITING|JF_FG)) &&
+ j->state != PSTOPPED) {
+ if (j == async_job || (j->flags & JF_KNOWN)) {
+ j->flags |= JF_ZOMBIE;
+ j->job = -1;
+ nzombie++;
+ } else
+ remove_job(j, "checkjob");
+ }
+}
+
+/*
+ * Print job status in either short, medium or long format.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_print(Job *j, int how, struct shf *shf)
+{
+ Proc *p;
+ int state;
+ int status;
+#ifdef WCOREDUMP
+ bool coredumped;
+#endif
+ char jobchar = ' ';
+ char buf[64];
+ const char *filler;
+ int output = 0;
+
+ if (how == JP_PGRP) {
+ /*
+ * POSIX doesn't say what to do it there is no process
+ * group leader (ie, !FMONITOR). We arbitrarily return
+ * last pid (which is what $! returns).
+ */
+ shf_fprintf(shf, Tf_dN, (int)(j->pgrp ? j->pgrp :
+ (j->last_proc ? j->last_proc->pid : 0)));
+ return;
+ }
+ j->flags &= ~JF_CHANGED;
+ filler = j->job > 10 ? "\n " : "\n ";
+ if (j == job_list)
+ jobchar = '+';
+ else if (j == job_list->next)
+ jobchar = '-';
+
+ for (p = j->proc_list; p != NULL;) {
+#ifdef WCOREDUMP
+ coredumped = false;
+#endif
+ switch (p->state) {
+ case PRUNNING:
+ memcpy(buf, "Running", 8);
+ break;
+ case PSTOPPED: {
+ int stopsig = WSTOPSIG(p->status);
+
+ strlcpy(buf, stopsig > 0 && stopsig < ksh_NSIG ?
+ sigtraps[stopsig].mess : "Stopped", sizeof(buf));
+ break;
+ }
+ case PEXITED: {
+ int exitstatus = (WEXITSTATUS(p->status)) & 255;
+
+ if (how == JP_SHORT)
+ buf[0] = '\0';
+ else if (exitstatus == 0)
+ memcpy(buf, "Done", 5);
+ else
+ shf_snprintf(buf, sizeof(buf), "Done (%d)",
+ exitstatus);
+ break;
+ }
+ case PSIGNALLED: {
+ int termsig = WTERMSIG(p->status);
+#ifdef WCOREDUMP
+ if (WCOREDUMP(p->status))
+ coredumped = true;
+#endif
+ /*
+ * kludge for not reporting 'normal termination
+ * signals' (i.e. SIGINT, SIGPIPE)
+ */
+ if (how == JP_SHORT &&
+#ifdef WCOREDUMP
+ !coredumped &&
+#endif
+ (termsig == SIGINT || termsig == SIGPIPE)) {
+ buf[0] = '\0';
+ } else
+ strlcpy(buf, termsig > 0 && termsig < ksh_NSIG ?
+ sigtraps[termsig].mess : "Signalled",
+ sizeof(buf));
+ break;
+ }
+ default:
+ buf[0] = '\0';
+ }
+
+ if (how != JP_SHORT) {
+ if (p == j->proc_list)
+ shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
+ else
+ shf_puts(filler, shf);
+ }
+
+ if (how == JP_LONG)
+ shf_fprintf(shf, "%5d ", (int)p->pid);
+
+ if (how == JP_SHORT) {
+ if (buf[0]) {
+ output = 1;
+#ifdef WCOREDUMP
+ shf_fprintf(shf, "%s%s ",
+ buf, coredumped ? " (core dumped)" : null);
+#else
+ shf_puts(buf, shf);
+ shf_putchar(' ', shf);
+#endif
+ }
+ } else {
+ output = 1;
+ shf_fprintf(shf, "%-20s %s%s%s", buf, p->command,
+ p->next ? "|" : null,
+#ifdef WCOREDUMP
+ coredumped ? " (core dumped)" :
+#endif
+ null);
+ }
+
+ state = p->state;
+ status = p->status;
+ p = p->next;
+ while (p && p->state == state && p->status == status) {
+ if (how == JP_LONG)
+ shf_fprintf(shf, "%s%5d %-20s %s%s", filler,
+ (int)p->pid, T1space, p->command,
+ p->next ? "|" : null);
+ else if (how == JP_MEDIUM)
+ shf_fprintf(shf, Tf__ss, p->command,
+ p->next ? "|" : null);
+ p = p->next;
+ }
+ }
+ if (output)
+ shf_putc('\n', shf);
+}
+
+/*
+ * Convert % sequence to job
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+j_lookup(const char *cp, int *ecodep)
+{
+ Job *j, *last_match;
+ Proc *p;
+ size_t len;
+ int job = 0;
+
+ if (ctype(*cp, C_DIGIT) && getn(cp, &job)) {
+ /* Look for last_proc->pid (what $! returns) first... */
+ for (j = job_list; j != NULL; j = j->next)
+ if (j->last_proc && j->last_proc->pid == job)
+ return (j);
+ /*
+ * ...then look for process group (this is non-POSIX,
+ * but should not break anything
+ */
+ for (j = job_list; j != NULL; j = j->next)
+ if (j->pgrp && j->pgrp == job)
+ return (j);
+ goto j_lookup_nosuch;
+ }
+ if (*cp != '%') {
+ j_lookup_invalid:
+ if (ecodep)
+ *ecodep = JL_INVALID;
+ return (NULL);
+ }
+ switch (*++cp) {
+ case '\0': /* non-standard */
+ case '+':
+ case '%':
+ if (job_list != NULL)
+ return (job_list);
+ break;
+
+ case '-':
+ if (job_list != NULL && job_list->next)
+ return (job_list->next);
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (!getn(cp, &job))
+ goto j_lookup_invalid;
+ for (j = job_list; j != NULL; j = j->next)
+ if (j->job == job)
+ return (j);
+ break;
+
+ /* %?string */
+ case '?':
+ last_match = NULL;
+ for (j = job_list; j != NULL; j = j->next)
+ for (p = j->proc_list; p != NULL; p = p->next)
+ if (strstr(p->command, cp+1) != NULL) {
+ if (last_match) {
+ if (ecodep)
+ *ecodep = JL_AMBIG;
+ return (NULL);
+ }
+ last_match = j;
+ }
+ if (last_match)
+ return (last_match);
+ break;
+
+ /* %string */
+ default:
+ len = strlen(cp);
+ last_match = NULL;
+ for (j = job_list; j != NULL; j = j->next)
+ if (strncmp(cp, j->proc_list->command, len) == 0) {
+ if (last_match) {
+ if (ecodep)
+ *ecodep = JL_AMBIG;
+ return (NULL);
+ }
+ last_match = j;
+ }
+ if (last_match)
+ return (last_match);
+ break;
+ }
+ j_lookup_nosuch:
+ if (ecodep)
+ *ecodep = JL_NOSUCH;
+ return (NULL);
+}
+
+static Job *free_jobs;
+static Proc *free_procs;
+
+/*
+ * allocate a new job and fill in the job number.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+new_job(void)
+{
+ int i;
+ Job *newj, *j;
+
+ if (free_jobs != NULL) {
+ newj = free_jobs;
+ free_jobs = free_jobs->next;
+ } else {
+ char *cp;
+
+ /*
+ * struct job includes ALLOC_ITEM for alignment constraints
+ * so first get the actually used memory, then assign it
+ */
+ cp = alloc(sizeof(Job) - sizeof(ALLOC_ITEM), APERM);
+ /* undo what alloc() did to the malloc result address */
+ newj = (void *)(cp - sizeof(ALLOC_ITEM));
+ }
+
+ /* brute force method */
+ i = 0;
+ do {
+ ++i;
+ j = job_list;
+ while (j && j->job != i)
+ j = j->next;
+ } while (j);
+ newj->job = i;
+
+ return (newj);
+}
+
+/*
+ * Allocate new process struct
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Proc *
+new_proc(void)
+{
+ Proc *p;
+
+ if (free_procs != NULL) {
+ p = free_procs;
+ free_procs = free_procs->next;
+ } else
+ p = alloc(sizeof(Proc), APERM);
+
+ return (p);
+}
+
+/*
+ * Take job out of job_list and put old structures into free list.
+ * Keeps nzombies, last_job and async_job up to date.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+remove_job(Job *j, const char *where)
+{
+ Proc *p, *tmp;
+ Job **prev, *curr;
+
+ prev = &job_list;
+ curr = job_list;
+ while (curr && curr != j) {
+ prev = &curr->next;
+ curr = *prev;
+ }
+ if (curr != j) {
+ internal_warningf("remove_job: job %s (%s)", Tnot_found, where);
+ return;
+ }
+ *prev = curr->next;
+
+ /* free up proc structures */
+ for (p = j->proc_list; p != NULL; ) {
+ tmp = p;
+ p = p->next;
+ tmp->next = free_procs;
+ free_procs = tmp;
+ }
+
+ if ((j->flags & JF_ZOMBIE) && j->ppid == procpid)
+ --nzombie;
+ j->next = free_jobs;
+ free_jobs = j;
+
+ if (j == last_job)
+ last_job = NULL;
+ if (j == async_job)
+ async_job = NULL;
+}
+
+/*
+ * put j in a particular location (taking it out job_list if it is there
+ * already)
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+put_job(Job *j, int where)
+{
+ Job **prev, *curr;
+
+ /* Remove job from list (if there) */
+ prev = &job_list;
+ curr = job_list;
+ while (curr && curr != j) {
+ prev = &curr->next;
+ curr = *prev;
+ }
+ if (curr == j)
+ *prev = curr->next;
+
+ switch (where) {
+ case PJ_ON_FRONT:
+ j->next = job_list;
+ job_list = j;
+ break;
+
+ case PJ_PAST_STOPPED:
+ prev = &job_list;
+ curr = job_list;
+ for (; curr && curr->state == PSTOPPED; prev = &curr->next,
+ curr = *prev)
+ ;
+ j->next = curr;
+ *prev = j;
+ break;
+ }
+}
+
+/*
+ * nuke a job (called when unable to start full job).
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+kill_job(Job *j, int sig)
+{
+ Proc *p;
+ int rval = 0;
+
+ for (p = j->proc_list; p != NULL; p = p->next)
+ if (p->pid != 0)
+ if (kill(p->pid, sig) < 0)
+ rval = -1;
+ return (rval);
+}
+
+static void
+tty_init_talking(void)
+{
+ switch (tty_init_fd()) {
+ case 0:
+ break;
+ case 1:
+#ifndef MKSH_DISABLE_TTY_WARNING
+ warningf(false, Tf_sD_s_sD_s,
+ "No controlling tty", Topen, T_devtty, cstrerror(errno));
+#endif
+ break;
+ case 2:
+#ifndef MKSH_DISABLE_TTY_WARNING
+ warningf(false, Tf_s_sD_s, Tcant_find, Ttty_fd,
+ cstrerror(errno));
+#endif
+ break;
+ case 3:
+ warningf(false, Tf_ssfaileds, "j_ttyinit",
+ Ttty_fd_dupof, cstrerror(errno));
+ break;
+ case 4:
+ warningf(false, Tf_sD_sD_s, "j_ttyinit",
+ "can't set close-on-exec flag", cstrerror(errno));
+ break;
+ }
+}
+
+static void
+tty_init_state(void)
+{
+ if (tty_fd >= 0) {
+ mksh_tcget(tty_fd, &tty_state);
+ tty_hasstate = true;
+ }
+}
diff --git a/shells/mksh/files/lalloc.c b/shells/mksh/files/lalloc.c
new file mode 100644
index 00000000000..0aff3aa9e26
--- /dev/null
+++ b/shells/mksh/files/lalloc.c
@@ -0,0 +1,193 @@
+/*-
+ * Copyright (c) 2009, 2010, 2011, 2013, 2014, 2016
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+#ifdef MKSH_ALLOC_CATCH_UNDERRUNS
+#include <err.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.26 2016/02/26 21:53:36 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_osi(n) : realloc_osi((p), (n)))
+#else
+#define remalloc(p,n) realloc_osi((p), (n))
+#endif
+
+
+static struct lalloc_common *findptr(struct lalloc_common **, char *, Area *);
+
+#ifndef MKSH_ALLOC_CATCH_UNDERRUNS
+#define ALLOC_ISUNALIGNED(p) (((size_t)(p)) % sizeof(struct lalloc_common))
+#else
+#define ALLOC_ISUNALIGNED(p) (((size_t)(p)) & 4095)
+#undef remalloc
+#undef free_osimalloc
+
+static void
+free_osimalloc(void *ptr)
+{
+ struct lalloc_item *lp = ptr;
+
+ if (munmap(lp, lp->len))
+ err(1, "free_osimalloc");
+}
+
+static void *
+remalloc(void *ptr, size_t size)
+{
+ struct lalloc_item *lp, *lold = ptr;
+
+ size = (size + 4095) & ~(size_t)4095;
+
+ if (lold && lold->len >= size)
+ return (ptr);
+
+ if ((lp = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, (off_t)0)) == MAP_FAILED)
+ err(1, "remalloc: mmap(%zu)", size);
+ if (ALLOC_ISUNALIGNED(lp))
+ errx(1, "remalloc: unaligned(%p)", lp);
+ if (mprotect(((char *)lp) + 4096, 4096, PROT_NONE))
+ err(1, "remalloc: mprotect");
+ lp->len = size;
+
+ if (lold) {
+ memcpy(((char *)lp) + 8192, ((char *)lold) + 8192,
+ lold->len - 8192);
+ if (munmap(lold, lold->len))
+ err(1, "remalloc: munmap");
+ }
+
+ return (lp);
+}
+#endif
+
+void
+ainit(Area *ap)
+{
+#ifdef MKSH_ALLOC_CATCH_UNDERRUNS
+ if (sysconf(_SC_PAGESIZE) != 4096) {
+ fprintf(stderr, "mksh: fatal: pagesize %lu not 4096!\n",
+ sysconf(_SC_PAGESIZE));
+ fflush(stderr);
+ abort();
+ }
+#endif
+ /* area pointer and items share struct lalloc_common */
+ ap->next = NULL;
+}
+
+static struct lalloc_common *
+findptr(struct lalloc_common **lpp, char *ptr, Area *ap)
+{
+ void *lp;
+
+#ifndef MKSH_SMALL
+ if (ALLOC_ISUNALIGNED(ptr))
+ goto fail;
+#endif
+ /* get address of ALLOC_ITEM from user item */
+ /*
+ * note: the alignment of "ptr" to ALLOC_ITEM is checked
+ * above; the "void *" gets us rid of a gcc 2.95 warning
+ */
+ *lpp = (lp = ptr - sizeof(ALLOC_ITEM));
+ /* search for allocation item in group list */
+ while (ap->next != lp)
+ if ((ap = ap->next) == NULL) {
+#ifndef MKSH_SMALL
+ fail:
+#endif
+#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)
+{
+ struct lalloc_common *lp = NULL;
+
+ /* resizing (true) or newly allocating? */
+ if (ptr != NULL) {
+ struct lalloc_common *pp;
+
+ pp = findptr(&lp, ptr, ap);
+ pp->next = lp->next;
+ }
+
+ if (notoktoadd(numb, sizeof(ALLOC_ITEM)) ||
+ (lp = remalloc(lp, numb + sizeof(ALLOC_ITEM))) == NULL
+#ifndef MKSH_SMALL
+ || ALLOC_ISUNALIGNED(lp)
+#endif
+ )
+ internal_errorf(Toomem, numb);
+ /* area pointer and items share struct lalloc_common */
+ lp->next = ap->next;
+ ap->next = lp;
+ /* return user item address */
+ return ((char *)lp + sizeof(ALLOC_ITEM));
+}
+
+void
+afree(void *ptr, Area *ap)
+{
+ if (ptr != NULL) {
+ struct lalloc_common *lp, *pp;
+
+ pp = findptr(&lp, ptr, ap);
+ /* unhook */
+ pp->next = lp->next;
+ /* now free ALLOC_ITEM */
+ free_osimalloc(lp);
+ }
+}
+
+void
+afreeall(Area *ap)
+{
+ struct lalloc_common *lp;
+
+ /* traverse group (linked list) */
+ while ((lp = ap->next) != NULL) {
+ /* make next ALLOC_ITEM head of list */
+ ap->next = lp->next;
+ /* free old head */
+ free_osimalloc(lp);
+ }
+}
diff --git a/shells/mksh/files/lex.c b/shells/mksh/files/lex.c
new file mode 100644
index 00000000000..21c2f354a79
--- /dev/null
+++ b/shells/mksh/files/lex.c
@@ -0,0 +1,1820 @@
+/* $OpenBSD: lex.c,v 1.51 2015/09/10 22:48:58 nicm Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.251 2020/03/10 23:48:40 tg Exp $");
+
+/*
+ * states while lexing word
+ */
+#define SBASE 0 /* outside any lexical constructs */
+#define SWORD 1 /* implicit quoting for substitute() */
+#define SLETPAREN 2 /* inside (( )), implicit quoting */
+#define SSQUOTE 3 /* inside '' */
+#define SDQUOTE 4 /* inside "" */
+#define SEQUOTE 5 /* inside $'' */
+#define SBRACE 6 /* inside ${} */
+#define SQBRACE 7 /* inside "${}" */
+#define SBQUOTE 8 /* inside `` */
+#define SASPAREN 9 /* inside $(( )) */
+#define SHEREDELIM 10 /* parsing << or <<- delimiter */
+#define SHEREDQUOTE 11 /* parsing " in << or <<- delimiter */
+#define SPATTERN 12 /* parsing *(...|...) pattern (*+?@!) */
+#define SADELIM 13 /* like SBASE, looking for delimiter */
+#define STBRACEKORN 14 /* parsing ${...[#%]...} !FSH */
+#define STBRACEBOURNE 15 /* parsing ${...[#%]...} FSH */
+#define SINVALID 255 /* invalid state */
+
+struct sretrace_info {
+ struct sretrace_info *next;
+ XString xs;
+ char *xp;
+};
+
+/*
+ * Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state.
+ */
+typedef struct lex_state {
+ union {
+ /* point to the next state block */
+ struct lex_state *base;
+ /* marks start of state output in output string */
+ size_t start;
+ /* SBQUOTE: true if in double quotes: "`...`" */
+ /* SEQUOTE: got NUL, ignore rest of string */
+ bool abool;
+ /* SADELIM information */
+ struct {
+ /* character to search for */
+ unsigned char delimiter;
+ /* max. number of delimiters */
+ unsigned char num;
+ } adelim;
+ } u;
+ /* count open parentheses */
+ short nparen;
+ /* type of this state */
+ uint8_t type;
+ /* extra flags */
+ uint8_t ls_flags;
+} Lex_state;
+#define ls_base u.base
+#define ls_start u.start
+#define ls_bool u.abool
+#define ls_adelim u.adelim
+
+/* ls_flags */
+#define LS_HEREDOC BIT(0)
+
+typedef struct {
+ Lex_state *base;
+ Lex_state *end;
+} State_info;
+
+static void readhere(struct ioword *);
+static void ungetsc(int);
+static void ungetsc_i(int);
+static int getsc_uu(void);
+static void getsc_line(Source *);
+static int getsc_bn(void);
+static int getsc_i(void);
+static char *get_brace_var(XString *, char *);
+static bool arraysub(char **);
+static void gethere(void);
+static Lex_state *push_state_i(State_info *, Lex_state *);
+static Lex_state *pop_state_i(State_info *, Lex_state *);
+
+static int backslash_skip;
+static int ignore_backslash_newline;
+
+/* optimised getsc_bn() */
+#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);
+
+/* callback */
+static int
+getsc_i(void)
+{
+ o_getsc_r((unsigned int)(unsigned char)o_getsc());
+}
+
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define getsc() getsc_i()
+#else
+static int getsc_r(int);
+
+static int
+getsc_r(int c)
+{
+ o_getsc_r(c);
+}
+
+#define getsc() getsc_r((unsigned int)(unsigned char)o_getsc())
+#endif
+
+#define STATE_BSIZE 8
+
+#define PUSH_STATE(s) do { \
+ uint8_t state_flags = statep->ls_flags; \
+ if (++statep == state_info.end) \
+ statep = push_state_i(&state_info, statep); \
+ state = statep->type = (s); \
+ statep->ls_flags = state_flags; \
+} while (/* CONSTCOND */ 0)
+
+#define POP_STATE() do { \
+ if (--statep == state_info.base) \
+ statep = pop_state_i(&state_info, statep); \
+ state = statep->type; \
+} while (/* CONSTCOND */ 0)
+
+#define PUSH_SRETRACE(s) do { \
+ struct sretrace_info *ri; \
+ \
+ PUSH_STATE(s); \
+ 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); \
+ POP_STATE(); \
+} 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. Note "$(...)" are now parsed recursively.
+ */
+
+int
+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].type = SINVALID;
+ states[0].ls_base = NULL;
+ statep = &states[1];
+ state_info.base = states;
+ state_info.end = &state_info.base[STATE_BSIZE];
+
+ Xinit(ws, wp, 64, ATEMP);
+
+ backslash_skip = 0;
+ ignore_backslash_newline = 0;
+
+ if (cf & ONEWORD)
+ state = SWORD;
+ else if (cf & LETEXPR) {
+ /* enclose arguments in (double) quotes */
+ *wp++ = OQUOTE;
+ state = SLETPAREN;
+ statep->nparen = 0;
+ } else {
+ /* normal lexing */
+ state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+ do {
+ c = getsc();
+ } while (ctype(c, C_BLANK));
+ if (c == '#') {
+ ignore_backslash_newline++;
+ do {
+ c = getsc();
+ } while (!ctype(c, C_NUL | C_LF));
+ ignore_backslash_newline--;
+ }
+ ungetsc(c);
+ }
+ if (source->flags & SF_ALIAS) {
+ /* trailing ' ' in alias definition */
+ source->flags &= ~SF_ALIAS;
+ /* POSIX: trailing space only counts if parsing simple cmd */
+ if (!Flag(FPOSIX) || (cf & CMDWORD))
+ cf |= ALIAS;
+ }
+
+ /* Initial state: one of SWORD SLETPAREN SHEREDELIM SBASE */
+ statep->type = state;
+ statep->ls_flags = (cf & HEREDOC) ? LS_HEREDOC : 0;
+
+ /* collect non-special or quoted characters to form word */
+ while (!((c = getsc()) == 0 ||
+ ((state == SBASE || state == SHEREDELIM) && ctype(c, C_LEX1)))) {
+ if (state == SBASE &&
+ subshell_nesting_type == ORD(/*{*/ '}') &&
+ (unsigned int)c == ORD(/*{*/ '}'))
+ /* possibly end ${ :;} */
+ break;
+ Xcheck(ws, wp);
+ switch (state) {
+ case SADELIM:
+ if ((unsigned int)c == ORD('('))
+ statep->nparen++;
+ else if ((unsigned int)c == ORD(')'))
+ statep->nparen--;
+ else if (statep->nparen == 0 &&
+ ((unsigned int)c == ORD(/*{*/ '}') ||
+ c == (int)statep->ls_adelim.delimiter)) {
+ *wp++ = ADELIM;
+ *wp++ = c;
+ if ((unsigned int)c == ORD(/*{*/ '}') ||
+ --statep->ls_adelim.num == 0)
+ POP_STATE();
+ if ((unsigned int)c == ORD(/*{*/ '}'))
+ POP_STATE();
+ break;
+ }
+ /* FALLTHROUGH */
+ case SBASE:
+ if ((unsigned int)c == ORD('[') && (cf & CMDASN)) {
+ /* temporary */
+ *wp = EOS;
+ if (is_wdvarname(Xstring(ws, wp), false)) {
+ char *p, *tmp;
+
+ if (arraysub(&tmp)) {
+ *wp++ = CHAR;
+ *wp++ = c;
+ for (p = tmp; *p; ) {
+ Xcheck(ws, wp);
+ *wp++ = CHAR;
+ *wp++ = *p++;
+ }
+ afree(tmp, ATEMP);
+ break;
+ }
+ }
+ *wp++ = CHAR;
+ *wp++ = c;
+ break;
+ }
+ /* FALLTHROUGH */
+ Sbase1: /* includes *(...|...) pattern (*+?@!) */
+ if (ctype(c, C_PATMO)) {
+ c2 = getsc();
+ if ((unsigned int)c2 == ORD('(' /*)*/)) {
+ *wp++ = OPAT;
+ *wp++ = c;
+ PUSH_STATE(SPATTERN);
+ break;
+ }
+ ungetsc(c2);
+ }
+ /* FALLTHROUGH */
+ Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */
+ switch (c) {
+ case ORD('\\'):
+ getsc_qchar:
+ if ((c = getsc())) {
+ /* trailing \ is lost */
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ break;
+ case ORD('\''):
+ open_ssquote_unless_heredoc:
+ if ((statep->ls_flags & LS_HEREDOC))
+ goto store_char;
+ *wp++ = OQUOTE;
+ ignore_backslash_newline++;
+ PUSH_STATE(SSQUOTE);
+ break;
+ case ORD('"'):
+ open_sdquote:
+ *wp++ = OQUOTE;
+ PUSH_STATE(SDQUOTE);
+ break;
+ case ORD('$'):
+ /*
+ * processing of dollar sign belongs into
+ * Subst, except for those which can open
+ * a string: $'…' and $"…"
+ */
+ subst_dollar_ex:
+ c = getsc();
+ switch (c) {
+ case ORD('"'):
+ goto open_sdquote;
+ case ORD('\''):
+ goto open_sequote;
+ default:
+ goto SubstS;
+ }
+ default:
+ goto Subst;
+ }
+ break;
+
+ Subst:
+ switch (c) {
+ case ORD('\\'):
+ c = getsc();
+ switch (c) {
+ case ORD('"'):
+ if ((statep->ls_flags & LS_HEREDOC))
+ goto heredocquote;
+ /* FALLTHROUGH */
+ case ORD('\\'):
+ case ORD('$'):
+ case ORD('`'):
+ store_qchar:
+ *wp++ = QCHAR;
+ *wp++ = c;
+ break;
+ default:
+ heredocquote:
+ Xcheck(ws, wp);
+ if (c) {
+ /* trailing \ is lost */
+ *wp++ = CHAR;
+ *wp++ = '\\';
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+ }
+ break;
+ case ORD('$'):
+ c = getsc();
+ SubstS:
+ if ((unsigned int)c == ORD('(' /*)*/)) {
+ c = getsc();
+ if ((unsigned int)c == ORD('(' /*)*/)) {
+ *wp++ = EXPRSUB;
+ PUSH_SRETRACE(SASPAREN);
+ /* unneeded? */
+ /*statep->ls_flags &= ~LS_HEREDOC;*/
+ statep->nparen = 2;
+ *retrace_info->xp++ = '(';
+ } else {
+ ungetsc(c);
+ subst_command:
+ c = COMSUB;
+ subst_command2:
+ sp = yyrecursive(c);
+ cz = strlen(sp) + 1;
+ XcheckN(ws, wp, cz);
+ *wp++ = c;
+ memcpy(wp, sp, cz);
+ wp += cz;
+ }
+ } else if ((unsigned int)c == ORD('{' /*}*/)) {
+ if ((unsigned int)(c = getsc()) == ORD('|')) {
+ /*
+ * non-subenvironment
+ * value substitution
+ */
+ c = VALSUB;
+ goto subst_command2;
+ } else if (ctype(c, C_IFSWS)) {
+ /*
+ * non-subenvironment
+ * "command" substitution
+ */
+ c = FUNSUB;
+ goto subst_command2;
+ }
+ ungetsc(c);
+ *wp++ = OSUBST;
+ *wp++ = '{' /*}*/;
+ wp = get_brace_var(&ws, wp);
+ c = getsc();
+ /* allow :# and :% (ksh88 compat) */
+ if ((unsigned int)c == ORD(':')) {
+ *wp++ = CHAR;
+ *wp++ = c;
+ c = getsc();
+ if ((unsigned int)c == ORD(':')) {
+ *wp++ = CHAR;
+ *wp++ = '0';
+ *wp++ = ADELIM;
+ *wp++ = ':';
+ PUSH_STATE(SBRACE);
+ /* perhaps unneeded? */
+ statep->ls_flags &= ~LS_HEREDOC;
+ PUSH_STATE(SADELIM);
+ statep->ls_adelim.delimiter = ':';
+ statep->ls_adelim.num = 1;
+ statep->nparen = 0;
+ break;
+ } else if (ctype(c, C_ALNUX | C_DOLAR | C_SPC) ||
+ c == '(' /*)*/) {
+ /* substring subst. */
+ if (c != ' ') {
+ *wp++ = CHAR;
+ *wp++ = ' ';
+ }
+ ungetsc(c);
+ PUSH_STATE(SBRACE);
+ /* perhaps unneeded? */
+ statep->ls_flags &= ~LS_HEREDOC;
+ PUSH_STATE(SADELIM);
+ statep->ls_adelim.delimiter = ':';
+ statep->ls_adelim.num = 2;
+ statep->nparen = 0;
+ break;
+ }
+ } else if (c == '/') {
+ c2 = ADELIM;
+ parse_adelim_slash:
+ *wp++ = CHAR;
+ *wp++ = c;
+ if ((unsigned int)(c = getsc()) == ORD('/')) {
+ *wp++ = c2;
+ *wp++ = c;
+ } else
+ ungetsc(c);
+ PUSH_STATE(SBRACE);
+ /* perhaps unneeded? */
+ statep->ls_flags &= ~LS_HEREDOC;
+ PUSH_STATE(SADELIM);
+ statep->ls_adelim.delimiter = '/';
+ statep->ls_adelim.num = 1;
+ statep->nparen = 0;
+ break;
+ } else if (c == '@') {
+ c2 = getsc();
+ ungetsc(c2);
+ if ((unsigned int)c2 == ORD('/')) {
+ c2 = CHAR;
+ goto parse_adelim_slash;
+ }
+ }
+ /*
+ * If this is a trim operation,
+ * treat (,|,) specially in STBRACE.
+ */
+ if (ctype(c, C_SUB2)) {
+ ungetsc(c);
+ if (Flag(FSH))
+ PUSH_STATE(STBRACEBOURNE);
+ else
+ PUSH_STATE(STBRACEKORN);
+ /* single-quotes-in-heredoc-trim */
+ statep->ls_flags &= ~LS_HEREDOC;
+ } else {
+ ungetsc(c);
+ if (state == SDQUOTE ||
+ state == SQBRACE)
+ PUSH_STATE(SQBRACE);
+ else
+ PUSH_STATE(SBRACE);
+ /* here no LS_HEREDOC removal */
+ /* single-quotes-in-heredoc-braces */
+ }
+ } else if (ctype(c, C_ALPHX)) {
+ *wp++ = OSUBST;
+ *wp++ = 'X';
+ do {
+ Xcheck(ws, wp);
+ *wp++ = c;
+ c = getsc();
+ } while (ctype(c, C_ALNUX));
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ *wp++ = 'X';
+ ungetsc(c);
+ } else if (ctype(c, C_VAR1 | C_DIGIT)) {
+ Xcheck(ws, wp);
+ *wp++ = OSUBST;
+ *wp++ = 'X';
+ *wp++ = c;
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ *wp++ = 'X';
+ } else {
+ *wp++ = CHAR;
+ *wp++ = '$';
+ ungetsc(c);
+ }
+ break;
+ case ORD('`'):
+ subst_gravis:
+ PUSH_STATE(SBQUOTE);
+ *wp++ = COMASUB;
+ /*
+ * We need to know whether we are within double
+ * quotes in order to translate \" to " within
+ * "…`…\"…`…" because, unlike for COMSUBs, the
+ * outer double quoteing changes the backslash
+ * meaning for the inside. For more details:
+ * http://austingroupbugs.net/view.php?id=1015
+ */
+ statep->ls_bool = false;
+ s2 = statep;
+ base = state_info.base;
+ while (/* CONSTCOND */ 1) {
+ for (; s2 != base; s2--) {
+ if (s2->type == SDQUOTE) {
+ statep->ls_bool = true;
+ break;
+ }
+ }
+ if (s2 != base)
+ break;
+ if (!(s2 = s2->ls_base))
+ break;
+ base = s2-- - STATE_BSIZE;
+ }
+ break;
+ case QCHAR:
+ if (cf & LQCHAR) {
+ *wp++ = QCHAR;
+ *wp++ = getsc();
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ store_char:
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SEQUOTE:
+ if ((unsigned int)c == ORD('\'')) {
+ POP_STATE();
+ *wp++ = CQUOTE;
+ ignore_backslash_newline--;
+ } else if ((unsigned int)c == ORD('\\')) {
+ if ((c2 = unbksl(true, getsc_i, ungetsc)) == -1)
+ c2 = getsc();
+ if (c2 == 0)
+ statep->ls_bool = true;
+ if (!statep->ls_bool) {
+ char ts[4];
+
+ if ((unsigned int)c2 < 0x100) {
+ *wp++ = QCHAR;
+ *wp++ = c2;
+ } else {
+ cz = utf_wctomb(ts, c2 - 0x100);
+ ts[cz] = 0;
+ cz = 0;
+ do {
+ *wp++ = QCHAR;
+ *wp++ = ts[cz];
+ } while (ts[++cz]);
+ }
+ }
+ } else if (!statep->ls_bool) {
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SSQUOTE:
+ if ((unsigned int)c == ORD('\'')) {
+ POP_STATE();
+ if ((statep->ls_flags & LS_HEREDOC) ||
+ state == SQBRACE)
+ goto store_char;
+ *wp++ = CQUOTE;
+ ignore_backslash_newline--;
+ } else {
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SDQUOTE:
+ if ((unsigned int)c == ORD('"')) {
+ POP_STATE();
+ *wp++ = CQUOTE;
+ } else
+ goto Subst;
+ break;
+
+ /* $(( ... )) */
+ case SASPAREN:
+ if ((unsigned int)c == ORD('('))
+ statep->nparen++;
+ else if ((unsigned int)c == ORD(')')) {
+ statep->nparen--;
+ if (statep->nparen == 1) {
+ /* end of EXPRSUB */
+ POP_SRETRACE();
+
+ if ((unsigned int)(c2 = getsc()) == ORD(/*(*/ ')')) {
+ cz = strlen(sp) - 2;
+ XcheckN(ws, wp, cz);
+ memcpy(wp, sp + 1, cz);
+ wp += cz;
+ afree(sp, ATEMP);
+ *wp++ = '\0';
+ break;
+ } else {
+ Source *s;
+
+ ungetsc(c2);
+ /*
+ * mismatched parenthesis -
+ * assume we were really
+ * parsing a $(...) expression
+ */
+ --wp;
+ s = pushs(SREREAD,
+ source->areap);
+ s->start = s->str =
+ s->u.freeme = sp;
+ s->next = source;
+ source = s;
+ goto subst_command;
+ }
+ }
+ }
+ /* reuse existing state machine */
+ goto Sbase2;
+
+ case SQBRACE:
+ if ((unsigned int)c == ORD('\\')) {
+ /*
+ * perform POSIX "quote removal" if the back-
+ * slash is "special", i.e. same cases as the
+ * {case '\\':} in Subst: plus closing brace;
+ * in mksh code "quote removal" on '\c' means
+ * write QCHAR+c, otherwise CHAR+\+CHAR+c are
+ * emitted (in heredocquote:)
+ */
+ if ((unsigned int)(c = getsc()) == ORD('"') ||
+ (unsigned int)c == ORD('\\') ||
+ ctype(c, C_DOLAR | C_GRAVE) ||
+ (unsigned int)c == ORD(/*{*/ '}'))
+ goto store_qchar;
+ goto heredocquote;
+ }
+ goto common_SQBRACE;
+
+ case SBRACE:
+ if ((unsigned int)c == ORD('\''))
+ goto open_ssquote_unless_heredoc;
+ else if ((unsigned int)c == ORD('\\'))
+ goto getsc_qchar;
+ common_SQBRACE:
+ if ((unsigned int)c == ORD('"'))
+ goto open_sdquote;
+ else if ((unsigned int)c == ORD('$'))
+ goto subst_dollar_ex;
+ else if ((unsigned int)c == ORD('`'))
+ goto subst_gravis;
+ else if ((unsigned int)c != ORD(/*{*/ '}'))
+ goto store_char;
+ POP_STATE();
+ *wp++ = CSUBST;
+ *wp++ = /*{*/ '}';
+ break;
+
+ /* Same as SBASE, except (,|,) treated specially */
+ case STBRACEKORN:
+ if ((unsigned int)c == ORD('|'))
+ *wp++ = SPAT;
+ else if ((unsigned int)c == ORD('(')) {
+ *wp++ = OPAT;
+ /* simile for @ */
+ *wp++ = ' ';
+ PUSH_STATE(SPATTERN);
+ } else /* FALLTHROUGH */
+ case STBRACEBOURNE:
+ if ((unsigned int)c == ORD(/*{*/ '}')) {
+ POP_STATE();
+ *wp++ = CSUBST;
+ *wp++ = /*{*/ '}';
+ } else
+ goto Sbase1;
+ break;
+
+ case SBQUOTE:
+ if ((unsigned int)c == ORD('`')) {
+ *wp++ = 0;
+ POP_STATE();
+ } else if ((unsigned int)c == ORD('\\')) {
+ switch (c = getsc()) {
+ case 0:
+ /* trailing \ is lost */
+ break;
+ case ORD('$'):
+ case ORD('`'):
+ case ORD('\\'):
+ *wp++ = c;
+ break;
+ case ORD('"'):
+ if (statep->ls_bool) {
+ *wp++ = c;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ *wp++ = '\\';
+ *wp++ = c;
+ break;
+ }
+ } else
+ *wp++ = c;
+ break;
+
+ /* ONEWORD */
+ case SWORD:
+ goto Subst;
+
+ /* LETEXPR: (( ... )) */
+ case SLETPAREN:
+ if ((unsigned int)c == ORD(/*(*/ ')')) {
+ if (statep->nparen > 0)
+ --statep->nparen;
+ else if ((unsigned int)(c2 = getsc()) == ORD(/*(*/ ')')) {
+ c = 0;
+ *wp++ = CQUOTE;
+ goto Done;
+ } else {
+ Source *s;
+
+ ungetsc(c2);
+ ungetsc(c);
+ /*
+ * mismatched parenthesis -
+ * assume we were really
+ * parsing a (...) expression
+ */
+ *wp = EOS;
+ sp = Xstring(ws, wp);
+ dp = wdstrip(sp + 1, WDS_TPUTS);
+ s = pushs(SREREAD, source->areap);
+ s->start = s->str = s->u.freeme = dp;
+ s->next = source;
+ source = s;
+ ungetsc('(' /*)*/);
+ return (ORD('(' /*)*/));
+ }
+ } else if ((unsigned int)c == ORD('('))
+ /*
+ * parentheses inside quotes and
+ * backslashes are lost, but AT&T ksh
+ * doesn't count them either
+ */
+ ++statep->nparen;
+ goto Sbase2;
+
+ /* << or <<- delimiter */
+ case SHEREDELIM:
+ /*
+ * here delimiters need a special case since
+ * $ and `...` are not to be treated specially
+ */
+ switch (c) {
+ case ORD('\\'):
+ if ((c = getsc())) {
+ /* trailing \ is lost */
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ break;
+ case ORD('\''):
+ goto open_ssquote_unless_heredoc;
+ case ORD('$'):
+ if ((unsigned int)(c2 = getsc()) == ORD('\'')) {
+ open_sequote:
+ *wp++ = OQUOTE;
+ ignore_backslash_newline++;
+ PUSH_STATE(SEQUOTE);
+ statep->ls_bool = false;
+ break;
+ } else if ((unsigned int)c2 == ORD('"')) {
+ /* FALLTHROUGH */
+ case ORD('"'):
+ PUSH_SRETRACE(SHEREDQUOTE);
+ break;
+ }
+ ungetsc(c2);
+ /* FALLTHROUGH */
+ default:
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ /* " in << or <<- delimiter */
+ case SHEREDQUOTE:
+ if ((unsigned int)c != ORD('"'))
+ goto Subst;
+ POP_SRETRACE();
+ dp = strnul(sp) - 1;
+ /* remove the trailing double quote */
+ *dp = '\0';
+ /* store the quoted string */
+ *wp++ = OQUOTE;
+ XcheckN(ws, wp, (dp - sp) * 2);
+ dp = sp;
+ while ((c = *dp++)) {
+ if (c == '\\') {
+ switch ((c = *dp++)) {
+ case ORD('\\'):
+ case ORD('"'):
+ case ORD('$'):
+ case ORD('`'):
+ break;
+ default:
+ *wp++ = CHAR;
+ *wp++ = '\\';
+ break;
+ }
+ }
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ afree(sp, ATEMP);
+ *wp++ = CQUOTE;
+ state = statep->type = SHEREDELIM;
+ break;
+
+ /* in *(...|...) pattern (*+?@!) */
+ case SPATTERN:
+ if ((unsigned int)c == ORD(/*(*/ ')')) {
+ *wp++ = CPAT;
+ POP_STATE();
+ } else if ((unsigned int)c == ORD('|')) {
+ *wp++ = SPAT;
+ } else if ((unsigned int)c == ORD('(')) {
+ *wp++ = OPAT;
+ /* simile for @ */
+ *wp++ = ' ';
+ PUSH_STATE(SPATTERN);
+ } else
+ goto Sbase1;
+ break;
+ }
+ }
+ Done:
+ Xcheck(ws, wp);
+ if (statep != &states[1])
+ /* XXX figure out what is missing */
+ yyerror("no closing quote");
+
+ /* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+ if (state == SHEREDELIM)
+ state = SBASE;
+
+ dp = Xstring(ws, wp);
+ if (state == SBASE && (
+ (c == '&' && !Flag(FSH) && !Flag(FPOSIX)) ||
+ ctype(c, C_ANGLE)) && ((c2 = Xlength(ws, wp)) == 0 ||
+ (c2 == 2 && dp[0] == CHAR && ctype(dp[1], C_DIGIT)))) {
+ struct ioword *iop = alloc(sizeof(struct ioword), ATEMP);
+
+ iop->unit = c2 == 2 ? ksh_numdig(dp[1]) : c == '<' ? 0 : 1;
+
+ if (c == '&') {
+ if ((unsigned int)(c2 = getsc()) != ORD('>')) {
+ ungetsc(c2);
+ goto no_iop;
+ }
+ c = c2;
+ iop->ioflag = IOBASH;
+ } else
+ iop->ioflag = 0;
+
+ c2 = getsc();
+ /* <<, >>, <> are ok, >< is not */
+ if (c == c2 || ((unsigned int)c == ORD('<') &&
+ (unsigned int)c2 == ORD('>'))) {
+ iop->ioflag |= c == c2 ?
+ ((unsigned int)c == ORD('>') ? IOCAT : IOHERE) : IORDWR;
+ if (iop->ioflag == IOHERE) {
+ if ((unsigned int)(c2 = getsc()) == ORD('-'))
+ iop->ioflag |= IOSKIP;
+ else if ((unsigned int)c2 == ORD('<'))
+ iop->ioflag |= IOHERESTR;
+ else
+ ungetsc(c2);
+ }
+ } else if ((unsigned int)c2 == ORD('&'))
+ iop->ioflag |= IODUP | ((unsigned int)c == ORD('<') ? IORDUP : 0);
+ else {
+ iop->ioflag |= (unsigned int)c == ORD('>') ? IOWRITE : IOREAD;
+ if ((unsigned int)c == ORD('>') && (unsigned int)c2 == ORD('|'))
+ iop->ioflag |= IOCLOB;
+ else
+ ungetsc(c2);
+ }
+
+ iop->ioname = NULL;
+ iop->delim = NULL;
+ iop->heredoc = NULL;
+ /* free word */
+ Xfree(ws, wp);
+ yylval.iop = iop;
+ return (REDIR);
+ no_iop:
+ afree(iop, ATEMP);
+ }
+
+ if (wp == dp && state == SBASE) {
+ /* free word */
+ Xfree(ws, wp);
+ /* no word, process LEX1 character */
+ if (((unsigned int)c == ORD('|')) ||
+ ((unsigned int)c == ORD('&')) ||
+ ((unsigned int)c == ORD(';')) ||
+ ((unsigned int)c == ORD('(' /*)*/))) {
+ if ((c2 = getsc()) == c)
+ c = ((unsigned int)c == ORD(';')) ? BREAK :
+ ((unsigned int)c == ORD('|')) ? LOGOR :
+ ((unsigned int)c == ORD('&')) ? LOGAND :
+ /* (unsigned int)c == ORD('(' )) */ MDPAREN;
+ else if ((unsigned int)c == ORD('|') && (unsigned int)c2 == ORD('&'))
+ c = COPROC;
+ else if ((unsigned int)c == ORD(';') && (unsigned int)c2 == ORD('|'))
+ c = BRKEV;
+ else if ((unsigned int)c == ORD(';') && (unsigned int)c2 == ORD('&'))
+ c = BRKFT;
+ else
+ ungetsc(c2);
+#ifndef MKSH_SMALL
+ if (c == BREAK) {
+ if ((unsigned int)(c2 = getsc()) == ORD('&'))
+ c = BRKEV;
+ else
+ ungetsc(c2);
+ }
+#endif
+ } else if ((unsigned int)c == ORD('\n')) {
+ if (cf & HEREDELIM)
+ ungetsc(c);
+ else {
+ gethere();
+ if (cf & CONTIN)
+ goto Again;
+ }
+ } else if (c == '\0' && !(cf & HEREDELIM)) {
+ struct ioword **p = heres;
+
+ while (p < herep)
+ if ((*p)->ioflag & IOHERESTR)
+ ++p;
+ else
+ /* ksh -c 'cat <<EOF' can cause this */
+ yyerror(Tf_heredoc,
+ evalstr((*p)->delim, 0));
+ }
+ return (c);
+ }
+
+ /* terminate word */
+ *wp++ = EOS;
+ yylval.cp = Xclose(ws, wp);
+ if (state == SWORD || state == SLETPAREN
+ /* XXX ONEWORD? */)
+ return (LWORD);
+
+ /* unget terminator */
+ ungetsc(c);
+
+ /*
+ * note: the alias-vs-function code below depends on several
+ * interna: starting from here, source->str is not modified;
+ * the way getsc() and ungetsc() operate; etc.
+ */
+
+ /* copy word to unprefixed string ident */
+ sp = yylval.cp;
+ dp = ident;
+ while ((dp - ident) < IDENT && (c = *sp++) == CHAR)
+ *dp++ = *sp++;
+ if (c != EOS)
+ /* word is not unquoted, or space ran out */
+ dp = ident;
+ /* make sure the ident array stays NUL padded */
+ memset(dp, 0, (ident + IDENT) - dp + 1);
+
+ if (*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 ||
+ (unsigned int)p->val.i == ORD(/*{*/ '}'))) {
+ afree(yylval.cp, ATEMP);
+ return (p->val.i);
+ }
+ if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) &&
+ (p->flag & ISSET)) {
+ /*
+ * this still points to the same character as the
+ * ungetsc'd terminator from above
+ */
+ const char *cp = source->str;
+
+ /* prefer POSIX but not Korn functions over aliases */
+ while (ctype(*cp, C_BLANK))
+ /*
+ * this is like getsc() without skipping
+ * over Source boundaries (including not
+ * parsing ungetsc'd characters that got
+ * pushed into an SREREAD) which is what
+ * we want here anyway: find out whether
+ * the alias name is followed by a POSIX
+ * function definition
+ */
+ ++cp;
+ /* prefer functions over aliases */
+ if (cp[0] != '(' || cp[1] != ')') {
+ Source *s = source;
+
+ while (s && (s->flags & SF_HASALIAS))
+ if (s->u.tblp == p)
+ return (LWORD);
+ else
+ s = s->next;
+ /* push alias expansion */
+ s = pushs(SALIAS, source->areap);
+ s->start = s->str = p->val.s;
+ s->u.tblp = p;
+ s->flags |= SF_HASALIAS;
+ s->line = source->line;
+ s->next = source;
+ if (source->type == SEOF) {
+ /* prevent infinite recursion at EOS */
+ source->u.tblp = p;
+ source->flags |= SF_HASALIAS;
+ }
+ source = s;
+ afree(yylval.cp, ATEMP);
+ goto Again;
+ }
+ }
+ } else if (*ident == '\0') {
+ /* retain typeset et al. even when quoted */
+ struct tbl *tt = get_builtin((dp = wdstrip(yylval.cp, 0)));
+ uint32_t flag = tt ? tt->flag : 0;
+
+ if (flag & (DECL_UTIL | DECL_FWDR))
+ strlcpy(ident, dp, sizeof(ident));
+ afree(dp, ATEMP);
+ }
+
+ return (LWORD);
+}
+
+static void
+gethere(void)
+{
+ struct ioword **p;
+
+ for (p = heres; p < herep; p++)
+ if (!((*p)->ioflag & IOHERESTR))
+ readhere(*p);
+ herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(struct ioword *iop)
+{
+ int c;
+ const char *eof, *eofp;
+ XString xs;
+ char *xp;
+ size_t xpos;
+
+ eof = evalstr(iop->delim, 0);
+
+ if (!(iop->ioflag & IOEVAL))
+ ignore_backslash_newline++;
+
+ Xinit(xs, xp, 256, ATEMP);
+
+ heredoc_read_line:
+ /* beginning of line */
+ eofp = eof;
+ xpos = Xsavepos(xs, xp);
+ if (iop->ioflag & 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 ORD(/*(*/ ')'):
+ if (!subshell_nesting_type)
+ /*-
+ * not allowed outside $(...) or (...)
+ * => mismatch
+ */
+ break;
+ /* allow $(...) or (...) to close here */
+ ungetsc(/*(*/ ')');
+ /* FALLTHROUGH */
+ case 0:
+ /*
+ * Allow EOF here to commands without trailing
+ * newlines (mksh -c '...') will work as well.
+ */
+ case ORD('\n'):
+ /* Newline terminates here document marker */
+ goto heredoc_found_terminator;
+ }
+ } else if ((unsigned int)c == ord(*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(Tf_heredoc, 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);
+
+ if (!(iop->ioflag & IOEVAL))
+ ignore_backslash_newline--;
+}
+
+void
+yyerror(const char *fmt, ...)
+{
+ va_list va;
+
+ /* pop aliases and re-reads */
+ while (source->type == SALIAS || source->type == SREREAD)
+ source = source->next;
+ /* zap pending input */
+ source->str = null;
+
+ error_prefix(true);
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ shf_putc('\n', shl_out);
+ va_end(va);
+ errorfz();
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(int type, Area *areap)
+{
+ Source *s;
+
+ s = alloc(sizeof(Source), areap);
+ memset(s, 0, sizeof(Source));
+ s->type = type;
+ s->str = null;
+ s->areap = areap;
+ if (type == SFILE || type == SSTDIN)
+ XinitN(s->xs, 256, s->areap);
+ return (s);
+}
+
+static int
+getsc_uu(void)
+{
+ Source *s = source;
+ int c;
+
+ while ((c = ord(*s->str++)) == 0) {
+ /* return 0 for EOF by default */
+ s->str = NULL;
+ switch (s->type) {
+ case SEOF:
+ s->str = null;
+ return (0);
+
+ case SSTDIN:
+ case SFILE:
+ getsc_line(s);
+ break;
+
+ case SWSTR:
+ break;
+
+ case SSTRING:
+ case SSTRINGCMDLINE:
+ break;
+
+ case SWORDS:
+ s->start = s->str = *s->u.strv++;
+ s->type = SWORDSEP;
+ break;
+
+ case SWORDSEP:
+ if (*s->u.strv == NULL) {
+ s->start = s->str = "\n";
+ s->type = SEOF;
+ } else {
+ s->start = s->str = T1space;
+ s->type = SWORDS;
+ }
+ break;
+
+ case SALIAS:
+ if (s->flags & SF_ALIASEND) {
+ /* pass on an unused SF_ALIAS flag */
+ source = s->next;
+ source->flags |= s->flags & SF_ALIAS;
+ s = source;
+ } else if (*s->u.tblp->val.s &&
+ ctype((c = strnul(s->u.tblp->val.s)[-1]), C_SPACE)) {
+ /* 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
+ * 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.
+ */
+ /* pop source stack */
+ source = s->next;
+ source->flags |= s->flags & SF_ALIAS;
+ c = getsc_uu();
+ if (c) {
+ s->flags |= SF_ALIASEND;
+ s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+ s->start = s->str = s->ugbuf;
+ s->next = source;
+ source = s;
+ } else {
+ s = source;
+ /* avoid reading EOF twice */
+ s->str = NULL;
+ break;
+ }
+ }
+ continue;
+
+ case SREREAD:
+ if (s->start != s->ugbuf)
+ /* yuck */
+ afree(s->u.freeme, ATEMP);
+ source = s = s->next;
+ continue;
+ }
+ if (s->str == NULL) {
+ s->type = SEOF;
+ s->start = s->str = null;
+ return ('\0');
+ }
+ if (s->flags & SF_ECHO) {
+ shf_puts(s->str, shl_out);
+ shf_flush(shl_out);
+ }
+ }
+ return (c);
+}
+
+static void
+getsc_line(Source *s)
+{
+ char *xp = Xstring(s->xs, xp), *cp;
+ bool interactive = Flag(FTALKING) && s->type == SSTDIN;
+ bool have_tty = interactive && (s->flags & SF_TTY) && tty_hasstate;
+
+ /* Done here to ensure nothing odd happens when a timeout occurs */
+ XcheckN(s->xs, xp, LINE);
+ *xp = '\0';
+ s->start = s->str = xp;
+
+ if (have_tty && ksh_tmout) {
+ ksh_tmout_state = TMOUT_READING;
+ alarm(ksh_tmout);
+ }
+ if (interactive) {
+ if (cur_prompt == PS1)
+ histsave(&s->line, NULL, HIST_FLUSH, true);
+ change_winsz();
+ }
+#ifndef MKSH_NO_CMDLINE_EDITING
+ if (have_tty && (
+#if !MKSH_S_NOVI
+ Flag(FVI) ||
+#endif
+ Flag(FEMACS) || Flag(FGMACS))) {
+ int nread;
+
+ nread = x_read(xp);
+ if (nread < 0)
+ /* read error */
+ nread = 0;
+ xp[nread] = '\0';
+ xp += nread;
+ } else
+#endif
+ {
+ if (interactive)
+ pprompt(prompt, 0);
+ else
+ s->line++;
+
+ while (/* CONSTCOND */ 1) {
+ char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
+
+ if (!p && shf_error(s->u.shf) &&
+ shf_errno(s->u.shf) == EINTR) {
+ shf_clearerr(s->u.shf);
+ if (trap)
+ runtraps(0);
+ continue;
+ }
+ if (!p || (xp = p, xp[-1] == '\n'))
+ break;
+ /* double buffer size */
+ /* move past NUL so doubling works... */
+ xp++;
+ XcheckN(s->xs, xp, Xlength(s->xs, xp));
+ /* ...and move back again */
+ xp--;
+ }
+ /*
+ * 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
+ */
+ if (s->type == SSTDIN)
+ shf_flush(s->u.shf);
+ }
+ /*
+ * XXX: temporary kludge to restore source after a
+ * trap may have been executed.
+ */
+ source = s;
+ if (have_tty && ksh_tmout) {
+ ksh_tmout_state = TMOUT_EXECUTING;
+ alarm(0);
+ }
+ cp = Xstring(s->xs, xp);
+ rndpush(cp);
+ s->start = s->str = cp;
+ strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp));
+ /* Note: if input is all nulls, this is not eof */
+ if (Xlength(s->xs, xp) == 0) {
+ /* EOF */
+ if (s->type == SFILE)
+ shf_fdclose(s->u.shf);
+ s->str = NULL;
+ } else if (interactive && *s->str) {
+ if (cur_prompt != PS1)
+ histsave(&s->line, s->str, HIST_APPEND, true);
+ else if (!ctype(*s->str, C_IFS | C_IFSWS))
+ histsave(&s->line, s->str, HIST_QUEUE, true);
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+ else
+ goto check_for_sole_return;
+ } else if (interactive && cur_prompt == PS1) {
+ check_for_sole_return:
+ cp = Xstring(s->xs, xp);
+ while (ctype(*cp, C_IFSWS))
+ ++cp;
+ if (!*cp) {
+ histsave(&s->line, NULL, HIST_FLUSH, true);
+ histsync();
+ }
+#endif
+ }
+ if (interactive)
+ set_prompt(PS2, NULL);
+}
+
+void
+set_prompt(int to, Source *s)
+{
+ cur_prompt = (uint8_t)to;
+
+ switch (to) {
+ /* 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.
+ */
+ {
+ struct shf *shf;
+ char * volatile ps1;
+ Area *saved_atemp;
+ int saved_lineno;
+
+ ps1 = str_val(global("PS1"));
+ shf = shf_sopen(NULL, strlen(ps1) * 2,
+ SHF_WR | SHF_DYNAMIC, NULL);
+ while (*ps1)
+ if (*ps1 != '!' || *++ps1 == '!')
+ shf_putchar(*ps1++, shf);
+ else
+ shf_fprintf(shf, Tf_lu, s ?
+ (unsigned long)s->line + 1 : 0UL);
+ ps1 = shf_sclose(shf);
+ saved_lineno = current_lineno;
+ if (s)
+ current_lineno = s->line + 1;
+ saved_atemp = ATEMP;
+ newenv(E_ERRH);
+ if (kshsetjmp(e->jbuf)) {
+ prompt = safe_prompt;
+ /*
+ * 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
+ * exits.
+ */
+ } else {
+ char *cp = substitute(ps1, 0);
+ strdupx(prompt, cp, saved_atemp);
+ }
+ current_lineno = saved_lineno;
+ quitenv(NULL);
+ }
+ break;
+ /* command continuation */
+ case PS2:
+ prompt = str_val(global("PS2"));
+ break;
+ }
+}
+
+int
+pprompt(const char *cp, int ntruncate)
+{
+ char delimiter = 0;
+ bool doprint = (ntruncate != -1);
+ bool indelimit = false;
+ int columns = 0, lines = 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
+ */
+ if (*cp && cp[1] == '\r') {
+ delimiter = *cp;
+ cp += 2;
+ }
+ for (; *cp; cp++) {
+ if (indelimit && *cp != delimiter)
+ ;
+ else if (ctype(*cp, C_CR | C_LF)) {
+ lines += columns / x_cols + ((*cp == '\n') ? 1 : 0);
+ columns = 0;
+ } else if (*cp == '\t') {
+ columns = (columns | 7) + 1;
+ } else if (*cp == '\b') {
+ if (columns > 0)
+ columns--;
+ } else if (*cp == delimiter)
+ indelimit = !indelimit;
+ else if (UTFMODE && (rtt2asc(*cp) > 0x7F)) {
+ const char *cp2;
+ columns += utf_widthadj(cp, &cp2);
+ if (doprint && (indelimit ||
+ (ntruncate < (x_cols * lines + columns))))
+ shf_write(cp, cp2 - cp, shl_out);
+ cp = cp2 - /* loop increment */ 1;
+ continue;
+ } else
+ columns++;
+ if (doprint && (*cp != delimiter) &&
+ (indelimit || (ntruncate < (x_cols * lines + columns))))
+ shf_putc(*cp, shl_out);
+ }
+ if (doprint)
+ shf_flush(shl_out);
+ return (x_cols * lines + columns);
+}
+
+/*
+ * 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_PERCENT, PS_SAW_HASH, PS_SAW_BANG,
+ PS_IDENT, PS_NUMBER, PS_VAR1
+ } state = PS_INITIAL;
+
+ while (/* CONSTCOND */ 1) {
+ c = getsc();
+ /* State machine to figure out where the variable part ends. */
+ switch (state) {
+ case PS_SAW_HASH:
+ if (ctype(c, C_VAR1)) {
+ char c2;
+
+ c2 = getsc();
+ ungetsc(c2);
+ if (ord(c2) != ORD(/*{*/ '}')) {
+ ungetsc(c);
+ goto out;
+ }
+ }
+ goto ps_common;
+ case PS_SAW_BANG:
+ switch (ord(c)) {
+ case ORD('@'):
+ case ORD('#'):
+ case ORD('-'):
+ case ORD('?'):
+ goto out;
+ }
+ goto ps_common;
+ case PS_INITIAL:
+ switch (ord(c)) {
+ case ORD('%'):
+ state = PS_SAW_PERCENT;
+ goto next;
+ case ORD('#'):
+ state = PS_SAW_HASH;
+ goto next;
+ case ORD('!'):
+ state = PS_SAW_BANG;
+ goto next;
+ }
+ /* FALLTHROUGH */
+ case PS_SAW_PERCENT:
+ ps_common:
+ if (ctype(c, C_ALPHX))
+ state = PS_IDENT;
+ else if (ctype(c, C_DIGIT))
+ state = PS_NUMBER;
+ else if (ctype(c, C_VAR1))
+ state = PS_VAR1;
+ else
+ goto out;
+ break;
+ case PS_IDENT:
+ if (!ctype(c, C_ALNUX)) {
+ if (ord(c) == ORD('[')) {
+ char *tmp, *p;
+
+ if (!arraysub(&tmp))
+ yyerror("missing ]");
+ *wp++ = c;
+ p = tmp;
+ while (*p) {
+ Xcheck(*wsp, wp);
+ *wp++ = *p++;
+ }
+ afree(tmp, ATEMP);
+ /* the ] */
+ c = getsc();
+ }
+ goto out;
+ }
+ next:
+ break;
+ case PS_NUMBER:
+ if (!ctype(c, C_DIGIT))
+ goto out;
+ break;
+ case PS_VAR1:
+ goto out;
+ }
+ Xcheck(*wsp, wp);
+ *wp++ = c;
+ }
+ out:
+ /* end of variable part */
+ *wp++ = '\0';
+ ungetsc(c);
+ return (wp);
+}
+
+/*
+ * Save an array subscript - returns true if matching bracket found, false
+ * if eof or newline was found.
+ * (Returned string double null terminated)
+ */
+static bool
+arraysub(char **strp)
+{
+ XString ws;
+ char *wp, c;
+ /* we are just past the initial [ */
+ unsigned int depth = 1;
+
+ Xinit(ws, wp, 32, ATEMP);
+
+ do {
+ c = getsc();
+ Xcheck(ws, wp);
+ *wp++ = c;
+ if (ord(c) == ORD('['))
+ depth++;
+ else if (ord(c) == ORD(']'))
+ depth--;
+ } while (depth > 0 && c && c != '\n');
+
+ *wp++ = '\0';
+ *strp = Xclose(ws, wp);
+
+ return (tobool(depth == 0));
+}
+
+/* Unget a char: handles case when we are already at the start of the buffer */
+static void
+ungetsc(int c)
+{
+ struct sretrace_info *rp = retrace_info;
+
+ if (backslash_skip)
+ backslash_skip--;
+ /* Don't unget EOF... */
+ if (source->str == null && c == '\0')
+ return;
+ while (rp) {
+ if (Xlength(rp->xs, rp->xp))
+ rp->xp--;
+ rp = rp->next;
+ }
+ ungetsc_i(c);
+}
+static void
+ungetsc_i(int c)
+{
+ if (source->str > source->start)
+ source->str--;
+ else {
+ Source *s;
+
+ s = pushs(SREREAD, source->areap);
+ s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+ s->start = s->str = s->ugbuf;
+ s->next = source;
+ source = s;
+ }
+}
+
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn(void)
+{
+ int c, c2;
+
+ if (ignore_backslash_newline)
+ return (o_getsc_u());
+
+ if (backslash_skip == 1) {
+ backslash_skip = 2;
+ return (o_getsc_u());
+ }
+
+ backslash_skip = 0;
+
+ while (/* CONSTCOND */ 1) {
+ c = o_getsc_u();
+ if (c == '\\') {
+ if ((c2 = o_getsc_u()) == '\n')
+ /* ignore the \newline; get the next char... */
+ continue;
+ ungetsc_i(c2);
+ backslash_skip = 1;
+ }
+ return (c);
+ }
+}
+
+void
+yyskiputf8bom(void)
+{
+ int c;
+
+ if (rtt2asc((c = o_getsc_u())) != 0xEF) {
+ ungetsc_i(c);
+ return;
+ }
+ if (rtt2asc((c = o_getsc_u())) != 0xBB) {
+ ungetsc_i(c);
+ ungetsc_i(asc2rtt(0xEF));
+ return;
+ }
+ if (rtt2asc((c = o_getsc_u())) != 0xBF) {
+ ungetsc_i(c);
+ ungetsc_i(asc2rtt(0xBB));
+ ungetsc_i(asc2rtt(0xEF));
+ return;
+ }
+ UTFMODE |= 8;
+}
+
+static Lex_state *
+push_state_i(State_info *si, Lex_state *old_end)
+{
+ Lex_state *news = alloc2(STATE_BSIZE, sizeof(Lex_state), ATEMP);
+
+ news[0].ls_base = old_end;
+ si->base = &news[0];
+ si->end = &news[STATE_BSIZE];
+ return (&news[1]);
+}
+
+static Lex_state *
+pop_state_i(State_info *si, Lex_state *old_end)
+{
+ Lex_state *old_base = si->base;
+
+ si->base = old_end->ls_base - STATE_BSIZE;
+ si->end = old_end->ls_base;
+
+ afree(old_base, ATEMP);
+
+ return (si->base + STATE_BSIZE - 1);
+}
diff --git a/shells/mksh/files/lksh.1 b/shells/mksh/files/lksh.1
new file mode 100644
index 00000000000..0d6b2269f4d
--- /dev/null
+++ b/shells/mksh/files/lksh.1
@@ -0,0 +1,354 @@
+.\" $MirOS: src/bin/mksh/lksh.1,v 1.25 2018/12/25 19:38:08 tg Exp $
+.\"-
+.\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017, 2018
+.\" mirabilos <m@mirbsd.org>
+.\"
+.\" Provided that these terms and disclaimer and all copyright notices
+.\" are retained or reproduced in an accompanying document, permission
+.\" is granted to deal in this work without restriction, including unâ€
+.\" limited rights to use, publicly perform, distribute, sell, modify,
+.\" merge, give away, or sublicence.
+.\"
+.\" This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+.\" the utmost extent permitted by applicable law, neither express nor
+.\" implied; without malicious intent or gross negligence. In no event
+.\" may a licensor, author or contributor be held liable for indirect,
+.\" direct, other damage, loss, or other issues arising in any way out
+.\" of dealing in the work, even if advised of the possibility of such
+.\" damage or existence of a defect, except proven that it results out
+.\" of said person’s immediate fault when using the work as intended.
+.\"-
+.\" 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
+.\" * <>| are problematic, so redefine and use \*(Lt\*(Gt\*(Ba
+.\" Also make sure to use \& *before* a punctuation char that is to not
+.\" be interpreted as punctuation, and especially with two-letter words
+.\" but also (after) a period that does not end a sentence (“e.g.\&”).
+.\" The section after the "doc" macropackage has been loaded contains
+.\" additional code to convene between the UCB mdoc macropackage (and
+.\" its variant as BSD mdoc in groff) and the GNU mdoc macropackage.
+.\"
+.ie \n(.g \{\
+. if \*[.T]ascii .tr \-\N'45'
+. if \*[.T]latin1 .tr \-\N'45'
+. if \*[.T]utf8 .tr \-\N'45'
+. ds <= \[<=]
+. ds >= \[>=]
+. ds Rq \[rq]
+. ds Lq \[lq]
+. ds sL \(aq
+. ds sR \(aq
+. if \*[.T]utf8 .ds sL `
+. if \*[.T]ps .ds sL `
+. if \*[.T]utf8 .ds sR '
+. if \*[.T]ps .ds sR '
+. ds aq \(aq
+. ds TI \(ti
+. ds ha \(ha
+. ds en \(en
+.\}
+.el \{\
+. ds aq '
+. ds TI ~
+. ds ha ^
+. ds en \(em
+.\}
+.\"
+.\" Implement .Dd with the Mdocdate RCS keyword
+.\"
+.rn Dd xD
+.de Dd
+.ie \\$1$Mdocdate: \{\
+. xD \\$2 \\$3, \\$4
+.\}
+.el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
+..
+.\"
+.\" .Dd must come before definition of .Mx, because when called
+.\" with -mandoc, it might implement .Mx itself, but we want to
+.\" use our own definition. And .Dd must come *first*, always.
+.\"
+.Dd $Mdocdate: December 25 2018 $
+.\"
+.\" Check which macro package we use, and do other -mdoc setup.
+.\"
+.ie \n(.g \{\
+. if \*[.T]utf8 .tr \[la]\*(Lt
+. if \*[.T]utf8 .tr \[ra]\*(Gt
+. ie d volume-ds-1 .ds tT gnu
+. el .ie d doc-volume-ds-1 .ds tT gnp
+. 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]\%MirBSD\*[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 .ie "\*(tT"gnp" \{\
+. eo
+. de Mx
+. nr doc-curr-font \n[.f]
+. nr doc-curr-size \n[.ps]
+. ds doc-str-Mx \f[\n[doc-curr-font]]\s[\n[doc-curr-size]u]
+. ds doc-str-Mx1 \*[doc-Tn-font-size]\%MirBSD\*[doc-str-Mx]
+. if !\n[doc-arg-limit] \
+. if \n[.$] \{\
+. ds doc-macro-name Mx
+. doc-parse-args \$@
+. \}
+. if (\n[doc-arg-limit] > \n[doc-arg-ptr]) \{\
+. nr doc-arg-ptr +1
+. ie (\n[doc-type\n[doc-arg-ptr]] == 2) \
+. as doc-str-Mx1 \~\*[doc-arg\n[doc-arg-ptr]]
+. el \
+. nr doc-arg-ptr -1
+. \}
+. ds doc-arg\n[doc-arg-ptr] "\*[doc-str-Mx1]
+. nr doc-type\n[doc-arg-ptr] 2
+. ds doc-space\n[doc-arg-ptr] "\*[doc-space]
+. nr doc-num-args (\n[doc-arg-limit] - \n[doc-arg-ptr])
+. nr doc-arg-limit \n[doc-arg-ptr]
+. if \n[doc-num-args] \
+. doc-parse-space-vector
+. doc-print-recursive
+..
+. ec
+. ds sP \s0
+. ds tN \*[doc-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 \&MirBSD\\*(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 \&MirBSD\ #\&\\*(A\\n(aP\\*(aa
+. ie \\n(aC>\\n(aP \{\
+. nr aP \\n(aP+1
+. nR
+. \}
+. el .aZ
+. \}
+. el \{\
+. as b1 \&MirBSD\\*(aa
+. nR
+. \}
+. \}
+..
+.\}
+.\"-
+.Dt LKSH 1
+.Os MirBSD
+.Sh NAME
+.Nm lksh
+.Nd Legacy Korn shell built on mksh
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl +abCefhiklmnprUuvXx
+.Op Fl +o Ar opt
+.Oo
+.Fl c Ar string \*(Ba
+.Fl s \*(Ba
+.Ar file
+.Op Ar args ...
+.Oc
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a command interpreter intended exclusively for running legacy
+shell scripts.
+It is built on
+.Nm mksh ;
+refer to its manual page for details on the scripting language.
+It is recommended to port scripts to
+.Nm mksh
+instead of relying on legacy or objectionable POSIX-mandated behaviour,
+since the MirBSD Korn Shell scripting language is much more consistent.
+.Pp
+Do not use
+.Nm
+as an interactive or login shell; use
+.Nm mksh
+instead.
+.Pp
+Note that it's strongly recommended to invoke
+.Nm
+with
+.Fl o Ic posix
+to fully enjoy better compatibility to the
+.Tn POSIX
+standard (which is probably why you use
+.Nm
+over
+.Nm mksh
+in the first place);
+.Fl o Ic sh
+(possibly additionally to the above) may be needed for some legacy scripts.
+.Sh LEGACY MODE
+.Nm
+currently has the following differences from
+.Nm mksh :
+.Bl -bullet
+.It
+The
+.Ev KSH_VERSION
+string identifies
+.Nm
+as
+.Dq Li LEGACY KSH
+instead of
+.Dq Li MIRBSD KSH .
+Note that the rest of the version string is identical between
+the two shell flavours, and the behaviour and differences can
+change between versions; see the accompanying manual page
+.Xr mksh 1
+for the versions this document applies to.
+.It
+.Nm
+uses
+.Tn POSIX
+arithmetic, which has quite a few implications:
+The data type for arithmetic operations is the host
+.Tn ISO
+C
+.Vt long
+data type.
+Signed integer wraparound is Undefined Behaviour; this means that...
+.Bd -literal -offset indent
+$ echo $((2147483647 + 1))
+.Ed
+.Pp
+\&... is permitted to, e.g. delete all files on your system
+(the figure differs for non-32-bit systems, the rule doesn't).
+The sign of the result of a modulo operation with at least one
+negative operand is unspecified.
+Shift operations on negative numbers are unspecified.
+Division of the largest negative number by \-1 is Undefined Behaviour.
+The compiler is permitted to delete all data and crash the system
+if Undefined Behaviour occurs (see above for an example).
+.It
+The rotation arithmetic operators are not available.
+.It
+The shift arithmetic operators take all bits of the second operand into
+account; if they exceed permitted precision, the result is unspecified.
+.It
+Unless
+.Ic set -o posix
+is active,
+.Nm
+always uses traditional mode for constructs like:
+.Bd -literal -offset indent
+$ set -- $(getopt ab:c "$@")
+$ echo $?
+.Ed
+.Pp
+POSIX mandates this to show 0, but traditional mode
+passes through the errorlevel from the
+.Xr getopt 1
+command.
+.It
+Functions defined with the
+.Ic function
+reserved word share the shell options
+.Pq Ic set -o
+instead of locally scoping them.
+.El
+.Sh SEE ALSO
+.Xr mksh 1
+.Pp
+.Pa http://www.mirbsd.org/mksh.htm
+.Pp
+.Pa http://www.mirbsd.org/ksh\-chan.htm
+.Sh CAVEATS
+To use
+.Nm
+as
+.Pa /bin/sh ,
+compilation to enable
+.Ic set -o posix
+by default if called as
+.Nm sh
+.Pq adding Dv \-DMKSH_BINSHPOSIX to Dv CPPFLAGS
+is highly recommended for better standards compliance.
+.Pp
+For better compatibility with legacy scripts, such as many
+.Tn Debian
+maintainer scripts, Upstart and SYSV init scripts, and other
+unfixed scripts, also adding the
+.Dv \-DMKSH_BINSHREDUCED
+compile-time option to enable
+.Em both
+.Ic set -o posix -o sh
+when the shell is run as
+.Nm sh ,
+as well as integrating the optional disrecommended
+.Xr printf 1
+builtin, might be necessary.
+.Pp
+.Nm
+tries to make a cross between a legacy bourne/posix compatibl-ish
+shell and a legacy pdksh-alike but
+.Dq legacy
+is not exactly specified.
+.Pp
+Talk to the
+.Mx
+development team and users using the mailing list at
+.Aq Mt miros\-mksh@mirbsd.org
+(please note the EU-DSGVO/GDPR notice on
+.Pa http://www.mirbsd.org/rss.htm#lists
+and in the SMTP banner!) or the
+.Li \&#\&!/bin/mksh
+.Pq or Li \&#ksh
+IRC channel at
+.Pa irc.freenode.net
+.Pq Port 6697 SSL, 6667 unencrypted
+if you need any further quirks or assistance,
+and consider migrating your legacy scripts to work with
+.Nm mksh
+instead of requiring
+.Nm .
diff --git a/shells/mksh/files/main.c b/shells/mksh/files/main.c
new file mode 100644
index 00000000000..3990f7d1276
--- /dev/null
+++ b/shells/mksh/files/main.c
@@ -0,0 +1,2150 @@
+/* $OpenBSD: main.c,v 1.57 2015/09/10 22:48:58 nicm Exp $ */
+/* $OpenBSD: tty.c,v 1.10 2014/08/10 02:44:26 guenther Exp $ */
+/* $OpenBSD: io.c,v 1.26 2015/09/11 08:00:27 guenther Exp $ */
+/* $OpenBSD: table.c,v 1.16 2015/09/01 13:12:31 tedu Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#define EXTERN
+#include "sh.h"
+
+#if HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#endif
+#if HAVE_SETLOCALE_CTYPE
+#include <locale.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.372 2020/05/16 22:51:24 tg Exp $");
+
+#ifndef MKSHRC_PATH
+#define MKSHRC_PATH "~/.mkshrc"
+#endif
+
+#ifndef MKSH_DEFAULT_TMPDIR
+#define MKSH_DEFAULT_TMPDIR MKSH_UNIXROOT "/tmp"
+#endif
+
+static uint8_t isuc(const char *);
+static int main_init(int, const char *[], Source **, struct block **);
+void chvt_reinit(void);
+static void reclaim(void);
+static void remove_temps(struct temp *);
+static mksh_uari_t rndsetup(void);
+static void init_environ(void);
+#ifdef SIGWINCH
+static void x_sigwinch(int);
+#endif
+
+static const char initsubs[] =
+ "${PS2=> }"
+ "${PS3=#? }"
+ "${PS4=+ }"
+ "${SECONDS=0}"
+ "${TMOUT=0}"
+ "${EPOCHREALTIME=}";
+
+static const char *initcoms[] = {
+ Ttypeset, Tdr, initvsn, NULL,
+ Ttypeset, Tdx, "HOME", TPATH, TSHELL, NULL,
+ Ttypeset, "-i10", "COLUMNS", "LINES", "SECONDS", "TMOUT", NULL,
+ Talias,
+ "integer=\\\\builtin typeset -i",
+ "local=\\\\builtin typeset",
+ /* not "alias -t --": hash -r needs to work */
+ "hash=\\\\builtin alias -t",
+ "type=\\\\builtin whence -v",
+ "autoload=\\\\builtin typeset -fu",
+ "functions=\\\\builtin typeset -f",
+ "history=\\\\builtin fc -l",
+ "nameref=\\\\builtin typeset -n",
+ "nohup=nohup ",
+ "r=\\\\builtin fc -e -",
+ "login=\\\\builtin exec login",
+ NULL,
+ /* this is what AT&T ksh seems to track, with the addition of emacs */
+ Talias, "-tU",
+ Tcat, "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
+ "make", "mv", "pr", "rm", "sed", Tsh, "vi", "who", NULL,
+ NULL
+};
+
+static const char *restr_com[] = {
+ Ttypeset, Tdr, TPATH, TENV, TSHELL, NULL
+};
+
+static bool initio_done;
+
+/* top-level parsing and execution environment */
+static struct env env;
+struct env *e = &env;
+
+/* compile-time assertions */
+
+/* this one should be defined by the standard */
+cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
+ (sizeof(unsigned char) == 1));
+cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) &&
+ ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF));
+/* the next assertion is probably not really needed */
+cta(short_is_2_char, sizeof(short) == 2);
+cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short));
+/* the next assertion is probably not really needed */
+cta(int_is_4_char, sizeof(int) == 4);
+cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int));
+
+cta(long_ge_int, sizeof(long) >= sizeof(int));
+cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long));
+
+#ifndef MKSH_LEGACY_MODE
+/* the next assertion is probably not really needed */
+cta(ari_is_4_char, sizeof(mksh_ari_t) == 4);
+/* but this is */
+cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1));
+/* the next assertion is probably not really needed */
+cta(uari_is_4_char, sizeof(mksh_uari_t) == 4);
+/* but the next three are; we REQUIRE unsigned integer wraparound */
+cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1));
+cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3));
+cta(uari_wrap_32_bit,
+ (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) >
+ (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4));
+#endif
+/* these are always required */
+cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0);
+cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0);
+/* we require these to have the precisely same size and assume 2s complement */
+cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t));
+
+cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t));
+cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *));
+cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void)));
+/* our formatting routines assume this */
+cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long));
+cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long));
+
+static mksh_uari_t
+rndsetup(void)
+{
+ register uint32_t h;
+ struct {
+ ALLOC_ITEM alloc_INT;
+ void *dataptr, *stkptr, *mallocptr;
+#if defined(__GLIBC__) && (__GLIBC__ >= 2)
+ sigjmp_buf jbuf;
+#endif
+ struct timeval tv;
+ } *bufptr;
+ char *cp;
+
+ cp = alloc(sizeof(*bufptr) - sizeof(ALLOC_ITEM), APERM);
+ /* clear the allocated space, for valgrind and to avoid UB */
+ memset(cp, 0, sizeof(*bufptr) - sizeof(ALLOC_ITEM));
+ /* undo what alloc() did to the malloc result address */
+ bufptr = (void *)(cp - sizeof(ALLOC_ITEM));
+ /* 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;
+#if defined(__GLIBC__) && (__GLIBC__ >= 2)
+ /* glibc pointer guard */
+ sigsetjmp(bufptr->jbuf, 1);
+#endif
+ /* introduce variation (and yes, second arg MBZ for portability) */
+ mksh_TIME(bufptr->tv);
+
+#ifdef MKSH_ALLOC_CATCH_UNDERRUNS
+ mprotect(((char *)bufptr) + 4096, 4096, PROT_READ | PROT_WRITE);
+#endif
+ h = chvt_rndsetup(bufptr, sizeof(*bufptr));
+
+ afree(cp, APERM);
+ return ((mksh_uari_t)h);
+}
+
+void
+chvt_reinit(void)
+{
+ kshpid = procpid = getpid();
+ ksheuid = geteuid();
+ kshpgrp = getpgrp();
+ kshppid = getppid();
+}
+
+static const char *empty_argv[] = {
+ Tmksh, NULL
+};
+
+static uint8_t
+isuc(const char *cx) {
+ char *cp, *x;
+ uint8_t rv = 0;
+
+ if (!cx || !*cx)
+ return (0);
+
+ /* uppercase a string duplicate */
+ strdupx(x, cx, ATEMP);
+ cp = x;
+ while ((*cp = ksh_toupper(*cp)))
+ ++cp;
+
+ /* check for UTF-8 */
+ if (strstr(x, "UTF-8") || strstr(x, "UTF8"))
+ rv = 1;
+
+ /* free copy and out */
+ afree(x, ATEMP);
+ return (rv);
+}
+
+static int
+main_init(int argc, const char *argv[], Source **sp, struct block **lp)
+{
+ int argi, i;
+ Source *s = NULL;
+ struct block *l;
+ unsigned char restricted_shell = 0, errexit, utf_flag;
+ char *cp;
+ const char *ccp, **wp;
+ struct tbl *vp;
+ struct stat s_stdin;
+#if !defined(_PATH_DEFPATH) && defined(_CS_PATH)
+ ssize_t k;
+#endif
+
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+ ebcdic_init();
+#endif
+ set_ifs(TC_IFSWS);
+
+#ifdef __OS2__
+ os2_init(&argc, &argv);
+#define builtin_name_cmp stricmp
+#else
+#define builtin_name_cmp strcmp
+#endif
+
+ /* do things like getpgrp() et al. */
+ chvt_reinit();
+
+ /* make sure argv[] is sane, for weird OSes */
+ if (!*argv) {
+ argv = empty_argv;
+ argc = 1;
+ }
+ kshname = argv[0];
+
+ /* initialise permanent Area */
+ ainit(&aperm);
+ /* max. name length: -2147483648 = 11 (+ NUL) */
+ vtemp = alloc(offsetof(struct tbl, name[0]) + 12, APERM);
+
+ /* set up base environment */
+ 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();
+
+ /* determine the basename (without '-' or path) of the executable */
+ ccp = kshname;
+ goto begin_parsing_kshname;
+ while ((i = ccp[argi++])) {
+ if (mksh_cdirsep(i)) {
+ ccp += argi;
+ begin_parsing_kshname:
+ argi = 0;
+ }
+ }
+ Flag(FLOGIN) = (ord(*ccp) == ORD('-')) || (ord(*kshname) == ORD('-'));
+ if (ord(*ccp) == ORD('-'))
+ ++ccp;
+ if (!*ccp)
+ ccp = empty_argv[0];
+
+ /*
+ * Turn on nohup by default. (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
+ * alternation always have it on.
+ */
+ Flag(FBRACEEXPAND) = 1;
+
+ /*
+ * Turn on "set -x" inheritance by default.
+ */
+ Flag(FXTRACEREC) = 1;
+
+ /* define built-in commands and see if we were called as one */
+ ktinit(APERM, &builtins,
+ /* currently up to 52 builtins: 75% of 128 = 2^7 */
+ 7);
+ for (i = 0; mkshbuiltins[i].name != NULL; ++i) {
+ const char *builtin_name;
+
+ builtin_name = builtin(mkshbuiltins[i].name,
+ mkshbuiltins[i].func);
+ if (!builtin_name_cmp(ccp, builtin_name)) {
+ /* canonicalise argv[0] */
+ ccp = builtin_name;
+ as_builtin = true;
+ }
+ }
+
+ if (!as_builtin) {
+ /* check for -T option early */
+ argi = parse_args(argv, OF_FIRSTTIME, NULL);
+ if (argi < 0)
+ return (1);
+ /* called as rsh, rmksh, -rsh, RKSH.EXE, etc.? */
+ if (ksh_eq(*ccp, 'R', 'r')) {
+ ++ccp;
+ ++restricted_shell;
+ }
+#if defined(MKSH_BINSHPOSIX) || defined(MKSH_BINSHREDUCED)
+ /* are we called as -rsh or /bin/sh or SH.EXE or so? */
+ if (ksh_eq(ccp[0], 'S', 's') &&
+ ksh_eq(ccp[1], 'H', 'h')) {
+ /* either also turns off braceexpand */
+#ifdef MKSH_BINSHPOSIX
+ /* enable better POSIX conformance */
+ change_flag(FPOSIX, OF_FIRSTTIME, true);
+#endif
+#ifdef MKSH_BINSHREDUCED
+ /* enable kludge/compat mode */
+ change_flag(FSH, OF_FIRSTTIME, true);
+#endif
+ }
+#endif
+ }
+
+ initvar();
+
+ inittraps();
+
+ coproc_init();
+
+ /* set up variable and command dictionaries */
+ ktinit(APERM, &taliases, 0);
+ ktinit(APERM, &aliases, 0);
+#ifndef MKSH_NOPWNAM
+ ktinit(APERM, &homedirs, 0);
+#endif
+
+ /* define shell keywords */
+ initkeywords();
+
+ init_histvec();
+
+ /* initialise tty size before importing environment */
+ change_winsz();
+
+#ifdef _PATH_DEFPATH
+ def_path = _PATH_DEFPATH;
+#else
+#ifdef _CS_PATH
+ if ((k = confstr(_CS_PATH, NULL, 0)) > 0 &&
+ confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1)
+ def_path = cp;
+ else
+#endif
+ /*
+ * this is uniform across all OSes unless it
+ * breaks somewhere hard; don't try to optimise,
+ * e.g. add stuff for Interix or remove /usr
+ * for HURD, because e.g. Debian GNU/HURD is
+ * "keeping a regular /usr"; this is supposed
+ * to be a sane 'basic' default PATH
+ */
+ def_path = MKSH_UNIXROOT "/bin" MKSH_PATHSEPS
+ MKSH_UNIXROOT "/usr/bin" MKSH_PATHSEPS
+ MKSH_UNIXROOT "/sbin" MKSH_PATHSEPS
+ MKSH_UNIXROOT "/usr/sbin";
+#endif
+
+ /*
+ * Set PATH to def_path (will set the path global variable).
+ * (import of environment below will probably change this setting).
+ */
+ vp = global(TPATH);
+ /* setstr can't fail here */
+ setstr(vp, def_path, KSH_RETURN_ERROR);
+
+#ifndef MKSH_NO_CMDLINE_EDITING
+ /*
+ * 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.
+ */
+ change_flag(FEMACS, OF_SPECIAL, true);
+#if !MKSH_S_NOVI
+ Flag(FVITABCOMPLETE) = 1;
+#endif
+#endif
+
+ /* import environment */
+ init_environ();
+
+ /* for security */
+ typeset(TinitIFS, 0, 0, 0, 0);
+
+ /* assign default shell variable values */
+ typeset("PATHSEP=" MKSH_PATHSEPS, 0, 0, 0, 0);
+ substitute(initsubs, 0);
+
+ /* Figure out the current working directory and set $PWD */
+ vp = global(TPWD);
+ cp = str_val(vp);
+ /* Try to use existing $PWD if it is valid */
+ set_current_wd((mksh_abspath(cp) && test_eval(NULL, TO_FILEQ, cp,
+ Tdot, 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++) {
+ c_builtin(wp);
+ while (*wp != NULL)
+ wp++;
+ }
+ setint_n(global("OPTIND"), 1, 10);
+
+ kshuid = getuid();
+ kshgid = getgid();
+ kshegid = getegid();
+
+ safe_prompt = ksheuid ? "$ " : "# ";
+ vp = global("PS1");
+ /* Set PS1 if unset or we are root and prompt doesn't contain a # */
+ if (!(vp->flag & ISSET) ||
+ (!ksheuid && !strchr(str_val(vp), '#')))
+ /* setstr can't fail here */
+ setstr(vp, safe_prompt, KSH_RETURN_ERROR);
+ setint_n((vp = global("BASHPID")), 0, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("PPID")), (mksh_uari_t)kshppid, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid, 10);
+ vp->flag |= INT_U;
+ setint_n((vp = global("RANDOM")), rndsetup(), 10);
+ vp->flag |= INT_U;
+ setint_n((vp_pipest = global("PIPESTATUS")), 0, 10);
+
+ /* Set this before parsing arguments */
+ Flag(FPRIVILEGED) = (kshuid != ksheuid || kshgid != kshegid) ? 2 : 0;
+
+ /* record if monitor is set on command line (see j_init() in jobs.c) */
+#ifndef MKSH_UNEMPLOYED
+ Flag(FMONITOR) = 127;
+#endif
+ /* this to note if utf-8 mode is set on command line (see below) */
+ UTFMODE = 2;
+
+ if (!as_builtin) {
+ argi = parse_args(argv, OF_CMDLINE, NULL);
+ if (argi < 0)
+ return (1);
+ }
+
+ /* process this later only, default to off (hysterical raisins) */
+ utf_flag = UTFMODE;
+ UTFMODE = 0;
+
+ if (as_builtin) {
+ /* auto-detect from environment variables, always */
+ utf_flag = 3;
+ } else if (Flag(FCOMMAND)) {
+ s = pushs(SSTRINGCMDLINE, ATEMP);
+ if (!(s->start = s->str = argv[argi++]))
+ errorf(Tf_optfoo, "", "", 'c', Treq_arg);
+ while (*s->str) {
+ if (ctype(*s->str, C_QUOTE))
+ break;
+ s->str++;
+ }
+ if (!*s->str)
+ s->flags |= SF_MAYEXEC;
+ s->str = s->start;
+#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
+ /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */
+ if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--"))
+ ++argi;
+#endif
+ if (argv[argi])
+ kshname = argv[argi++];
+ } else if (argi < argc && !Flag(FSTDIN)) {
+ s = pushs(SFILE, ATEMP);
+#ifdef __OS2__
+ /*
+ * A bug in OS/2 extproc (like shebang) handling makes
+ * it not pass the full pathname of a script, so we need
+ * to search for it. This changes the behaviour of a
+ * simple "mksh foo", but can't be helped.
+ */
+ s->file = argv[argi++];
+ if (search_access(s->file, X_OK) != 0)
+ s->file = search_path(s->file, path, X_OK, NULL);
+ if (!s->file || !*s->file)
+ s->file = argv[argi - 1];
+#else
+ s->file = argv[argi++];
+#endif
+ s->u.shf = shf_open(s->file, O_RDONLY | O_MAYEXEC, 0,
+ SHF_MAPHI | SHF_CLEXEC);
+ if (s->u.shf == NULL) {
+ shl_stdout_ok = false;
+ warningf(true, Tf_sD_s, s->file, cstrerror(errno));
+ /* mandated by SUSv4 */
+ exstat = 127;
+ unwind(LERROR);
+ }
+ kshname = s->file;
+ } else {
+ Flag(FSTDIN) = 1;
+ s = pushs(SSTDIN, ATEMP);
+ s->file = "<stdin>";
+ s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0),
+ NULL);
+ if (isatty(0) && isatty(2)) {
+ Flag(FTALKING) = Flag(FTALKING_I) = 1;
+ /* The following only if isatty(0) */
+ s->flags |= SF_TTY;
+ s->u.shf->flags |= SHF_INTERRUPT;
+ s->file = NULL;
+ }
+ }
+
+ /* this bizarreness is mandated by POSIX */
+ if (Flag(FTALKING) && fstat(0, &s_stdin) >= 0 &&
+ S_ISCHR(s_stdin.st_mode))
+ reset_nonblock(0);
+
+ /* initialise job control */
+ j_init();
+ /* do this after j_init() which calls tty_init_state() */
+ if (Flag(FTALKING)) {
+ if (utf_flag == 2) {
+#ifndef MKSH_ASSUME_UTF8
+ /* auto-detect from locale or environment */
+ utf_flag = 4;
+#else /* this may not be an #elif */
+#if MKSH_ASSUME_UTF8
+ utf_flag = 1;
+#else
+ /* always disable UTF-8 (for interactive) */
+ utf_flag = 0;
+#endif
+#endif
+ }
+#ifndef MKSH_NO_CMDLINE_EDITING
+ x_init();
+#endif
+ }
+
+#ifdef SIGWINCH
+ sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
+ setsig(&sigtraps[SIGWINCH], x_sigwinch,
+ SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+#endif
+
+ l = e->loc;
+ if (as_builtin) {
+ l->argc = argc;
+ l->argv = argv;
+ l->argv[0] = ccp;
+ } else {
+ l->argc = argc - argi;
+ /*
+ * allocate a new array because otherwise, when we modify
+ * it in-place, ps(1) output changes; the meaning of argc
+ * here is slightly different as it excludes kshname, and
+ * we add a trailing NULL sentinel as well
+ */
+ l->argv = alloc2(l->argc + 2, sizeof(void *), APERM);
+ l->argv[0] = kshname;
+ memcpy(&l->argv[1], &argv[argi], l->argc * sizeof(void *));
+ l->argv[l->argc + 1] = NULL;
+ getopts_reset(1);
+ }
+
+ /* divine the initial state of the utf8-mode Flag */
+ 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;
+#endif
+ /* FALLTHROUGH */
+
+ /* 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;
+ }
+
+ /* Disable during .profile/ENV reading */
+ restricted_shell |= Flag(FRESTRICTED);
+ Flag(FRESTRICTED) = 0;
+ errexit = Flag(FERREXIT);
+ Flag(FERREXIT) = 0;
+
+ /* save flags for "set +o" handling */
+ memcpy(baseline_flags, shell_flags, sizeof(shell_flags));
+ /* disable these because they have special handling */
+ baseline_flags[(int)FPOSIX] = 0;
+ baseline_flags[(int)FSH] = 0;
+ /* ensure these always show up setting, for FPOSIX/FSH */
+ baseline_flags[(int)FBRACEEXPAND] = 0;
+ baseline_flags[(int)FUNNYCODE] = 0;
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ /* mark as initialised */
+ baseline_flags[(int)FNFLAGS] = 1;
+#endif
+ if (as_builtin)
+ goto skip_startup_files;
+
+ /*
+ * Do this before profile/$ENV so that if it causes problems in them,
+ * user will know why things broke.
+ */
+ if (!current_wd[0] && Flag(FTALKING))
+ warningf(false, "can't determine current directory");
+
+ if (Flag(FLOGIN))
+ include(MKSH_SYSTEM_PROFILE, 0, NULL, true);
+ if (Flag(FPRIVILEGED)) {
+ include(MKSH_SUID_PROFILE, 0, NULL, true);
+ /* note whether -p was enabled during startup */
+ if (Flag(FPRIVILEGED) == 1)
+ /* allow set -p to setuid() later */
+ Flag(FPRIVILEGED) = 3;
+ else
+ /* turn off -p if not set explicitly */
+ change_flag(FPRIVILEGED, OF_INTERNAL, false);
+ /* track shell-imposed changes */
+ baseline_flags[(int)FPRIVILEGED] = Flag(FPRIVILEGED);
+ } else {
+ if (Flag(FLOGIN))
+ include(substitute("$HOME/.profile", 0), 0, NULL, true);
+ if (Flag(FTALKING)) {
+ cp = substitute("${ENV:-" MKSHRC_PATH "}", DOTILDE);
+ if (cp[0] != '\0')
+ include(cp, 0, NULL, true);
+ }
+ }
+ if (restricted_shell) {
+ c_builtin(restr_com);
+ /* After typeset command... */
+ Flag(FRESTRICTED) = 1;
+ /* track shell-imposed changes */
+ baseline_flags[(int)FRESTRICTED] = 1;
+ }
+ Flag(FERREXIT) = errexit;
+
+ if (Flag(FTALKING) && s)
+ hist_init(s);
+ else {
+ /* set after ENV */
+ skip_startup_files:
+ Flag(FTRACKALL) = 1;
+ /* track shell-imposed change (might lower surprise) */
+ baseline_flags[(int)FTRACKALL] = 1;
+ }
+
+ alarm_init();
+
+ *sp = s;
+ *lp = l;
+ return (0);
+}
+
+/* this indirection barrier reduces stack usage during normal operation */
+
+int
+main(int argc, const char *argv[])
+{
+ int rv;
+ Source *s;
+ struct block *l;
+
+ if ((rv = main_init(argc, argv, &s, &l)) == 0) {
+ if (as_builtin) {
+ rv = c_builtin(l->argv);
+ } else {
+ shell(s, 0);
+ /* NOTREACHED */
+ }
+ }
+ return (rv);
+}
+
+int
+include(const char *name, int argc, const char **argv, bool intr_ok)
+{
+ Source *volatile s = NULL;
+ struct shf *shf;
+ const char **volatile old_argv;
+ volatile int old_argc;
+ int i;
+
+ shf = shf_open(name, O_RDONLY | O_MAYEXEC, 0, SHF_MAPHI | SHF_CLEXEC);
+ if (shf == NULL)
+ return (-1);
+
+ if (argv) {
+ old_argv = e->loc->argv;
+ old_argc = e->loc->argc;
+ } else {
+ old_argv = NULL;
+ old_argc = 0;
+ }
+ newenv(E_INCL);
+ if ((i = kshsetjmp(e->jbuf))) {
+ quitenv(s ? s->u.shf : NULL);
+ if (old_argv) {
+ e->loc->argv = old_argv;
+ e->loc->argc = old_argc;
+ }
+ switch (i) {
+ case LRETURN:
+ case LERROR:
+ case LERREXT:
+ /* see below */
+ return (exstat & 0xFF);
+ case LINTR:
+ /*
+ * 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 & 0xFF) - 128) != SIGTERM)
+ return (1);
+ /* FALLTHROUGH */
+ case LEXIT:
+ case LLEAVE:
+ case LSHELL:
+ unwind(i);
+ /* NOTREACHED */
+ default:
+ internal_errorf(Tunexpected_type, Tunwind, Tsource, i);
+ /* NOTREACHED */
+ }
+ }
+ if (argv) {
+ e->loc->argv = argv;
+ e->loc->argc = argc;
+ }
+ s = pushs(SFILE, ATEMP);
+ s->u.shf = shf;
+ strdupx(s->file, name, ATEMP);
+ i = shell(s, 1);
+ quitenv(s->u.shf);
+ if (old_argv) {
+ e->loc->argv = old_argv;
+ e->loc->argc = old_argc;
+ }
+ /* & 0xff to ensure value not -1 */
+ return (i & 0xFF);
+}
+
+/* spawn a command into a shell optionally keeping track of the line number */
+int
+command(const char *comm, int line)
+{
+ Source *s, *sold = source;
+ int rv;
+
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = comm;
+ s->line = line;
+ rv = shell(s, 1);
+ source = sold;
+ return (rv);
+}
+
+/*
+ * run the commands from the input source, returning status.
+ */
+int
+shell(Source * volatile s, volatile int level)
+{
+ struct op *t;
+ volatile bool wastty = tobool(s->flags & SF_TTY);
+ volatile uint8_t attempts = 13;
+ volatile bool interactive = (level == 0) && Flag(FTALKING);
+ volatile bool sfirst = true;
+ Source *volatile old_source = source;
+ int i;
+
+ newenv(level == 2 ? E_EVAL : E_PARSE);
+ if (level == 2)
+ e->flags |= EF_IN_EVAL;
+ if (interactive)
+ really_exit = false;
+ switch ((i = kshsetjmp(e->jbuf))) {
+ case 0:
+ break;
+ case LBREAK:
+ case LCONTIN:
+ /* assert: interactive == false */
+ source = old_source;
+ quitenv(NULL);
+ if (level == 2) {
+ /* keep on going */
+ unwind(i);
+ /* NOTREACHED */
+ }
+ internal_errorf(Tf_cant_s, Tshell,
+ i == LBREAK ? Tbreak : Tcontinue);
+ /* NOTREACHED */
+ case LINTR:
+ /* we get here if SIGINT not caught or ignored */
+ case LERROR:
+ case LERREXT:
+ case LSHELL:
+ if (interactive) {
+ if (i == LINTR)
+ shellf("\n");
+ /*
+ * 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
+ * top level shell. Kind of strange since
+ * interactive is set if we are reading from
+ * a tty, but to have stopped jobs, one only
+ * needs FMONITOR set (not FTALKING/SF_TTY)...
+ */
+ /* toss any input we have so far */
+ yyrecursive_pop(true);
+ s->start = s->str = null;
+ retrace_info = NULL;
+ herep = heres;
+ break;
+ }
+ /* FALLTHROUGH */
+ case LEXIT:
+ case LLEAVE:
+ case LRETURN:
+ source = old_source;
+ quitenv(NULL);
+ if (i == LERREXT && level == 2)
+ return (exstat & 0xFF);
+ /* keep on going */
+ unwind(i);
+ /* NOTREACHED */
+ default:
+ source = old_source;
+ quitenv(NULL);
+ internal_errorf(Tunexpected_type, Tunwind, Tshell, i);
+ /* NOTREACHED */
+ }
+ while (/* CONSTCOND */ 1) {
+ if (trap)
+ runtraps(0);
+
+ if (s->next == NULL) {
+ if (Flag(FVERBOSE))
+ s->flags |= SF_ECHO;
+ else
+ s->flags &= ~SF_ECHO;
+ }
+ if (interactive) {
+ j_notify();
+ set_prompt(PS1, s);
+ }
+ t = compile(s, sfirst, true);
+ if (interactive)
+ histsave(&s->line, NULL, HIST_FLUSH, true);
+ sfirst = false;
+ if (!t)
+ goto source_no_tree;
+ if (t->type == TEOF) {
+ if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
+ shellf("Use 'exit' to leave mksh\n");
+ s->type = SSTDIN;
+ } else if (wastty && !really_exit &&
+ j_stopped_running()) {
+ really_exit = true;
+ s->type = SSTDIN;
+ } else {
+ /*
+ * this for POSIX which says EXIT traps
+ * shall be taken in the environment
+ * immediately after the last command
+ * executed.
+ */
+ if (level == 0)
+ unwind(LEXIT);
+ break;
+ }
+ } else if ((s->flags & SF_MAYEXEC) && t->type == TCOM)
+ t->u.evalflags |= DOTCOMEXEC;
+ if (!Flag(FNOEXEC) || (s->flags & SF_TTY))
+ exstat = execute(t, 0, NULL) & 0xFF;
+
+ if (t->type != TEOF && interactive && really_exit)
+ really_exit = false;
+
+ source_no_tree:
+ reclaim();
+ }
+ source = old_source;
+ quitenv(NULL);
+ return (exstat & 0xFF);
+}
+
+/* return to closest error handler or shell(), exit if none found */
+/* note: i MUST NOT be 0 */
+void
+unwind(int i)
+{
+ /* during eval, skip FERREXIT trap */
+ if (i == LERREXT && (e->flags & EF_IN_EVAL))
+ goto defer_traps;
+
+ /* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */
+ if (i == LEXIT || ((i == LERROR || i == LERREXT || i == LINTR) &&
+ sigtraps[ksh_SIGEXIT].trap &&
+ (!Flag(FTALKING) || Flag(FERREXIT)))) {
+ ++trap_nested;
+ runtrap(&sigtraps[ksh_SIGEXIT], trap_nested == 1);
+ --trap_nested;
+ i = LLEAVE;
+ } else if (Flag(FERREXIT) && (i == LERROR || i == LERREXT || i == LINTR)) {
+ ++trap_nested;
+ runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1);
+ --trap_nested;
+ i = LLEAVE;
+ }
+ defer_traps:
+
+ while (/* CONSTCOND */ 1) {
+ switch (e->type) {
+ case E_PARSE:
+ case E_FUNC:
+ case E_INCL:
+ case E_LOOP:
+ case E_ERRH:
+ case E_EVAL:
+ kshlongjmp(e->jbuf, i);
+ /* NOTREACHED */
+ case E_NONE:
+ if (i == LINTR)
+ e->flags |= EF_FAKE_SIGDIE;
+ /* FALLTHROUGH */
+ default:
+ quitenv(NULL);
+ }
+ }
+}
+
+void
+newenv(int type)
+{
+ struct env *ep;
+ char *cp;
+
+ /*
+ * struct env includes ALLOC_ITEM for alignment constraints
+ * so first get the actually used memory, then assign it
+ */
+ cp = alloc(sizeof(struct env) - sizeof(ALLOC_ITEM), ATEMP);
+ /* undo what alloc() did to the malloc result address */
+ ep = (void *)(cp - sizeof(ALLOC_ITEM));
+ /* initialise public members of struct env (not the ALLOC_ITEM) */
+ ainit(&ep->area);
+ ep->oenv = e;
+ ep->loc = e->loc;
+ ep->savefd = NULL;
+ ep->temps = NULL;
+ ep->yyrecursive_statep = NULL;
+ ep->type = type;
+ ep->flags = e->flags & EF_IN_EVAL;
+ e = ep;
+}
+
+void
+quitenv(struct shf *shf)
+{
+ struct env *ep = e;
+ char *cp;
+ int fd;
+
+ yyrecursive_pop(true);
+ while (ep->oenv && ep->oenv->loc != ep->loc)
+ popblock();
+ if (ep->savefd != NULL) {
+ for (fd = 0; fd < NUFILE; fd++)
+ /* 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 */
+ shf_reopen(2, SHF_WR, shl_out);
+ }
+ /*
+ * Bottom of the stack.
+ * Either main shell is exiting or cleanup_parents_env() was called.
+ */
+ if (ep->oenv == NULL) {
+#ifdef DEBUG_LEAKS
+ int i;
+#endif
+
+ if (ep->type == E_NONE) {
+ /* Main shell exiting? */
+#if HAVE_PERSISTENT_HISTORY
+ if (Flag(FTALKING))
+ hist_finish();
+#endif
+ j_exit();
+ if (ep->flags & EF_FAKE_SIGDIE) {
+ int sig = (exstat & 0xFF) - 128;
+
+ /*
+ * 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..
+ */
+ if ((sig == SIGINT || sig == SIGTERM) &&
+ (kshpgrp == kshpid)) {
+ setsig(&sigtraps[sig], SIG_DFL,
+ SS_RESTORE_CURR | SS_FORCE);
+ kill(0, sig);
+ }
+ }
+ }
+ if (shf)
+ shf_close(shf);
+ reclaim();
+#ifdef DEBUG_LEAKS
+#ifndef MKSH_NO_CMDLINE_EDITING
+ x_done();
+#endif
+#ifndef MKSH_NOPROSPECTOFWORK
+ /* block at least SIGCHLD during/after afreeall */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, NULL);
+#endif
+ afreeall(APERM);
+ for (fd = 3; fd < NUFILE; fd++)
+ if ((i = fcntl(fd, F_GETFD, 0)) != -1 &&
+ (i & FD_CLOEXEC))
+ close(fd);
+ close(2);
+ close(1);
+ close(0);
+#endif
+ exit(exstat & 0xFF);
+ }
+ if (shf)
+ shf_close(shf);
+ reclaim();
+
+ e = e->oenv;
+
+ /* free the struct env - tricky due to the ALLOC_ITEM inside */
+ cp = (void *)ep;
+ afree(cp + sizeof(ALLOC_ITEM), ATEMP);
+}
+
+/* Called after a fork to cleanup stuff left over from parents environment */
+void
+cleanup_parents_env(void)
+{
+ struct env *ep;
+ int fd;
+
+ /*
+ * Don't clean up temporary files - parent will probably need them.
+ * Also, can't easily reclaim memory since variables, etc. could be
+ * anywhere.
+ */
+
+ /* close all file descriptors hiding in savefd */
+ for (ep = e; ep; ep = ep->oenv) {
+ if (ep->savefd) {
+ for (fd = 0; fd < NUFILE; fd++)
+ if (ep->savefd[fd] > 0)
+ close(ep->savefd[fd]);
+ afree(ep->savefd, &ep->area);
+ ep->savefd = NULL;
+ }
+#ifdef DEBUG_LEAKS
+ if (ep->type != E_NONE)
+ ep->type = E_GONE;
+#endif
+ }
+#ifndef DEBUG_LEAKS
+ e->oenv = NULL;
+#endif
+}
+
+/* Called just before an execve cleanup stuff temporary files */
+void
+cleanup_proc_env(void)
+{
+ struct env *ep;
+
+ for (ep = e; ep; ep = ep->oenv)
+ remove_temps(ep->temps);
+}
+
+/* remove temp files and free ATEMP Area */
+static void
+reclaim(void)
+{
+ struct block *l;
+
+ while ((l = e->loc) && (!e->oenv || e->oenv->loc != l)) {
+ e->loc = l->next;
+ afreeall(&l->area);
+ }
+
+ remove_temps(e->temps);
+ e->temps = NULL;
+
+ /*
+ * if the memory backing source is reclaimed, things
+ * will end up badly when a function expecting it to
+ * be valid is run; a NULL pointer is easily debugged
+ */
+ if (source && source->areap == &e->area)
+ source = NULL;
+ afreeall(&e->area);
+}
+
+static void
+remove_temps(struct temp *tp)
+{
+ while (tp) {
+ if (tp->pid == procpid)
+ unlink(tp->tffn);
+ tp = tp->next;
+ }
+}
+
+/*
+ * Initialise tty_fd. Used for tracking the size of the terminal,
+ * saving/resetting tty modes upon forground job completion, and
+ * for setting up the tty process group. Return values:
+ * 0 = got controlling tty
+ * 1 = got terminal but no controlling tty
+ * 2 = cannot find a terminal
+ * 3 = cannot dup fd
+ * 4 = cannot make fd close-on-exec
+ * An existing tty_fd is cached if no "better" one could be found,
+ * i.e. if tty_devtty was already set or the new would not set it.
+ */
+int
+tty_init_fd(void)
+{
+ int fd, rv, eno = 0;
+ bool do_close = false, is_devtty = true;
+
+ if (tty_devtty) {
+ /* already got a tty which is /dev/tty */
+ return (0);
+ }
+
+#ifdef _UWIN
+ /*XXX imake style */
+ if (isatty(3)) {
+ /* fd 3 on UWIN _is_ /dev/tty (or our controlling tty) */
+ fd = 3;
+ goto got_fd;
+ }
+#endif
+ if ((fd = open(T_devtty, O_RDWR, 0)) >= 0) {
+ do_close = true;
+ goto got_fd;
+ }
+ eno = errno;
+
+ if (tty_fd >= 0) {
+ /* already got a non-devtty one */
+ rv = 1;
+ goto out;
+ }
+ is_devtty = false;
+
+ if (isatty((fd = 0)) || isatty((fd = 2)))
+ goto got_fd;
+ /* cannot find one */
+ rv = 2;
+ /* assert: do_close == false */
+ goto out;
+
+ got_fd:
+ if ((rv = fcntl(fd, F_DUPFD, FDBASE)) < 0) {
+ eno = errno;
+ rv = 3;
+ goto out;
+ }
+ if (fcntl(rv, F_SETFD, FD_CLOEXEC) < 0) {
+ eno = errno;
+ close(rv);
+ rv = 4;
+ goto out;
+ }
+ tty_fd = rv;
+ tty_devtty = is_devtty;
+ rv = eno = 0;
+ out:
+ if (do_close)
+ close(fd);
+ errno = eno;
+ return (rv);
+}
+
+/* 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) {
+ if (flags & VWARNINGF_INTERNAL)
+ shf_fprintf(shl_out, Tf_sD_, "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, Tf_sD_, 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;
+
+ exstat = 1;
+
+ /* 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);
+}
+
+/* like errorf(), but no unwind is done */
+void
+warningf(bool fileline, const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vwarningf(VWARNINGF_ERRORPREFIX | (fileline ? VWARNINGF_FILELINE : 0),
+ fmt, va);
+ va_end(va);
+}
+
+/*
+ * Used by built-in utilities to prefix shell and utility name to message
+ * (also unwinds environments for special builtins).
+ */
+void
+bi_errorf(const char *fmt, ...)
+{
+ va_list va;
+
+ /* debugging: note that stdout not valid */
+ shl_stdout_ok = false;
+
+ exstat = 1;
+
+ va_start(va, fmt);
+ vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE |
+ VWARNINGF_BUILTIN, fmt, va);
+ va_end(va);
+
+ /* POSIX special builtins cause non-interactive shells to exit */
+ if (builtin_spec) {
+ builtin_argv0 = NULL;
+ /* may not want to use LERROR here */
+ unwind(LERROR);
+ }
+}
+
+/*
+ * Used by functions called by builtins and not:
+ * identical to errorfx if first argument is nil,
+ * like bi_errorf storing the errorlevel into it otherwise
+ */
+void
+maybe_errorf(int *ep, int rc, const char *fmt, ...)
+{
+ va_list va;
+
+ /* debugging: note that stdout not valid */
+ shl_stdout_ok = false;
+
+ exstat = rc;
+
+ va_start(va, fmt);
+ vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE |
+ (ep ? VWARNINGF_BUILTIN : 0), fmt, va);
+ va_end(va);
+
+ if (!ep)
+ goto and_out;
+ *ep = rc;
+
+ /* POSIX special builtins cause non-interactive shells to exit */
+ if (builtin_spec) {
+ builtin_argv0 = NULL;
+ /* may not want to use LERROR here */
+ and_out:
+ unwind(LERROR);
+ }
+}
+
+/* Called when something that shouldn't happen does */
+void
+internal_errorf(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vwarningf(VWARNINGF_INTERNAL, fmt, va);
+ va_end(va);
+ unwind(LERROR);
+}
+
+void
+internal_warningf(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vwarningf(VWARNINGF_INTERNAL, fmt, va);
+ va_end(va);
+}
+
+/* used by error reporting functions to print "ksh: .kshrc[25]: " */
+void
+error_prefix(bool fileline)
+{
+ /* Avoid foo: foo[2]: ... */
+ if (!fileline || !source || !source->file ||
+ strcmp(source->file, kshname) != 0)
+ shf_fprintf(shl_out, Tf_sD_, kshname + (*kshname == '-'));
+ if (fileline && source && source->file != NULL) {
+ shf_fprintf(shl_out, "%s[%lu]: ", source->file,
+ (unsigned long)(source->errline ?
+ source->errline : source->line));
+ source->errline = 0;
+ }
+}
+
+/* printf to shl_out (stderr) with flush */
+void
+shellf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!initio_done)
+ /* shl_out may not be set up yet... */
+ return;
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_flush(shl_out);
+}
+
+/* printf to shl_stdout (stdout) */
+void
+shprintf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!shl_stdout_ok)
+ internal_errorf("shl_stdout not valid");
+ va_start(va, fmt);
+ shf_vfprintf(shl_stdout, fmt, va);
+ va_end(va);
+}
+
+/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */
+int
+can_seek(int fd)
+{
+ struct stat statb;
+
+ return (fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ?
+ SHF_UNBUF : 0);
+}
+
+#ifdef DF
+int shl_dbg_fd;
+#define NSHF_IOB 4
+#else
+#define NSHF_IOB 3
+#endif
+struct shf shf_iob[NSHF_IOB];
+
+void
+initio(void)
+{
+#ifdef DF
+ const char *lfp;
+#endif
+
+ /* force buffer allocation */
+ shf_fdopen(1, SHF_WR, shl_stdout);
+ shf_fdopen(2, SHF_WR, shl_out);
+ shf_fdopen(2, SHF_WR, shl_xtrace);
+#ifdef DF
+ if ((lfp = getenv("SDMKSH_PATH")) == NULL) {
+ if ((lfp = getenv("HOME")) == NULL || !mksh_abspath(lfp))
+ errorf("can't get home directory");
+ strpathx(lfp, lfp, "mksh-dbg.txt", 1);
+ }
+
+ if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0)
+ errorf("can't open debug output file %s", lfp);
+ if (shl_dbg_fd < FDBASE) {
+ int nfd;
+
+ nfd = fcntl(shl_dbg_fd, F_DUPFD, FDBASE);
+ close(shl_dbg_fd);
+ if ((shl_dbg_fd = nfd) == -1)
+ errorf("can't dup debug output file");
+ }
+ fcntl(shl_dbg_fd, F_SETFD, FD_CLOEXEC);
+ shf_fdopen(shl_dbg_fd, SHF_WR, shl_dbg);
+ DF("=== open ===");
+#endif
+ initio_done = true;
+}
+
+/* A dup2() with error checking */
+int
+ksh_dup2(int ofd, int nfd, bool errok)
+{
+ int rv;
+
+ if (((rv = dup2(ofd, nfd)) < 0) && !errok && (errno != EBADF))
+ errorf(Ttoo_many_files);
+
+#ifdef __ultrix
+ /*XXX imake style */
+ if (rv >= 0)
+ fcntl(nfd, F_SETFD, 0);
+#endif
+
+ return (rv);
+}
+
+/*
+ * 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)
+{
+ int nfd = fd;
+
+ if (fd < FDBASE && (nfd = fcntl(fd, F_DUPFD, FDBASE)) < 0 &&
+ (errno == EBADF || errno == EPERM))
+ return (-1);
+ if (nfd < 0 || nfd > SHRT_MAX)
+ errorf(Ttoo_many_files);
+ fcntl(nfd, F_SETFD, FD_CLOEXEC);
+ return ((short)nfd);
+}
+
+void
+restfd(int fd, int ofd)
+{
+ if (fd == 2)
+ shf_flush(&shf_iob[/* fd */ 2]);
+ if (ofd < 0)
+ /* original fd closed */
+ close(fd);
+ else if (fd != ofd) {
+ /*XXX: what to do if this dup fails? */
+ ksh_dup2(ofd, fd, true);
+ close(ofd);
+ }
+}
+
+void
+openpipe(int *pv)
+{
+ int lpv[2];
+
+ if (pipe(lpv) < 0)
+ errorf("can't create pipe - try again");
+ pv[0] = savefd(lpv[0]);
+ if (pv[0] != lpv[0])
+ close(lpv[0]);
+ pv[1] = savefd(lpv[1]);
+ if (pv[1] != lpv[1])
+ close(lpv[1]);
+#ifdef __OS2__
+ setmode(pv[0], O_BINARY);
+ setmode(pv[1], O_BINARY);
+#endif
+}
+
+void
+closepipe(int *pv)
+{
+ close(pv[0]);
+ close(pv[1]);
+}
+
+/*
+ * 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
+check_fd(const char *name, int mode, const char **emsgp)
+{
+ int fd, fl;
+
+ if (!name[0] || name[1])
+ goto illegal_fd_name;
+ if (name[0] == 'p')
+ return (coproc_getfd(mode, emsgp));
+ if (!ctype(name[0], C_DIGIT)) {
+ illegal_fd_name:
+ if (emsgp)
+ *emsgp = "illegal file descriptor name";
+ return (-1);
+ }
+
+ if ((fl = fcntl((fd = ksh_numdig(name[0])), F_GETFL, 0)) < 0) {
+ if (emsgp)
+ *emsgp = "bad file descriptor";
+ return (-1);
+ }
+ fl &= O_ACCMODE;
+ /*
+ * 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).
+ */
+ if (!(mode & X_OK) && fl != O_RDWR && (
+ ((mode & R_OK) && fl != O_RDONLY) ||
+ ((mode & W_OK) && fl != O_WRONLY))) {
+ if (emsgp)
+ *emsgp = (fl == O_WRONLY) ?
+ "fd not open for reading" :
+ "fd not open for writing";
+ return (-1);
+ }
+ return (fd);
+}
+
+/* Called once from main */
+void
+coproc_init(void)
+{
+ coproc.read = coproc.readw = coproc.write = -1;
+ coproc.njobs = 0;
+ coproc.id = 0;
+}
+
+/* Called by c_read() when eof is read - close fd if it is the co-process fd */
+void
+coproc_read_close(int fd)
+{
+ if (coproc.read >= 0 && fd == coproc.read) {
+ coproc_readw_close(fd);
+ close(coproc.read);
+ coproc.read = -1;
+ }
+}
+
+/*
+ * Called by c_read() and by iosetup() to close the other side of the
+ * read pipe, so reads will actually terminate.
+ */
+void
+coproc_readw_close(int fd)
+{
+ if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) {
+ close(coproc.readw);
+ coproc.readw = -1;
+ }
+}
+
+/*
+ * Called by c_print when a write to a fd fails with EPIPE and by iosetup
+ * when co-process input is dup'd
+ */
+void
+coproc_write_close(int fd)
+{
+ if (coproc.write >= 0 && fd == coproc.write) {
+ close(coproc.write);
+ coproc.write = -1;
+ }
+}
+
+/*
+ * 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
+coproc_getfd(int mode, const char **emsgp)
+{
+ int fd = (mode & R_OK) ? coproc.read : coproc.write;
+
+ if (fd >= 0)
+ return (fd);
+ if (emsgp)
+ *emsgp = "no coprocess";
+ return (-1);
+}
+
+/*
+ * called to close file descriptors related to the coprocess (if any)
+ * Should be called with SIGCHLD blocked.
+ */
+void
+coproc_cleanup(int reuse)
+{
+ /* This to allow co-processes to share output pipe */
+ if (!reuse || coproc.readw < 0 || coproc.read < 0) {
+ if (coproc.read >= 0) {
+ close(coproc.read);
+ coproc.read = -1;
+ }
+ if (coproc.readw >= 0) {
+ close(coproc.readw);
+ coproc.readw = -1;
+ }
+ }
+ if (coproc.write >= 0) {
+ close(coproc.write);
+ coproc.write = -1;
+ }
+}
+
+struct temp *
+maketemp(Area *ap, Temp_type type, struct temp **tlist)
+{
+ char *cp;
+ size_t len;
+ int i, j;
+ struct temp *tp;
+ const char *dir;
+ struct stat sb;
+
+ dir = tmpdir ? tmpdir : MKSH_DEFAULT_TMPDIR;
+ /* add "/shXXXXXX.tmp" plus NUL */
+ len = strlen(dir);
+ checkoktoadd(len, offsetof(struct temp, tffn[0]) + 14);
+ tp = alloc(offsetof(struct temp, tffn[0]) + 14 + len, ap);
+
+ tp->shf = NULL;
+ tp->pid = procpid;
+ tp->type = type;
+
+ if (stat(dir, &sb) || !S_ISDIR(sb.st_mode)) {
+ tp->tffn[0] = '\0';
+ goto maketemp_out;
+ }
+
+ cp = (void *)tp;
+ cp += offsetof(struct temp, tffn[0]);
+ memcpy(cp, dir, len);
+ cp += len;
+ memcpy(cp, "/shXXXXXX.tmp", 14);
+ /* point to the first of six Xes */
+ cp += 3;
+
+ /* cyclically attempt to open a temporary file */
+ do {
+ /* generate random part of filename */
+ len = 0;
+ do {
+ cp[len++] = digits_lc[rndget() % 36];
+ } while (len < 6);
+
+ /* check if this one works */
+ if ((i = binopen3(tp->tffn, O_CREAT | O_EXCL | O_RDWR,
+ 0600)) < 0 && errno != EEXIST)
+ goto maketemp_out;
+ } while (i < 0);
+
+ if (type == TT_FUNSUB) {
+ /* map us high and mark as close-on-exec */
+ if ((j = savefd(i)) != i) {
+ close(i);
+ i = j;
+ }
+
+ /* operation mode for the shf */
+ j = SHF_RD;
+ } else
+ j = SHF_WR;
+
+ /* shf_fdopen cannot fail, so no fd leak */
+ tp->shf = shf_fdopen(i, j, NULL);
+
+ maketemp_out:
+ tp->next = *tlist;
+ *tlist = tp;
+ return (tp);
+}
+
+/*
+ * We use a similar collision resolution algorithm as Python 2.5.4
+ * but with a slightly tweaked implementation written from scratch.
+ */
+
+#define INIT_TBLSHIFT 3 /* initial table shift (2^3 = 8) */
+#define PERTURB_SHIFT 5 /* see Python 2.5.4 Objects/dictobject.c */
+
+static void tgrow(struct table *);
+static int tnamecmp(const void *, const void *);
+
+static void
+tgrow(struct table *tp)
+{
+ size_t i, j, osize, mask, perturb;
+ struct tbl *tblp, **pp;
+ struct tbl **ntblp, **otblp = tp->tbls;
+
+ 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 very full when reaching its size limit */
+ tp->nfree = (tp->tshift == 30) ? 0x3FFF0000UL :
+ /* but otherwise, only 75% */
+ ((i * 3) / 4);
+ tp->tbls = ntblp;
+ if (otblp == NULL)
+ return;
+
+ 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;
+ 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 & mask];
+ if (*pp != NULL)
+ goto find_next_empty_slot;
+ /* found an empty hash table slot */
+ *pp = tblp;
+ tp->nfree--;
+ } else if (!(tblp->flag & FINUSE)) {
+ afree(tblp, tp->areap);
+ }
+ }
+ afree(otblp, tp->areap);
+}
+
+void
+ktinit(Area *ap, struct table *tp, uint8_t initshift)
+{
+ tp->areap = ap;
+ tp->tbls = NULL;
+ tp->tshift = ((initshift > INIT_TBLSHIFT) ?
+ initshift : INIT_TBLSHIFT) - 1;
+ tgrow(tp);
+}
+
+/* table, name (key) to search for, hash(name), rv pointer to tbl ptr */
+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 = ((size_t)1 << (tp->tshift)) - 1;
+ /* search for hash table slot matching name */
+ j = perturb = h;
+ goto find_first_slot;
+ find_next_slot:
+ j = (j << 2) + j + perturb + 1;
+ perturb >>= PERTURB_SHIFT;
+ find_first_slot:
+ pp = &tp->tbls[j & mask];
+ if ((p = *pp) != NULL && (p->ua.hval != h || !(p->flag & DEFINED) ||
+ strcmp(p->name, name)))
+ goto find_next_slot;
+ /* p == NULL if not found, correct found entry otherwise */
+ if (ppp)
+ *ppp = pp;
+ return (p);
+}
+
+/* table, name (key) to enter, hash(n) */
+struct tbl *
+ktenter(struct table *tp, const char *n, uint32_t h)
+{
+ struct tbl **pp, *p;
+ size_t len;
+
+ Search:
+ if ((p = ktscan(tp, n, h, &pp)))
+ return (p);
+
+ if (tp->nfree == 0) {
+ /* too full */
+ tgrow(tp);
+ goto Search;
+ }
+
+ /* create new tbl entry */
+ 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;
+ p->ua.hval = h;
+ p->u2.field = 0;
+ p->u.array = NULL;
+ memcpy(p->name, n, len);
+
+ /* enter in tp->tbls */
+ tp->nfree--;
+ *pp = p;
+ return (p);
+}
+
+void
+ktwalk(struct tstate *ts, struct table *tp)
+{
+ ts->left = (size_t)1 << (tp->tshift);
+ ts->next = tp->tbls;
+}
+
+struct tbl *
+ktnext(struct tstate *ts)
+{
+ while (--ts->left >= 0) {
+ struct tbl *p = *ts->next++;
+ if (p != NULL && (p->flag & DEFINED))
+ return (p);
+ }
+ return (NULL);
+}
+
+static int
+tnamecmp(const void *p1, const void *p2)
+{
+ const struct tbl *a = *((const struct tbl * const *)p1);
+ const struct tbl *b = *((const struct tbl * const *)p2);
+
+ return (ascstrcmp(a->name, b->name));
+}
+
+struct tbl **
+ktsort(struct table *tp)
+{
+ size_t i;
+ struct tbl **p, **sp, **dp;
+
+ /*
+ * 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 */
+ while (i--)
+ if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) ||
+ ((*dp)->flag & ARRAY)))
+ dp++;
+ qsort(p, (i = dp - p), sizeof(struct tbl *), tnamecmp);
+ p[i] = NULL;
+ return (p);
+}
+
+#ifdef SIGWINCH
+static void
+x_sigwinch(int sig MKSH_A_UNUSED)
+{
+ /* this runs inside interrupt context, with errno saved */
+
+ got_winch = 1;
+}
+#endif
+
+#ifdef DF
+void
+DF(const char *fmt, ...)
+{
+ va_list args;
+ struct timeval tv;
+ mirtime_mjd mjd;
+
+ mksh_lockfd(shl_dbg_fd);
+ mksh_TIME(tv);
+ timet2mjd(&mjd, tv.tv_sec);
+ shf_fprintf(shl_dbg, "[%02u:%02u:%02u (%u) %u.%06u] ",
+ (unsigned)mjd.sec / 3600, ((unsigned)mjd.sec / 60) % 60,
+ (unsigned)mjd.sec % 60, (unsigned)getpid(),
+ (unsigned)tv.tv_sec, (unsigned)tv.tv_usec);
+ va_start(args, fmt);
+ shf_vfprintf(shl_dbg, fmt, args);
+ va_end(args);
+ shf_putc('\n', shl_dbg);
+ shf_flush(shl_dbg);
+ mksh_unlkfd(shl_dbg_fd);
+}
+#endif
+
+void
+x_mkraw(int fd, mksh_ttyst *ocb, bool forread)
+{
+ mksh_ttyst cb;
+
+ if (ocb)
+ mksh_tcget(fd, ocb);
+ else
+ ocb = &tty_state;
+
+ cb = *ocb;
+ if (forread) {
+ cb.c_iflag &= ~(ISTRIP);
+ cb.c_lflag &= ~(ICANON) | ECHO;
+ } else {
+ cb.c_iflag &= ~(INLCR | ICRNL | ISTRIP);
+ 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 and OSF/1 process 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;
+
+ mksh_tcset(fd, &cb);
+}
+
+#ifdef MKSH_ENVDIR
+static void
+init_environ(void)
+{
+ char *xp;
+ ssize_t n;
+ XString xs;
+ struct shf *shf;
+ DIR *dirp;
+ struct dirent *dent;
+
+ if ((dirp = opendir(MKSH_ENVDIR)) == NULL) {
+ warningf(false, "cannot read environment from %s: %s",
+ MKSH_ENVDIR, cstrerror(errno));
+ return;
+ }
+ XinitN(xs, 256, ATEMP);
+ read_envfile:
+ errno = 0;
+ if ((dent = readdir(dirp)) != NULL) {
+ if (skip_varname(dent->d_name, true)[0] == '\0') {
+ strpathx(xp, MKSH_ENVDIR, dent->d_name, 1);
+ if (!(shf = shf_open(xp, O_RDONLY, 0, 0))) {
+ warningf(false,
+ "cannot read environment %s from %s: %s",
+ dent->d_name, MKSH_ENVDIR,
+ cstrerror(errno));
+ goto read_envfile;
+ }
+ afree(xp, ATEMP);
+ n = strlen(dent->d_name);
+ xp = Xstring(xs, xp);
+ XcheckN(xs, xp, n + 32);
+ memcpy(xp, dent->d_name, n);
+ xp += n;
+ *xp++ = '=';
+ while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+ xp += n;
+ if (Xnleft(xs, xp) <= 0)
+ XcheckN(xs, xp, Xlength(xs, xp));
+ }
+ if (n < 0) {
+ warningf(false,
+ "cannot read environment %s from %s: %s",
+ dent->d_name, MKSH_ENVDIR,
+ cstrerror(shf_errno(shf)));
+ } else {
+ *xp = '\0';
+ xp = Xstring(xs, xp);
+ rndpush(xp);
+ typeset(xp, IMPORT | EXPORT, 0, 0, 0);
+ }
+ shf_close(shf);
+ }
+ goto read_envfile;
+ } else if (errno)
+ warningf(false, "cannot read environment from %s: %s",
+ MKSH_ENVDIR, cstrerror(errno));
+ closedir(dirp);
+ Xfree(xs, xp);
+}
+#else
+extern char **environ;
+
+static void
+init_environ(void)
+{
+ const char **wp;
+
+ if (environ == NULL)
+ return;
+
+ wp = (const char **)environ;
+ while (*wp != NULL) {
+ rndpush(*wp);
+ typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
+ ++wp;
+ }
+}
+#endif
+
+#ifdef MKSH_EARLY_LOCALE_TRACKING
+void
+recheck_ctype(void)
+{
+ const char *ccp;
+ uint8_t old_utfmode = UTFMODE;
+
+ 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);
+#if HAVE_SETLOCALE_CTYPE
+ ccp = setlocale(LC_CTYPE, ccp);
+#if HAVE_LANGINFO_CODESET
+ if (!isuc(ccp))
+ ccp = nl_langinfo(CODESET);
+#endif
+ if (isuc(ccp))
+ UTFMODE = 1;
+#endif
+
+ if (Flag(FPOSIX) && UTFMODE && !old_utfmode)
+ warningf(true, "early locale tracking enabled UTF-8 mode while in POSIX mode, you are now noncompliant");
+}
+#endif
diff --git a/shells/mksh/files/mirhash.h b/shells/mksh/files/mirhash.h
new file mode 100644
index 00000000000..0105b2296e0
--- /dev/null
+++ b/shells/mksh/files/mirhash.h
@@ -0,0 +1,226 @@
+/*-
+ * Copyright © 2011, 2014, 2015
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including unâ€
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person’s immediate fault when using the work as intended.
+ *-
+ * This file provides BAFH (Better Avalanche for the Jenkins Hash) as
+ * inline macro bodies that operate on “register uint32_t” variables,
+ * with variants that use their local intermediate registers.
+ *
+ * Usage note for BAFH with entropy distribution: input up to 4 bytes
+ * is best combined into a 32-bit unsigned integer, which is then run
+ * through BAFHFinish_reg for mixing and then used as context instead
+ * of 0. Longer input should be handled the same: take the first four
+ * bytes as IV after mixing then add subsequent bytes the same way.
+ * This needs counting input bytes and is endian-dependent, thus not,
+ * for speed reasons, specified for the regular stable hash, but very
+ * much recommended if the actual output value may differ across runs
+ * (so is using a random value instead of 0 for the IV).
+ *-
+ * Little quote gem:
+ * We are looking into it. Changing the core
+ * hash function in PHP isn't a trivial change
+ * and will take us some time.
+ * -- Rasmus Lerdorf
+ */
+
+#ifndef SYSKERN_MIRHASH_H
+#define SYSKERN_MIRHASH_H 1
+#define SYSKERN_MIRHASH_BAFH
+
+#include <sys/types.h>
+
+__RCSID("$MirOS: src/bin/mksh/mirhash.h,v 1.6 2015/11/29 17:05:02 tg Exp $");
+
+/*-
+ * BAFH itself is defined by the following primitives:
+ *
+ * • BAFHInit(ctx) initialises the hash context, which consists of a
+ * sole 32-bit unsigned integer (ideally in a register), to 0.
+ * It is possible to use any initial value out of [0; 2³²[ – which
+ * is, in fact, recommended if using BAFH for entropy distribution
+ * – but for a regular stable hash, the IV 0 is needed.
+ *
+ * • BAFHUpdateOctet(ctx,val) compresses the unsigned 8-bit quantity
+ * into the hash context. The algorithm used is Jenkins’ one-at-a-
+ * time, except that an additional constant 1 is added so that, if
+ * the context is (still) zero, adding a NUL byte is not ignored.
+ *
+ * • BAFHror(eax,cl) evaluates to the unsigned 32-bit integer “eax”,
+ * rotated right by “cl” â [0; 31] (no casting, be careful!) where
+ * “eax” must be uint32_t and “cl” an in-range integer.
+ *
+ * • BAFHFinish(ctx) avalanches the context around so every sub-byte
+ * depends on all input octets; afterwards, the context variable’s
+ * value is the hash output. BAFH does not use any padding, nor is
+ * the input length added; this is due to the common use case (for
+ * quick entropy distribution and use with a hashtable).
+ * Warning: BAFHFinish uses the MixColumn algorithm of AES – which
+ * is reversible (to avoid introducing funnels and reducing entroâ€
+ * py), so blinding may need to be employed for some uses, e.g. in
+ * mksh, after a fork.
+ *
+ * The BAFHUpdateOctet and BAFHFinish are available in two flavours:
+ * suffixed with _reg (assumes the context is in a register) or _mem
+ * (which doesn’t).
+ *
+ * The following high-level macros (with _reg and _mem variants) are
+ * available:
+ *
+ * • BAFHUpdateMem(ctx,buf,len) adds a memory block to a context.
+ * • BAFHUpdateStr(ctx,buf) is equivalent to using len=strlen(buf).
+ * • BAFHHostMem(ctx,buf,len) calculates the hash of the memory bufâ€
+ * fer using the first 4 octets (mixed) for IV, as outlined above;
+ * the result is endian-dependent; “ctx” assumed to be a register.
+ * • BAFHHostStr(ctx,buf) does the same for C strings.
+ *
+ * All macros may use ctx multiple times in their expansion, but all
+ * other arguments are always evaluated at most once except BAFHror.
+ *
+ * To stay portable, never use the BAFHHost*() macros (these are for
+ * host-local entropy shuffling), and encode numbers using ULEB128.
+ */
+
+#define BAFHInit(h) do { \
+ (h) = 0; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHUpdateOctet_reg(h,b) do { \
+ (h) += (uint8_t)(b); \
+ ++(h); \
+ (h) += (h) << 10; \
+ (h) ^= (h) >> 6; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHUpdateOctet_mem(m,b) do { \
+ register uint32_t BAFH_h = (m); \
+ \
+ BAFHUpdateOctet_reg(BAFH_h, (b)); \
+ (m) = BAFH_h; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHror(eax,cl) (((eax) >> (cl)) | ((eax) << (32 - (cl))))
+
+#define BAFHFinish_reg(h) do { \
+ register uint32_t BAFHFinish_v; \
+ \
+ BAFHFinish_v = ((h) >> 7) & 0x01010101U; \
+ BAFHFinish_v += BAFHFinish_v << 1; \
+ BAFHFinish_v += BAFHFinish_v << 3; \
+ BAFHFinish_v ^= ((h) << 1) & 0xFEFEFEFEU; \
+ \
+ BAFHFinish_v ^= BAFHror(BAFHFinish_v, 8); \
+ BAFHFinish_v ^= ((h) = BAFHror((h), 8)); \
+ BAFHFinish_v ^= ((h) = BAFHror((h), 8)); \
+ (h) = BAFHror((h), 8) ^ BAFHFinish_v; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHFinish_mem(m) do { \
+ register uint32_t BAFHFinish_v, BAFH_h = (m); \
+ \
+ BAFHFinish_v = (BAFH_h >> 7) & 0x01010101U; \
+ BAFHFinish_v += BAFHFinish_v << 1; \
+ BAFHFinish_v += BAFHFinish_v << 3; \
+ BAFHFinish_v ^= (BAFH_h << 1) & 0xFEFEFEFEU; \
+ \
+ BAFHFinish_v ^= BAFHror(BAFHFinish_v, 8); \
+ BAFHFinish_v ^= (BAFH_h = BAFHror(BAFH_h, 8)); \
+ BAFHFinish_v ^= (BAFH_h = BAFHror(BAFH_h, 8)); \
+ (m) = BAFHror(BAFH_h, 8) ^ BAFHFinish_v; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHUpdateMem_reg(h,p,z) do { \
+ register const uint8_t *BAFHUpdate_p; \
+ register size_t BAFHUpdate_z = (z); \
+ \
+ BAFHUpdate_p = (const void *)(p); \
+ while (BAFHUpdate_z--) \
+ BAFHUpdateOctet_reg((h), *BAFHUpdate_p++); \
+} while (/* CONSTCOND */ 0)
+
+/* meh should have named them _r/m but that’s not valid C */
+#define BAFHUpdateMem_mem(m,p,z) do { \
+ register uint32_t BAFH_h = (m); \
+ \
+ BAFHUpdateMem_reg(BAFH_h, (p), (z)); \
+ (m) = BAFH_h; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHUpdateStr_reg(h,s) do { \
+ register const uint8_t *BAFHUpdate_s; \
+ register uint8_t BAFHUpdate_c; \
+ \
+ BAFHUpdate_s = (const void *)(s); \
+ while ((BAFHUpdate_c = *BAFHUpdate_s++) != 0) \
+ BAFHUpdateOctet_reg((h), BAFHUpdate_c); \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHUpdateStr_mem(m,s) do { \
+ register uint32_t BAFH_h = (m); \
+ \
+ BAFHUpdateStr_reg(BAFH_h, (s)); \
+ (m) = BAFH_h; \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHHostMem(h,p,z) do { \
+ register const uint8_t *BAFHUpdate_p; \
+ register size_t BAFHUpdate_z = (z); \
+ size_t BAFHHost_z; \
+ union { \
+ uint8_t as_u8[4]; \
+ uint32_t as_u32; \
+ } BAFHHost_v; \
+ \
+ BAFHUpdate_p = (const void *)(p); \
+ BAFHHost_v.as_u32 = 0; \
+ BAFHHost_z = BAFHUpdate_z < 4 ? BAFHUpdate_z : 4; \
+ memcpy(BAFHHost_v.as_u8, BAFHUpdate_p, BAFHHost_z); \
+ BAFHUpdate_p += BAFHHost_z; \
+ BAFHUpdate_z -= BAFHHost_z; \
+ (h) = BAFHHost_v.as_u32; \
+ BAFHFinish_reg(h); \
+ while (BAFHUpdate_z--) \
+ BAFHUpdateOctet_reg((h), *BAFHUpdate_p++); \
+ BAFHFinish_reg(h); \
+} while (/* CONSTCOND */ 0)
+
+#define BAFHHostStr(h,s) do { \
+ register const uint8_t *BAFHUpdate_s; \
+ register uint8_t BAFHUpdate_c; \
+ union { \
+ uint8_t as_u8[4]; \
+ uint32_t as_u32; \
+ } BAFHHost_v; \
+ \
+ BAFHUpdate_s = (const void *)(s); \
+ BAFHHost_v.as_u32 = 0; \
+ if ((BAFHHost_v.as_u8[0] = *BAFHUpdate_s) != 0) \
+ ++BAFHUpdate_s; \
+ if ((BAFHHost_v.as_u8[1] = *BAFHUpdate_s) != 0) \
+ ++BAFHUpdate_s; \
+ if ((BAFHHost_v.as_u8[2] = *BAFHUpdate_s) != 0) \
+ ++BAFHUpdate_s; \
+ if ((BAFHHost_v.as_u8[3] = *BAFHUpdate_s) != 0) \
+ ++BAFHUpdate_s; \
+ (h) = BAFHHost_v.as_u32; \
+ BAFHFinish_reg(h); \
+ while ((BAFHUpdate_c = *BAFHUpdate_s++) != 0) \
+ BAFHUpdateOctet_reg((h), BAFHUpdate_c); \
+ BAFHFinish_reg(h); \
+} while (/* CONSTCOND */ 0)
+
+#endif
diff --git a/shells/mksh/files/misc.c b/shells/mksh/files/misc.c
new file mode 100644
index 00000000000..b19a2531ea7
--- /dev/null
+++ b/shells/mksh/files/misc.c
@@ -0,0 +1,2633 @@
+/* $OpenBSD: misc.c,v 1.41 2015/09/10 22:48:58 nicm Exp $ */
+/* $OpenBSD: path.c,v 1.13 2015/09/05 09:47:08 jsg Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+ * 2020
+ * mirabilos <m@mirbsd.org>
+ * Copyright (c) 2015
+ * Daniel Richard G. <skunk@iSKUNK.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.
+ */
+
+#include "sh.h"
+#if !HAVE_GETRUSAGE
+#include <sys/times.h>
+#endif
+#if HAVE_GRP_H
+#include <grp.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.299 2020/05/16 22:19:58 tg Exp $");
+
+#define KSH_CHVT_FLAG
+#ifdef MKSH_SMALL
+#undef KSH_CHVT_FLAG
+#endif
+#ifdef TIOCSCTTY
+#define KSH_CHVT_CODE
+#define KSH_CHVT_FLAG
+#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) MKSH_A_PURE;
+static int do_gmatch(const unsigned char *, const unsigned char *,
+ const unsigned char *, const unsigned char *,
+ const unsigned char *) MKSH_A_PURE;
+static const unsigned char *gmatch_cclass(const unsigned char *, unsigned char)
+ MKSH_A_PURE;
+#ifdef KSH_CHVT_CODE
+static void chvt(const Getopt *);
+#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
+
+
+/* called from XcheckN() to grow buffer */
+char *
+Xcheck_grow(XString *xsp, const char *xp, size_t more)
+{
+ const char *old_beg = xsp->beg;
+
+ 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));
+}
+
+
+#define SHFLAGS_DEFNS
+#define FN(sname,cname,flags,ochar) \
+ static const struct { \
+ /* character flag (if any) */ \
+ char c; \
+ /* OF_* */ \
+ unsigned char optflags; \
+ /* long name of option */ \
+ char name[sizeof(sname)]; \
+ } shoptione_ ## cname = { \
+ ochar, flags, sname \
+ };
+#include "sh_flags.gen"
+
+#define OFC(i) (options[i][-2])
+#define OFF(i) (((const unsigned char *)options[i])[-1])
+#define OFN(i) (options[i])
+
+const char * const options[] = {
+#define SHFLAGS_ITEMS
+#include "sh_flags.gen"
+};
+
+/*
+ * translate -o option into F* constant (also used for test -o option)
+ */
+size_t
+option(const char *n)
+{
+ size_t i = 0;
+
+ if (ctype(n[0], C_MINUS | C_PLUS) && n[1] && !n[2])
+ while (i < NELEM(options)) {
+ if (OFC(i) == n[1])
+ return (i);
+ ++i;
+ }
+ else
+ while (i < NELEM(options)) {
+ if (!strcmp(OFN(i), n))
+ return (i);
+ ++i;
+ }
+
+ return ((size_t)-1);
+}
+
+struct options_info {
+ int opt_width;
+ int opts[NELEM(options)];
+};
+
+static void options_fmt_entry(char *, size_t, unsigned int, const void *);
+static int printoptions(bool);
+static int printoption(size_t);
+
+/* format a single select menu item */
+static void
+options_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg)
+{
+ const struct options_info *oi = (const struct options_info *)arg;
+
+ shf_snprintf(buf, buflen, "%-*s %s",
+ oi->opt_width, OFN(oi->opts[i]),
+ Flag(oi->opts[i]) ? "on" : "off");
+}
+
+static int
+printoption(size_t i)
+{
+ if (Flag(i) == baseline_flags[i])
+ return (0);
+ if (!OFN(i)[0]) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ bi_errorf(Tf_sd, "change in unnamed option", (int)i);
+#endif
+ return (1);
+ }
+ if (Flag(i) != 0 && Flag(i) != 1) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ bi_errorf(Tf_s_sD_s, Tdo, OFN(i), "not 0 or 1");
+#endif
+ return (1);
+ }
+ shprintf(Tf__s_s, Flag(i) ? Tdo : Tpo, OFN(i));
+ return (0);
+}
+
+static int
+printoptions(bool verbose)
+{
+ size_t i = 0;
+ int rv = 0;
+
+ if (verbose) {
+ size_t n = 0, len, octs = 0;
+ struct options_info oi;
+ struct columnise_opts co;
+
+ /* verbose version */
+ shf_puts("Current option settings\n", shl_stdout);
+
+ oi.opt_width = 0;
+ while (i < NELEM(options)) {
+ if ((len = strlen(OFN(i)))) {
+ oi.opts[n++] = i;
+ if (len > octs)
+ octs = len;
+ len = utf_mbswidth(OFN(i));
+ if ((int)len > oi.opt_width)
+ oi.opt_width = (int)len;
+ }
+ ++i;
+ }
+ co.shf = shl_stdout;
+ co.linesep = '\n';
+ co.prefcol = co.do_last = true;
+ print_columns(&co, n, options_fmt_entry, &oi,
+ octs + 4, oi.opt_width + 4);
+ } else {
+ /* short version like AT&T ksh93 */
+ shf_puts(Tset, shl_stdout);
+ shf_puts(To_o_reset, shl_stdout);
+ printoption(FSH);
+ printoption(FPOSIX);
+ while (i < FNFLAGS) {
+ if (i != FSH && i != FPOSIX)
+ rv |= printoption(i);
+ ++i;
+ }
+ shf_putc('\n', shl_stdout);
+ }
+ return (rv);
+}
+
+char *
+getoptions(void)
+{
+ size_t i = 0;
+ char c, m[(int)FNFLAGS + 1];
+ char *cp = m;
+
+ while (i < NELEM(options)) {
+ if ((c = OFC(i)) && Flag(i))
+ *cp++ = c;
+ ++i;
+ }
+ strndupx(cp, m, cp - m, ATEMP);
+ return (cp);
+}
+
+/* change a Flag(*) value; takes care of special actions */
+void
+change_flag(enum sh_flag f, int what, bool newset)
+{
+ unsigned char oldval = Flag(f);
+ unsigned char newval = (newset ? 1 : 0);
+
+ if (f == FXTRACE) {
+ change_xtrace(newval, true);
+ return;
+ } else if (f == FPRIVILEGED) {
+ if (!oldval)
+ /* no getting back dropped privs */
+ return;
+ else if (!newval) {
+ /* turning off -p */
+ kshegid = kshgid;
+ ksheuid = kshuid;
+ } else if (oldval != 3)
+ /* nor going full sugid */
+ goto change_flag;
+
+ /* +++ set group IDs +++ */
+#if HAVE_SETRESUGID
+ DO_SETUID(setresgid, (kshegid, kshegid, kshgid));
+#else /* !HAVE_SETRESUGID */
+ /* setgid, setegid don't EAGAIN on Linux */
+ setgid(kshegid);
+#ifndef MKSH__NO_SETEUGID
+ setegid(kshegid);
+#endif /* !MKSH__NO_SETEUGID */
+#endif /* !HAVE_SETRESUGID */
+
+ /* +++ wipe groups vector +++ */
+#if HAVE_SETGROUPS
+ /* setgroups doesn't EAGAIN on Linux */
+ setgroups(0, NULL);
+#endif /* HAVE_SETGROUPS */
+
+ /* +++ set user IDs +++ */
+#if HAVE_SETRESUGID
+ DO_SETUID(setresuid, (ksheuid, ksheuid, kshuid));
+#else /* !HAVE_SETRESUGID */
+ /* seteuid doesn't EAGAIN on Linux */
+ DO_SETUID(setuid, (ksheuid));
+#ifndef MKSH__NO_SETEUGID
+ seteuid(ksheuid);
+#endif /* !MKSH__NO_SETEUGID */
+#endif /* !HAVE_SETRESUGID */
+
+ /* +++ privs changed +++ */
+ } else if ((f == FPOSIX || f == FSH) && newval) {
+ /* Turning on -o posix? */
+ if (f == FPOSIX)
+ /* C locale required for compliance */
+ UTFMODE = 0;
+ /* Turning on -o posix or -o sh? */
+ Flag(FBRACEEXPAND) = 0;
+#ifndef MKSH_NO_CMDLINE_EDITING
+ } else if ((f == FEMACS ||
+#if !MKSH_S_NOVI
+ f == FVI ||
+#endif
+ f == FGMACS) && newval) {
+#if !MKSH_S_NOVI
+ Flag(FVI) = 0;
+#endif
+ Flag(FEMACS) = Flag(FGMACS) = 0;
+#endif
+ }
+
+ change_flag:
+ Flag(f) = newval;
+
+ if (f == FTALKING) {
+ /* Changing interactive flag? */
+ if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
+ Flag(FTALKING_I) = newval;
+#ifndef MKSH_UNEMPLOYED
+ } else if (f == FMONITOR) {
+ if (what != OF_CMDLINE && newval != oldval)
+ j_change();
+#endif
+ }
+}
+
+void
+change_xtrace(unsigned char newval, bool dosnapshot)
+{
+ static bool in_xtrace;
+
+ if (in_xtrace)
+ return;
+
+ if (!dosnapshot && newval == Flag(FXTRACE))
+ return;
+
+ if (Flag(FXTRACE) == 2) {
+ shf_putc('\n', shl_xtrace);
+ Flag(FXTRACE) = 1;
+ shf_flush(shl_xtrace);
+ }
+
+ if (!dosnapshot && Flag(FXTRACE) == 1)
+ switch (newval) {
+ case 1:
+ return;
+ case 2:
+ goto changed_xtrace;
+ }
+
+ shf_flush(shl_xtrace);
+ if (shl_xtrace->fd != 2)
+ close(shl_xtrace->fd);
+ if (!newval || (shl_xtrace->fd = savefd(2)) == -1)
+ shl_xtrace->fd = 2;
+
+ changed_xtrace:
+ if ((Flag(FXTRACE) = newval) == 2) {
+ in_xtrace = true;
+ Flag(FXTRACE) = 0;
+ shf_puts(substitute(str_val(global("PS4")), 0), shl_xtrace);
+ Flag(FXTRACE) = 2;
+ in_xtrace = false;
+ }
+}
+
+/*
+ * 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,
+ /* OF_FIRSTTIME, OF_CMDLINE, or OF_SET */
+ int what,
+ bool *setargsp)
+{
+ static const char cmd_opts[] =
+#define SHFLAGS_NOT_SET
+#define SHFLAGS_OPTCS
+#include "sh_flags.gen"
+#undef SHFLAGS_NOT_SET
+ ;
+ static const char set_opts[] =
+#define SHFLAGS_NOT_CMD
+#define SHFLAGS_OPTCS
+#include "sh_flags.gen"
+#undef SHFLAGS_NOT_CMD
+ ;
+ bool set;
+ const char *opts = what == OF_CMDLINE || what == OF_FIRSTTIME ?
+ cmd_opts : set_opts;
+ const char *array = NULL;
+ Getopt go;
+ size_t i;
+ int optc, arrayset = 0;
+ bool sortargs = false;
+ bool fcompatseen = false;
+
+ ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
+ while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
+ set = tobool(!(go.info & GI_PLUS));
+ switch (optc) {
+ case 'A':
+ if (what == OF_FIRSTTIME)
+ break;
+ arrayset = set ? 1 : -1;
+ array = go.optarg;
+ break;
+
+ case 'o':
+ if (what == OF_FIRSTTIME)
+ break;
+ if (go.optarg == NULL) {
+ /*
+ * lone -o: print options
+ *
+ * Note that on the command line, -o requires
+ * an option (ie, can't get here if what is
+ * OF_CMDLINE).
+ */
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ if (!set && !baseline_flags[(int)FNFLAGS]) {
+ bi_errorf(Tf_s_s, "too early",
+ Tset_po);
+ return (-1);
+ }
+#endif
+ if (printoptions(set))
+ return (-1);
+ break;
+ }
+ i = option(go.optarg);
+ if ((i == FPOSIX || i == FSH) && set && !fcompatseen) {
+ /*
+ * If running 'set -o posix' or
+ * 'set -o sh', turn off the other;
+ * if running 'set -o posix -o sh'
+ * allow both to be set though.
+ */
+ Flag(FPOSIX) = 0;
+ Flag(FSH) = 0;
+ fcompatseen = true;
+ }
+ if ((i != (size_t)-1) && (set ? 1U : 0U) == Flag(i))
+ /*
+ * 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.
+ */
+ ;
+ else if ((i != (size_t)-1) && (OFF(i) & what))
+ change_flag((enum sh_flag)i, what, set);
+ else if (!strcmp(go.optarg, To_reset)) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ if (!baseline_flags[(int)FNFLAGS]) {
+ bi_errorf(Tf_ss, "too early",
+ To_o_reset);
+ return (-1);
+ }
+#endif
+ /*
+ * ordering, with respect to side effects,
+ * was ensured above by printoptions
+ */
+ for (i = 0; i < FNFLAGS; ++i)
+ if (Flag(i) != baseline_flags[i])
+ change_flag((enum sh_flag)i,
+ what, baseline_flags[i]);
+ } else {
+ bi_errorf(Tf_sD_s, go.optarg,
+ Tunknown_option);
+ return (-1);
+ }
+ break;
+
+#ifdef KSH_CHVT_FLAG
+ case 'T':
+ if (what != OF_FIRSTTIME)
+ break;
+#ifndef KSH_CHVT_CODE
+ errorf("no TIOCSCTTY ioctl");
+#else
+ change_flag(FTALKING, OF_CMDLINE, true);
+ chvt(&go);
+ break;
+#endif
+#endif
+
+ case '?':
+ return (-1);
+
+ default:
+ if (what == OF_FIRSTTIME)
+ break;
+ /* -s: sort positional params (AT&T ksh stupidity) */
+ if (what == OF_SET && optc == 's') {
+ sortargs = true;
+ break;
+ }
+ for (i = 0; i < NELEM(options); i++)
+ if (optc == OFC(i) &&
+ (what & OFF(i))) {
+ change_flag((enum sh_flag)i, what, set);
+ break;
+ }
+ if (i == NELEM(options))
+ internal_errorf("parse_args: '%c'", optc);
+ }
+ }
+ if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
+ ctype(argv[go.optind][0], C_MINUS | C_PLUS) &&
+ argv[go.optind][1] == '\0') {
+ /* lone - clears -v and -x flags */
+ if (argv[go.optind][0] == '-') {
+ Flag(FVERBOSE) = 0;
+ change_xtrace(0, false);
+ }
+ /* set skips lone - or + option */
+ go.optind++;
+ }
+ if (setargsp)
+ /* -- means set $#/$* even if there are no arguments */
+ *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
+ argv[go.optind]);
+
+ if (arrayset) {
+ const char *ccp = NULL;
+
+ if (array && *array)
+ ccp = skip_varname(array, false);
+ if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
+ bi_errorf(Tf_sD_s, array, Tnot_ident);
+ return (-1);
+ }
+ }
+ if (sortargs) {
+ for (i = go.optind; argv[i]; i++)
+ ;
+ qsort(&argv[go.optind], i - go.optind, sizeof(void *),
+ ascpstrcmp);
+ }
+ if (arrayset)
+ go.optind += set_array(array, tobool(arrayset > 0),
+ argv + go.optind);
+
+ return (go.optind);
+}
+
+/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */
+int
+getn(const char *s, int *ai)
+{
+ char c;
+ mksh_ari_u num;
+ bool neg = false;
+
+ num.u = 0;
+
+ do {
+ c = *s++;
+ } while (ctype(c, C_SPACE));
+
+ switch (c) {
+ case '-':
+ neg = true;
+ /* FALLTHROUGH */
+ case '+':
+ c = *s++;
+ break;
+ }
+
+ do {
+ if (!ctype(c, C_DIGIT))
+ /* not numeric */
+ return (0);
+ if (num.u > 214748364U)
+ /* overflow on multiplication */
+ return (0);
+ num.u = num.u * 10U + (unsigned int)ksh_numdig(c);
+ /* now: num.u <= 2147483649U */
+ } while ((c = *s++));
+
+ if (num.u > (neg ? 2147483648U : 2147483647U))
+ /* overflow for signed 32-bit int */
+ return (0);
+
+ if (neg)
+ num.u = -num.u;
+ *ai = num.i;
+ return (1);
+}
+
+/**
+ * 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 = strnul(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 -------- */
+
+/*
+ * int gmatch(string, pattern)
+ * char *string, *pattern;
+ *
+ * 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);
+
+ pe = strnul(p);
+ /*
+ * isfile is false iff no syntax check has been done on
+ * the pattern. If check fails, just do a strcmp().
+ */
+ if (!isfile && !has_globbing(p)) {
+ size_t len = pe - p + 1;
+ char tbuf[64];
+ char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP);
+ debunk(t, p, len);
+ return (!strcmp(t, s));
+ }
+ se = strnul(s);
+
+ /*
+ * since the do_gmatch() engine sucks so much, we must do some
+ * pattern simplifications
+ */
+ pnew = simplify_gmatch_pattern((const unsigned char *)p);
+ pe = strnul(pnew);
+
+ rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se,
+ (const unsigned char *)pnew, (const unsigned char *)pe,
+ (const unsigned char *)s);
+ afree(pnew, ATEMP);
+ return (rv);
+}
+
+/**
+ * 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 ]
+ * - imbalanced $(...) expression
+ * - [...] 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 ?
+ */
+bool
+has_globbing(const char *pat)
+{
+ unsigned char c, subc;
+ bool saw_glob = false;
+ unsigned int nest = 0;
+ const unsigned char *p = (const unsigned char *)pat;
+ const unsigned char *s;
+
+ while ((c = *p++)) {
+ /* regular character? ok. */
+ if (!ISMAGIC(c))
+ continue;
+ /* MAGIC + NUL? abort. */
+ if (!(c = *p++))
+ return (false);
+ /* some specials */
+ if (ord(c) == ORD('*') || ord(c) == ORD('?')) {
+ /* easy glob, accept */
+ saw_glob = true;
+ } else if (ord(c) == ORD('[')) {
+ /* bracket expression; eat negation and initial ] */
+ if (ISMAGIC(p[0]) && ord(p[1]) == ORD('!'))
+ p += 2;
+ if (ISMAGIC(p[0]) && ord(p[1]) == ORD(']'))
+ p += 2;
+ /* check next string part */
+ s = p;
+ while ((c = *s++)) {
+ /* regular chars are ok */
+ if (!ISMAGIC(c))
+ continue;
+ /* MAGIC + NUL cannot happen */
+ if (!(c = *s++))
+ return (false);
+ /* terminating bracket? */
+ if (ord(c) == ORD(']')) {
+ /* accept and continue */
+ p = s;
+ saw_glob = true;
+ break;
+ }
+ /* sub-bracket expressions */
+ if (ord(c) == ORD('[') && (
+ /* collating element? */
+ ord(*s) == ORD('.') ||
+ /* equivalence class? */
+ ord(*s) == ORD('=') ||
+ /* character class? */
+ ord(*s) == ORD(':'))) {
+ /* must stop with exactly the same c */
+ subc = *s++;
+ /* arbitrarily many chars in betwixt */
+ while ((c = *s++))
+ /* but only this sequence... */
+ if (c == subc && ISMAGIC(*s) &&
+ ord(s[1]) == ORD(']')) {
+ /* accept, terminate */
+ s += 2;
+ break;
+ }
+ /* EOS without: reject bracket expr */
+ if (!c)
+ break;
+ /* continue; */
+ }
+ /* anything else just goes on */
+ }
+ } else if ((c & 0x80) && ctype(c & 0x7F, C_PATMO | C_SPC)) {
+ /* opening pattern */
+ saw_glob = true;
+ ++nest;
+ } else if (ord(c) == ORD(/*(*/ ')')) {
+ /* closing pattern */
+ if (nest)
+ --nest;
+ }
+ }
+ return (saw_glob && !nest);
+}
+
+/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
+static int
+do_gmatch(const unsigned char *s, const unsigned char *se,
+ const unsigned char *p, const unsigned char *pe,
+ const unsigned char *smin)
+{
+ unsigned char sc, pc, sl = 0;
+ const unsigned char *prest, *psub, *pnext;
+ const unsigned char *srest;
+
+ if (s == NULL || p == NULL)
+ return (0);
+ if (s > smin && s <= se)
+ sl = s[-1];
+ while (p < pe) {
+ pc = *p++;
+ sc = s < se ? *s : '\0';
+ s++;
+ if (!ISMAGIC(pc)) {
+ if (sc != pc)
+ return (0);
+ sl = sc;
+ continue;
+ }
+ switch (ord(*p++)) {
+ case ORD('['):
+ /* BSD cclass extension? */
+ if (ISMAGIC(p[0]) && ord(p[1]) == ORD('[') &&
+ ord(p[2]) == ORD(':') &&
+ ctype((pc = p[3]), C_ANGLE) &&
+ ord(p[4]) == ORD(':') &&
+ ISMAGIC(p[5]) && ord(p[6]) == ORD(']') &&
+ ISMAGIC(p[7]) && ord(p[8]) == ORD(']')) {
+ /* zero-length match */
+ --s;
+ p += 9;
+ /* word begin? */
+ if (ord(pc) == ORD('<') &&
+ !ctype(sl, C_ALNUX) &&
+ ctype(sc, C_ALNUX))
+ break;
+ /* word end? */
+ if (ord(pc) == ORD('>') &&
+ ctype(sl, C_ALNUX) &&
+ !ctype(sc, C_ALNUX))
+ break;
+ /* neither */
+ return (0);
+ }
+ if (sc == 0 || (p = gmatch_cclass(p, sc)) == NULL)
+ return (0);
+ break;
+
+ case ORD('?'):
+ if (sc == 0)
+ return (0);
+ if (UTFMODE) {
+ --s;
+ s += utf_ptradj((const void *)s);
+ }
+ break;
+
+ case ORD('*'):
+ if (p == pe)
+ return (1);
+ s--;
+ do {
+ if (do_gmatch(s, se, p, pe, smin))
+ return (1);
+ } while (s++ < se);
+ return (0);
+
+ /**
+ * [+*?@!](pattern|pattern|..)
+ * This is also needed for ${..%..}, etc.
+ */
+
+ /* matches one or more times */
+ case ORD('+') | 0x80:
+ /* matches zero or more times */
+ case ORD('*') | 0x80:
+ if (!(prest = pat_scan(p, pe, false)))
+ return (0);
+ s--;
+ /* take care of zero matches */
+ if (ord(p[-1]) == (0x80 | ORD('*')) &&
+ do_gmatch(s, se, prest, pe, smin))
+ return (1);
+ for (psub = p; ; psub = pnext) {
+ pnext = pat_scan(psub, pe, true);
+ for (srest = s; srest <= se; srest++) {
+ if (do_gmatch(s, srest, psub, pnext - 2, smin) &&
+ (do_gmatch(srest, se, prest, pe, smin) ||
+ (s != srest &&
+ do_gmatch(srest, se, p - 2, pe, smin))))
+ return (1);
+ }
+ if (pnext == prest)
+ break;
+ }
+ return (0);
+
+ /* matches zero or once */
+ case ORD('?') | 0x80:
+ /* matches one of the patterns */
+ case ORD('@') | 0x80:
+ /* simile for @ */
+ case ORD(' ') | 0x80:
+ if (!(prest = pat_scan(p, pe, false)))
+ return (0);
+ s--;
+ /* Take care of zero matches */
+ if (ord(p[-1]) == (0x80 | ORD('?')) &&
+ do_gmatch(s, se, prest, pe, smin))
+ return (1);
+ for (psub = p; ; psub = pnext) {
+ pnext = pat_scan(psub, pe, true);
+ srest = prest == pe ? se : s;
+ for (; srest <= se; srest++) {
+ if (do_gmatch(s, srest, psub, pnext - 2, smin) &&
+ do_gmatch(srest, se, prest, pe, smin))
+ return (1);
+ }
+ if (pnext == prest)
+ break;
+ }
+ return (0);
+
+ /* matches none of the patterns */
+ case ORD('!') | 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, true);
+ if (do_gmatch(s, srest, psub,
+ pnext - 2, smin)) {
+ matched = 1;
+ break;
+ }
+ if (pnext == prest)
+ break;
+ }
+ if (!matched &&
+ do_gmatch(srest, se, prest, pe, smin))
+ return (1);
+ }
+ return (0);
+
+ default:
+ if (sc != p[-1])
+ return (0);
+ break;
+ }
+ sl = sc;
+ }
+ return (s == se);
+}
+
+/*XXX this is a prime example for bsearch or a const hashtable */
+static const struct cclass {
+ const char *name;
+ uint32_t value;
+} cclasses[] = {
+ /* POSIX */
+ { "alnum", C_ALNUM },
+ { "alpha", C_ALPHA },
+ { "blank", C_BLANK },
+ { "cntrl", C_CNTRL },
+ { "digit", C_DIGIT },
+ { "graph", C_GRAPH },
+ { "lower", C_LOWER },
+ { "print", C_PRINT },
+ { "punct", C_PUNCT },
+ { "space", C_SPACE },
+ { "upper", C_UPPER },
+ { "xdigit", C_SEDEC },
+ /* BSD */
+ /* "<" and ">" are handled inline */
+ /* GNU bash */
+ { "ascii", C_ASCII },
+ { "word", C_ALNUX },
+ /* mksh */
+ { "sh_alias", C_ALIAS },
+ { "sh_edq", C_EDQ },
+ { "sh_ifs", C_IFS },
+ { "sh_ifsws", C_IFSWS },
+ { "sh_nl", C_NL },
+ { "sh_quote", C_QUOTE },
+ /* sentinel */
+ { NULL, 0 }
+};
+
+static const unsigned char *
+gmatch_cclass(const unsigned char *pat, unsigned char sc)
+{
+ unsigned char c, subc, lc;
+ const unsigned char *p = pat, *s;
+ bool found = false;
+ bool negated = false;
+ char *subp;
+
+ /* check for negation */
+ if (ISMAGIC(p[0]) && ord(p[1]) == ORD('!')) {
+ p += 2;
+ negated = true;
+ }
+ /* make initial ] non-MAGIC */
+ if (ISMAGIC(p[0]) && ord(p[1]) == ORD(']'))
+ ++p;
+ /* iterate over bracket expression, debunk()ing on the fly */
+ while ((c = *p++)) {
+ nextc:
+ /* non-regular character? */
+ if (ISMAGIC(c)) {
+ /* MAGIC + NUL cannot happen */
+ if (!(c = *p++))
+ break;
+ /* terminating bracket? */
+ if (ord(c) == ORD(']')) {
+ /* accept and return */
+ return (found != negated ? p : NULL);
+ }
+ /* sub-bracket expressions */
+ if (ord(c) == ORD('[') && (
+ /* collating element? */
+ ord(*p) == ORD('.') ||
+ /* equivalence class? */
+ ord(*p) == ORD('=') ||
+ /* character class? */
+ ord(*p) == ORD(':'))) {
+ /* must stop with exactly the same c */
+ subc = *p++;
+ /* save away start of substring */
+ s = p;
+ /* arbitrarily many chars in betwixt */
+ while ((c = *p++))
+ /* but only this sequence... */
+ if (c == subc && ISMAGIC(*p) &&
+ ord(p[1]) == ORD(']')) {
+ /* accept, terminate */
+ p += 2;
+ break;
+ }
+ /* EOS without: reject bracket expr */
+ if (!c)
+ break;
+ /* debunk substring */
+ strndupx(subp, s, p - s - 3, ATEMP);
+ debunk(subp, subp, p - s - 3 + 1);
+ cclass_common:
+ /* whither subexpression */
+ if (ord(subc) == ORD(':')) {
+ const struct cclass *cls = cclasses;
+
+ /* search for name in cclass list */
+ while (cls->name)
+ if (!strcmp(subp, cls->name)) {
+ /* found, match? */
+ if (ctype(sc,
+ cls->value))
+ found = true;
+ /* break either way */
+ break;
+ } else
+ ++cls;
+ /* that's all here */
+ afree(subp, ATEMP);
+ continue;
+ }
+ /* collating element or equivalence class */
+ /* Note: latter are treated as former */
+ if (ctype(subp[0], C_ASCII) && !subp[1])
+ /* [.a.] where a is one ASCII char */
+ c = subp[0];
+ else
+ /* force no match */
+ c = 0;
+ /* no longer needed */
+ afree(subp, ATEMP);
+ } else if (!ISMAGIC(c) && (c & 0x80)) {
+ /* 0x80|' ' is plain (...) */
+ if ((c &= 0x7F) != ' ') {
+ /* check single match NOW */
+ if (sc == c)
+ found = true;
+ /* next character is (...) */
+ }
+ c = '(' /*)*/;
+ }
+ }
+ /* range expression? */
+ if (!(ISMAGIC(p[0]) && ord(p[1]) == ORD('-') &&
+ /* not terminating bracket? */
+ (!ISMAGIC(p[2]) || ord(p[3]) != ORD(']')))) {
+ /* no, check single match */
+ if (sc == c)
+ /* note: sc is never NUL */
+ found = true;
+ /* do the next "first" character */
+ continue;
+ }
+ /* save lower range bound */
+ lc = c;
+ /* skip over the range operator */
+ p += 2;
+ /* do the same shit as above... almost */
+ subc = 0;
+ if (!(c = *p++))
+ break;
+ /* non-regular character? */
+ if (ISMAGIC(c)) {
+ /* MAGIC + NUL cannot happen */
+ if (!(c = *p++))
+ break;
+ /* sub-bracket expressions */
+ if (ord(c) == ORD('[') && (
+ /* collating element? */
+ ord(*p) == ORD('.') ||
+ /* equivalence class? */
+ ord(*p) == ORD('=') ||
+ /* character class? */
+ ord(*p) == ORD(':'))) {
+ /* must stop with exactly the same c */
+ subc = *p++;
+ /* save away start of substring */
+ s = p;
+ /* arbitrarily many chars in betwixt */
+ while ((c = *p++))
+ /* but only this sequence... */
+ if (c == subc && ISMAGIC(*p) &&
+ ord(p[1]) == ORD(']')) {
+ /* accept, terminate */
+ p += 2;
+ break;
+ }
+ /* EOS without: reject bracket expr */
+ if (!c)
+ break;
+ /* debunk substring */
+ strndupx(subp, s, p - s - 3, ATEMP);
+ debunk(subp, subp, p - s - 3 + 1);
+ /* whither subexpression */
+ if (ord(subc) == ORD(':')) {
+ /* oops, not a range */
+
+ /* match single previous char */
+ if (lc && (sc == lc))
+ found = true;
+ /* match hyphen-minus */
+ if (ord(sc) == ORD('-'))
+ found = true;
+ /* handle cclass common part */
+ goto cclass_common;
+ }
+ /* collating element or equivalence class */
+ /* Note: latter are treated as former */
+ if (ctype(subp[0], C_ASCII) && !subp[1])
+ /* [.a.] where a is one ASCII char */
+ c = subp[0];
+ else
+ /* force no match */
+ c = 0;
+ /* no longer needed */
+ afree(subp, ATEMP);
+ /* other meaning below */
+ subc = 0;
+ } else if (c == (0x80 | ' ')) {
+ /* 0x80|' ' is plain (...) */
+ c = '(' /*)*/;
+ } else if (!ISMAGIC(c) && (c & 0x80)) {
+ c &= 0x7F;
+ subc = '(' /*)*/;
+ }
+ }
+ /* now do the actual range match check */
+ if (lc != 0 /* && c != 0 */ &&
+ asciibetical(lc) <= asciibetical(sc) &&
+ asciibetical(sc) <= asciibetical(c))
+ found = true;
+ /* forced next character? */
+ if (subc) {
+ c = subc;
+ goto nextc;
+ }
+ /* otherwise, just go on with the pattern string */
+ }
+ /* if we broke here, the bracket expression was invalid */
+ if (ord(sc) == ORD('['))
+ /* initial opening bracket as literal match */
+ return (pat);
+ /* or rather no match */
+ return (NULL);
+}
+
+/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
+static const unsigned char *
+pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep)
+{
+ int nest = 0;
+
+ for (; p < pe; p++) {
+ if (!ISMAGIC(*p))
+ continue;
+ if ((*++p == /*(*/ ')' && nest-- == 0) ||
+ (*p == '|' && match_sep && nest == 0))
+ return (p + 1);
+ if ((*p & 0x80) && ctype(*p & 0x7F, C_PATMO | C_SPC))
+ nest++;
+ }
+ return (NULL);
+}
+
+int
+ascstrcmp(const void *s1, const void *s2)
+{
+ const uint8_t *cp1 = s1, *cp2 = s2;
+
+ while (*cp1 == *cp2) {
+ if (*cp1++ == '\0')
+ return (0);
+ ++cp2;
+ }
+ return ((int)asciibetical(*cp1) - (int)asciibetical(*cp2));
+}
+
+int
+ascpstrcmp(const void *pstr1, const void *pstr2)
+{
+ return (ascstrcmp(*(const char * const *)pstr1,
+ *(const char * const *)pstr2));
+}
+
+/* Initialise a Getopt structure */
+void
+ksh_getopt_reset(Getopt *go, int flags)
+{
+ go->optind = 1;
+ go->optarg = NULL;
+ go->p = 0;
+ go->flags = flags;
+ go->info = 0;
+ go->buf[1] = '\0';
+}
+
+
+/**
+ * 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.
+ * If GF_ERROR is set (and option doesn't start with :), errors result in
+ * a call to bi_errorf().
+ *
+ * Non-standard features:
+ * - ';' is like ':' in options, except the argument is optional
+ * (if it isn't present, optarg is set to 0).
+ * Used for 'set -o'.
+ * - ',' is like ':' in options, except the argument always immediately
+ * follows the option character (optarg is set to the null string if
+ * the option is missing).
+ * Used for 'read -u2', 'print -u2' and fc -40.
+ * - '#' is like ':' in options, expect that the argument is optional
+ * and must start with a digit. If the argument doesn't start with a
+ * digit, it is assumed to be missing and normal option processing
+ * continues (optarg is set to 0 if the option is missing).
+ * Used for 'typeset -LZ4'.
+ * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an
+ * option starting with + is accepted, the GI_PLUS flag will be set
+ * in go->info.
+ */
+int
+ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
+{
+ char c;
+ const char *o;
+
+ if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
+ const char *arg = argv[go->optind], flag = arg ? *arg : '\0';
+
+ go->p = 1;
+ if (flag == '-' && ksh_isdash(arg + 1)) {
+ go->optind++;
+ go->p = 0;
+ go->info |= GI_MINUSMINUS;
+ return (-1);
+ }
+ if (arg == NULL ||
+ ((flag != '-' ) &&
+ /* neither a - nor a + (if + allowed) */
+ (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
+ (c = arg[1]) == '\0') {
+ go->p = 0;
+ return (-1);
+ }
+ go->optind++;
+ go->info &= ~(GI_MINUS|GI_PLUS);
+ go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
+ }
+ go->p++;
+ if (ctype(c, C_QUEST | C_COLON | C_HASH) || c == ';' || c == ',' ||
+ !(o = cstrchr(optionsp, c))) {
+ if (optionsp[0] == ':') {
+ go->buf[0] = c;
+ go->optarg = go->buf;
+ } else {
+ warningf(true, Tf_optfoo,
+ (go->flags & GF_NONAME) ? "" : argv[0],
+ (go->flags & GF_NONAME) ? "" : Tcolsp,
+ c, Tunknown_option);
+ if (go->flags & GF_ERROR)
+ bi_errorfz();
+ }
+ return ('?');
+ }
+ /**
+ * : 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.
+ */
+ if (*++o == ':' || *o == ';') {
+ if (argv[go->optind - 1][go->p])
+ go->optarg = argv[go->optind - 1] + go->p;
+ else if (argv[go->optind])
+ go->optarg = argv[go->optind++];
+ else if (*o == ';')
+ go->optarg = NULL;
+ else {
+ if (optionsp[0] == ':') {
+ go->buf[0] = c;
+ go->optarg = go->buf;
+ return (':');
+ }
+ warningf(true, Tf_optfoo,
+ (go->flags & GF_NONAME) ? "" : argv[0],
+ (go->flags & GF_NONAME) ? "" : Tcolsp,
+ c, Treq_arg);
+ if (go->flags & GF_ERROR)
+ bi_errorfz();
+ return ('?');
+ }
+ go->p = 0;
+ } else if (*o == ',') {
+ /* argument is attached to option character, even if null */
+ go->optarg = argv[go->optind - 1] + go->p;
+ go->p = 0;
+ } else if (*o == '#') {
+ /*
+ * 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.
+ */
+ if (argv[go->optind - 1][go->p]) {
+ if (ctype(argv[go->optind - 1][go->p], C_DIGIT)) {
+ go->optarg = argv[go->optind - 1] + go->p;
+ go->p = 0;
+ } else
+ go->optarg = NULL;
+ } else {
+ if (argv[go->optind] &&
+ ctype(argv[go->optind][0], C_DIGIT)) {
+ go->optarg = argv[go->optind++];
+ go->p = 0;
+ } else
+ go->optarg = NULL;
+ }
+ }
+ return (c);
+}
+
+/*
+ * print variable/alias value using necessary quotes
+ * (POSIX says they should be suitable for re-entry...)
+ * No trailing newline is printed.
+ */
+void
+print_value_quoted(struct shf *shf, const char *s)
+{
+ unsigned char c;
+ const unsigned char *p = (const unsigned char *)s;
+ bool inquote = true;
+
+ /* first, special-case empty strings (for re-entrancy) */
+ if (!*s) {
+ shf_putc('\'', shf);
+ shf_putc('\'', shf);
+ return;
+ }
+
+ /* non-empty; check whether any quotes are needed */
+ while (rtt2asc(c = *p++) >= 32)
+ if (ctype(c, C_QUOTE | C_SPC))
+ inquote = false;
+
+ p = (const unsigned char *)s;
+ if (c == 0) {
+ if (inquote) {
+ /* nope, use the shortcut */
+ shf_puts(s, shf);
+ return;
+ }
+
+ /* otherwise, quote nicely via state machine */
+ while ((c = *p++) != 0) {
+ if (c == '\'') {
+ /*
+ * multiple single quotes or any of them
+ * at the beginning of a string look nicer
+ * this way than when simply substituting
+ */
+ if (inquote) {
+ shf_putc('\'', shf);
+ inquote = false;
+ }
+ shf_putc('\\', shf);
+ } else if (!inquote) {
+ shf_putc('\'', shf);
+ inquote = true;
+ }
+ shf_putc(c, shf);
+ }
+ } else {
+ unsigned int wc;
+ size_t n;
+
+ /* use $'...' quote format */
+ shf_putc('$', shf);
+ shf_putc('\'', shf);
+ while ((c = *p) != 0) {
+#ifndef MKSH_EBCDIC
+ if (c >= 0xC2) {
+ n = utf_mbtowc(&wc, (const char *)p);
+ if (n != (size_t)-1) {
+ p += n;
+ shf_fprintf(shf, "\\u%04X", wc);
+ continue;
+ }
+ }
+#endif
+ ++p;
+ switch (c) {
+ /* see unbksl() in this file for comments */
+ case KSH_BEL:
+ c = 'a';
+ if (0)
+ /* FALLTHROUGH */
+ case '\b':
+ c = 'b';
+ if (0)
+ /* FALLTHROUGH */
+ case '\f':
+ c = 'f';
+ if (0)
+ /* FALLTHROUGH */
+ case '\n':
+ c = 'n';
+ if (0)
+ /* FALLTHROUGH */
+ case '\r':
+ c = 'r';
+ if (0)
+ /* FALLTHROUGH */
+ case '\t':
+ c = 't';
+ if (0)
+ /* FALLTHROUGH */
+ case KSH_VTAB:
+ c = 'v';
+ if (0)
+ /* FALLTHROUGH */
+ case KSH_ESC:
+ /* take E not e because \e is \ in *roff */
+ c = 'E';
+ /* FALLTHROUGH */
+ case '\\':
+ shf_putc('\\', shf);
+
+ if (0)
+ /* FALLTHROUGH */
+ default:
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+ if (ksh_isctrl(c))
+#else
+ if (!ctype(c, C_PRINT))
+#endif
+ {
+ /* FALLTHROUGH */
+ case '\'':
+ shf_fprintf(shf, "\\%03o", c);
+ break;
+ }
+
+ shf_putc(c, shf);
+ break;
+ }
+ }
+ inquote = true;
+ }
+ if (inquote)
+ shf_putc('\'', shf);
+}
+
+/*
+ * Print things in columns and rows - func() is called to format
+ * the i-th element
+ */
+void
+print_columns(struct columnise_opts *opts, unsigned int n,
+ void (*func)(char *, size_t, unsigned int, const void *),
+ const void *arg, size_t max_oct, size_t max_colz)
+{
+ unsigned int i, r = 0, c, rows, cols, nspace, max_col;
+ char *str;
+
+ if (!n)
+ return;
+
+ if (max_colz > 2147483646) {
+#ifndef MKSH_SMALL
+ internal_warningf("print_columns called with %s=%zu >= INT_MAX",
+ "max_col", max_colz);
+#endif
+ return;
+ }
+ max_col = (unsigned int)max_colz;
+
+ if (max_oct > 2147483646) {
+#ifndef MKSH_SMALL
+ internal_warningf("print_columns called with %s=%zu >= INT_MAX",
+ "max_oct", max_oct);
+#endif
+ return;
+ }
+ ++max_oct;
+ str = alloc(max_oct, ATEMP);
+
+ /*
+ * We use (max_col + 2) to consider the separator space.
+ * Note that no spaces are printed after the last column
+ * to avoid problems with terminals that have auto-wrap,
+ * but we need to also take this into account in x_cols.
+ */
+ cols = (x_cols + 1) / (max_col + 2);
+
+ /* if we can only print one column anyway, skip the goo */
+ if (cols < 2) {
+ goto prcols_easy;
+ while (r < n) {
+ shf_putc(opts->linesep, opts->shf);
+ prcols_easy:
+ (*func)(str, max_oct, r++, arg);
+ shf_puts(str, opts->shf);
+ }
+ goto out;
+ }
+
+ rows = (n + cols - 1) / cols;
+ if (opts->prefcol && cols > rows) {
+ cols = rows;
+ rows = (n + cols - 1) / cols;
+ }
+
+ nspace = (x_cols - max_col * cols) / cols;
+ if (nspace < 2)
+ nspace = 2;
+ max_col = -max_col;
+ goto prcols_hard;
+ while (r < rows) {
+ shf_putchar(opts->linesep, opts->shf);
+ prcols_hard:
+ for (c = 0; c < cols; c++) {
+ if ((i = c * rows + r) >= n)
+ break;
+ (*func)(str, max_oct, i, arg);
+ if (i + rows >= n)
+ shf_puts(str, opts->shf);
+ else
+ shf_fprintf(opts->shf, "%*s%*s",
+ (int)max_col, str, (int)nspace, null);
+ }
+ ++r;
+ }
+ out:
+ if (opts->do_last)
+ shf_putchar(opts->linesep, opts->shf);
+ afree(str, ATEMP);
+}
+
+/* strip all NUL bytes from buf; output is NUL-terminated if stripped */
+void
+strip_nuls(char *buf, size_t len)
+{
+ char *cp, *dp, *ep;
+
+ if (!len || !(dp = memchr(buf, '\0', len)))
+ return;
+
+ ep = buf + len;
+ cp = dp;
+
+ cp_has_nul_byte:
+ while (cp++ < ep && *cp == '\0')
+ ; /* nothing */
+ while (cp < ep && *cp != '\0')
+ *dp++ = *cp++;
+ if (cp < ep)
+ goto cp_has_nul_byte;
+
+ *dp = '\0';
+}
+
+/*
+ * Like read(2), but if read fails due to non-blocking flag,
+ * resets flag and restarts read.
+ */
+ssize_t
+blocking_read(int fd, char *buf, size_t nbytes)
+{
+ 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 = true;
+ continue;
+ }
+ errno = EAGAIN;
+ }
+ break;
+ }
+ return (ret);
+}
+
+/*
+ * 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.
+ */
+int
+reset_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ return (-1);
+ if (!(flags & O_NONBLOCK))
+ return (0);
+ flags &= ~O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return (-1);
+ return (1);
+}
+
+/* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */
+char *
+ksh_get_wd(void)
+{
+#ifdef MKSH__NO_PATH_MAX
+ char *rv, *cp;
+
+ if ((cp = get_current_dir_name())) {
+ strdupx(rv, cp, ATEMP);
+ free_gnu_gcdn(cp);
+ } else
+ rv = NULL;
+#else
+ char *rv;
+
+ if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) {
+ afree(rv, ATEMP);
+ rv = NULL;
+ }
+#endif
+
+ return (rv);
+}
+
+#ifndef ELOOP
+#define ELOOP E2BIG
+#endif
+
+char *
+do_realpath(const char *upath)
+{
+ char *xp, *ip, *tp, *ipath, *ldest = NULL;
+ XString xs;
+ size_t pos, len;
+ int llen;
+ struct stat sb;
+#ifdef MKSH__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 (mksh_abspath(upath)) {
+ /* upath is an absolute pathname */
+ strdupx(ipath, upath, ATEMP);
+#ifdef MKSH_DOSPATH
+ } else if (mksh_drvltr(upath)) {
+ /* upath is a drive-relative pathname */
+ if (getdrvwd(&ldest, ord(*upath)))
+ return (NULL);
+ /* A:foo -> A:/cwd/foo; A: -> A:/cwd */
+ strpathx(ipath, ldest, upath + 2, 0);
+#endif
+ } else {
+ /* upath is a relative pathname, prepend cwd */
+ if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp))
+ return (NULL);
+ strpathx(ipath, tp, upath, 1);
+ 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 (mksh_cdirsep(*ip))
+ ++ip;
+ if (!*ip)
+ break;
+
+ /* get next pathname component from input */
+ tp = ip;
+ while (*ip && !mksh_cdirsep(*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 */
+ /*XXX consider a rooted pathname */
+ while (xp > Xstring(xs, xp))
+ if (mksh_cdirsep(*--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 (mksh_lstat(Xstring(xs, xp), &sb)) {
+ /* lstat failed */
+ if (errno == ENOENT) {
+ /* because the pathname does not exist */
+ while (mksh_cdirsep(*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)) {
+#ifndef MKSH__NO_SYMLINK
+ /* reached maximum recursion depth? */
+ if (!symlinks--) {
+ /* yep, prevent infinite loops */
+ errno = ELOOP;
+ goto notfound;
+ }
+
+ /* get symlink(7) target */
+ if (pathcnd) {
+#ifdef MKSH__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
+ */
+#ifdef MKSH_DOSPATH
+ assemble_symlink:
+#endif
+ /* append rest of current input path to link target */
+ strpathx(tp, ldest, ip, 0);
+ afree(ipath, ATEMP);
+ ip = ipath = tp;
+ if (!mksh_abspath(ipath)) {
+#ifdef MKSH_DOSPATH
+ /* symlink target might be drive-relative */
+ if (mksh_drvltr(ipath)) {
+ if (getdrvwd(&ldest, ord(*ipath)))
+ goto notfound;
+ ip += 2;
+ goto assemble_symlink;
+ }
+#endif
+ /* symlink target is a relative path */
+ xp = Xrestpos(xs, xp, pos);
+ } else
+#endif
+ {
+ /* symlink target is an absolute path */
+ xp = Xstring(xs, xp);
+ beginning_of_a_pathname:
+ /* assert: mksh_abspath(ip == ipath) */
+ /* assert: xp == xs.beg => start of path */
+
+ /* exactly two leading slashes? (SUSv4 3.266) */
+ if (ip[1] == ip[0] && !mksh_cdirsep(ip[2])) {
+ /* keep them, e.g. for UNC pathnames */
+ Xput(xs, xp, '/');
+ }
+#ifdef MKSH_DOSPATH
+ /* drive letter? */
+ if (mksh_drvltr(ip)) {
+ /* keep it */
+ Xput(xs, xp, *ip++);
+ Xput(xs, xp, *ip++);
+ }
+#endif
+ }
+ }
+ /* 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 && mksh_cdirsep(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 */
+ afree(ldest, ATEMP);
+ afree(ipath, ATEMP);
+ return (Xclose(xs, xp));
+
+ notfound:
+ /* save; freeing memory might trash it */
+ llen = errno;
+ 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
+ * - if file starts with ./ or ../ append cwd and file to result
+ * and set cdpathp to NULL
+ * - if the first element of cdpathp doesnt start with a '/' xx or '.' xx
+ * then cwd is appended to result.
+ * - the first element of cdpathp is appended to result
+ * - file is appended to result
+ * - cdpathp is set to the start of the next element in cdpathp (or NULL
+ * if there are no more elements.
+ * The return value indicates whether a non-null element from cdpathp
+ * was appended to result.
+ */
+static int
+make_path(const char *cwd, const char *file,
+ /* pointer to colon-separated list */
+ char **cdpathp,
+ XString *xsp,
+ int *phys_pathp)
+{
+ int rval = 0;
+ bool use_cdpath = true;
+ char *plist;
+ size_t len, plen = 0;
+ char *xp = Xstring(*xsp, xp);
+
+ if (!file)
+ file = null;
+
+ if (mksh_abspath(file)) {
+ *phys_pathp = 0;
+ use_cdpath = false;
+ } else {
+ if (file[0] == '.') {
+ char c = file[1];
+
+ if (c == '.')
+ c = file[2];
+ if (mksh_cdirsep(c) || c == '\0')
+ use_cdpath = false;
+ }
+
+ plist = *cdpathp;
+ if (!plist)
+ use_cdpath = false;
+ else if (use_cdpath) {
+ char *pend = plist;
+
+ while (*pend && *pend != MKSH_PATHSEPC)
+ ++pend;
+ plen = pend - plist;
+ *cdpathp = *pend ? pend + 1 : NULL;
+ }
+
+ if ((!use_cdpath || !plen || !mksh_abspath(plist)) &&
+ (cwd && *cwd)) {
+ len = strlen(cwd);
+ XcheckN(*xsp, xp, len);
+ memcpy(xp, cwd, len);
+ xp += len;
+ if (!mksh_cdirsep(cwd[len - 1]))
+ Xput(*xsp, xp, '/');
+ }
+ *phys_pathp = Xlength(*xsp, xp);
+ if (use_cdpath && plen) {
+ XcheckN(*xsp, xp, plen);
+ memcpy(xp, plist, plen);
+ xp += plen;
+ if (!mksh_cdirsep(plist[plen - 1]))
+ Xput(*xsp, xp, '/');
+ rval = 1;
+ }
+ }
+
+ len = strlen(file) + 1;
+ XcheckN(*xsp, xp, len);
+ memcpy(xp, file, len);
+
+ if (!use_cdpath)
+ *cdpathp = NULL;
+
+ return (rval);
+}
+
+/*-
+ * Simplify pathnames containing "." and ".." entries.
+ *
+ * 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
+ * C:/foo/../.. C:/
+ * C:. C:
+ * C:.. C:..
+ * C:foo/../../blah C:../blah
+ *
+ * XXX consider a rooted pathname: we cannot really 'cd ..' for
+ * pathnames like: '/', 'c:/', '//foo', '//foo/', '/@unixroot/'
+ * (no effect), 'c:', 'c:.' (effect is retaining the '../') but
+ * we need to honour this throughout the shell
+ */
+void
+simplify_path(char *p)
+{
+ char *dp, *ip, *sp, *tp;
+ size_t len;
+ bool needslash;
+#ifdef MKSH_DOSPATH
+ bool needdot = true;
+
+ /* keep drive letter */
+ if (mksh_drvltr(p)) {
+ p += 2;
+ needdot = false;
+ }
+#else
+#define needdot true
+#endif
+
+ switch (*p) {
+ case 0:
+ return;
+ case '/':
+#ifdef MKSH_DOSPATH
+ case '\\':
+#endif
+ /* exactly two leading slashes? (SUSv4 3.266) */
+ if (p[1] == p[0] && !mksh_cdirsep(p[2]))
+ /* keep them, e.g. for UNC pathnames */
+ ++p;
+ needslash = true;
+ break;
+ default:
+ needslash = false;
+ }
+ dp = ip = sp = p;
+
+ while (*ip) {
+ /* skip slashes in input */
+ while (mksh_cdirsep(*ip))
+ ++ip;
+ if (!*ip)
+ break;
+
+ /* get next pathname component from input */
+ tp = ip;
+ while (*ip && !mksh_cdirsep(*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] == '.') {
+ /* parent level, but how? (see above) */
+ if (mksh_abspath(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 (mksh_cdirsep(*--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 (needslash)
+ *dp++ = '/';
+
+ /* 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 (or slash, when absolute) */
+ if (needslash)
+ *dp++ = '/';
+ else if (needdot)
+ *dp++ = '.';
+ }
+ *dp = '\0';
+#undef needdot
+}
+
+void
+set_current_wd(const char *nwd)
+{
+ char *allocd = NULL;
+
+ 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(Tcant_cd);
+ return (2);
+ }
+
+ pwd_s = global(TPWD);
+ oldpwd_s = global(TOLDPWD);
+
+ 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(Tno_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(Tbadsubst);
+ 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(Ttoo_many_args);
+ return (2);
+ }
+
+#ifdef MKSH_DOSPATH
+ tryp = NULL;
+ if (mksh_drvltr(dir) && !mksh_cdirsep(dir[2]) &&
+ !getdrvwd(&tryp, ord(*dir))) {
+ strpathx(dir, tryp, dir + 2, 0);
+ afree(tryp, ATEMP);
+ afree(allocd, ATEMP);
+ allocd = dir;
+ }
+#endif
+
+#ifdef MKSH__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(Tf_sD_s, dir, "bad directory");
+ else
+ bi_errorf(Tf_sD_s, tryp, cstrerror(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 (!mksh_abspath(Xstring(xs, xp))) {
+ pwd = NULL;
+ } else if (!physical) {
+ goto norealpath_PWD;
+ } else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) {
+ if (eflag)
+ rv = 1;
+ norealpath_PWD:
+ pwd = Xstring(xs, xp);
+ }
+
+ /* Set PWD */
+ if (pwd) {
+ char *ptmp = pwd;
+
+ set_current_wd(ptmp);
+ /* Ignore failure (happens if readonly or integer) */
+ setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+ } else {
+ set_current_wd(null);
+ pwd = Xstring(xs, xp);
+ /* XXX unset $PWD? */
+ if (eflag)
+ rv = 1;
+ }
+ if (printpath || cdnode)
+ shprintf(Tf_sN, pwd);
+
+ afree(allocd, ATEMP);
+ Xfree(xs, xp);
+ return (rv);
+}
+
+
+#ifdef KSH_CHVT_CODE
+extern void chvt_reinit(void);
+
+static void
+chvt(const Getopt *go)
+{
+ const char *dv = go->optarg;
+ char *cp = NULL;
+ int fd;
+
+ switch (*dv) {
+ case '-':
+ dv = "/dev/null";
+ break;
+ case '!':
+ ++dv;
+ /* FALLTHROUGH */
+ default: {
+ struct stat sb;
+
+ if (stat(dv, &sb)) {
+ cp = shf_smprintf("/dev/ttyC%s", dv);
+ dv = cp;
+ if (stat(dv, &sb)) {
+ memmove(cp + 1, cp, /* /dev/tty */ 8);
+ dv = cp + 1;
+ if (stat(dv, &sb)) {
+ errorf(Tf_sD_sD_s, "chvt",
+ "can't find tty", go->optarg);
+ }
+ }
+ }
+ if (!(sb.st_mode & S_IFCHR))
+ errorf(Tf_sD_sD_s, "chvt", "not a char device", dv);
+#ifndef MKSH_DISABLE_REVOKE_WARNING
+#if HAVE_REVOKE
+ if (revoke(dv))
+#endif
+ warningf(false, Tf_sD_s_s, "chvt",
+ "new shell is potentially insecure, can't revoke",
+ dv);
+#endif
+ }
+ }
+ if ((fd = binopen2(dv, O_RDWR)) < 0) {
+ sleep(1);
+ if ((fd = binopen2(dv, O_RDWR)) < 0) {
+ errorf(Tf_sD_s_s, "chvt", Tcant_open, dv);
+ }
+ }
+ if (go->optarg[0] != '!') {
+ switch (fork()) {
+ case -1:
+ errorf(Tf_sD_s_s, "chvt", "fork", "failed");
+ case 0:
+ break;
+ default:
+ exit(0);
+ }
+ }
+ if (setsid() == -1)
+ errorf(Tf_sD_s_s, "chvt", "setsid", "failed");
+ if (go->optarg[0] != '-') {
+ if (ioctl(fd, TIOCSCTTY, NULL) == -1)
+ errorf(Tf_sD_s_s, "chvt", "TIOCSCTTY", "failed");
+ if (tcflush(fd, TCIOFLUSH))
+ errorf(Tf_sD_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);
+ rndset((unsigned long)chvt_rndsetup(go, sizeof(Getopt)));
+ chvt_reinit();
+}
+#endif
+
+#ifdef DEBUG
+char *
+strchr(char *p, int ch)
+{
+ for (;; ++p) {
+ if (*p == ch)
+ return (p);
+ if (!*p)
+ return (NULL);
+ }
+ /* NOTREACHED */
+}
+
+char *
+strstr(char *b, const char *l)
+{
+ char first, c;
+ size_t n;
+
+ if ((first = *l++) == '\0')
+ return (b);
+ n = strlen(l);
+ strstr_look:
+ while ((c = *b++) != first)
+ if (c == '\0')
+ return (NULL);
+ if (strncmp(b, l, n))
+ goto strstr_look;
+ return (b - 1);
+}
+#endif
+
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+char *
+strndup_i(const char *src, size_t len, Area *ap)
+{
+ char *dst = NULL;
+
+ if (src != NULL) {
+ dst = alloc(len + 1, ap);
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+ }
+ return (dst);
+}
+
+char *
+strdup_i(const char *src, Area *ap)
+{
+ return (src == NULL ? NULL : strndup_i(src, strlen(src), ap));
+}
+#endif
+
+#if !HAVE_GETRUSAGE
+#define INVTCK(r,t) do { \
+ r.tv_usec = ((t) % (1000000 / CLK_TCK)) * (1000000 / CLK_TCK); \
+ r.tv_sec = (t) / CLK_TCK; \
+} while (/* CONSTCOND */ 0)
+
+int
+getrusage(int what, struct rusage *ru)
+{
+ struct tms tms;
+ clock_t u, s;
+
+ if (/* ru == NULL || */ times(&tms) == (clock_t)-1)
+ return (-1);
+
+ switch (what) {
+ case RUSAGE_SELF:
+ u = tms.tms_utime;
+ s = tms.tms_stime;
+ break;
+ case RUSAGE_CHILDREN:
+ u = tms.tms_cutime;
+ s = tms.tms_cstime;
+ break;
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ INVTCK(ru->ru_utime, u);
+ INVTCK(ru->ru_stime, s);
+ return (0);
+}
+#endif
+
+/*
+ * process the string available via fg (get a char)
+ * and fp (put back a char) for backslash escapes,
+ * assuming the first call to *fg gets the char di-
+ * rectly after the backslash; return the character
+ * (0..0xFF), UCS (wc + 0x100), or -1 if no known
+ * escape sequence was found
+ */
+int
+unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
+{
+ int wc, i, c, fc, n;
+
+ fc = (*fg)();
+ switch (fc) {
+ case 'a':
+ wc = KSH_BEL;
+ break;
+ case 'b':
+ wc = '\b';
+ break;
+ case 'c':
+ if (!cstyle)
+ goto unknown_escape;
+ c = (*fg)();
+ wc = ksh_toctrl(c);
+ break;
+ case 'E':
+ case 'e':
+ wc = KSH_ESC;
+ break;
+ case 'f':
+ wc = '\f';
+ break;
+ case 'n':
+ wc = '\n';
+ break;
+ case 'r':
+ wc = '\r';
+ break;
+ case 't':
+ wc = '\t';
+ break;
+ case 'v':
+ wc = KSH_VTAB;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ if (!cstyle)
+ goto unknown_escape;
+ /* FALLTHROUGH */
+ case '0':
+ if (cstyle)
+ (*fp)(fc);
+ /*
+ * look for an octal number with up to three
+ * digits, not counting the leading zero;
+ * convert it to a raw octet
+ */
+ wc = 0;
+ i = 3;
+ while (i--)
+ if (ctype((c = (*fg)()), C_OCTAL))
+ wc = (wc << 3) + ksh_numdig(c);
+ else {
+ (*fp)(c);
+ break;
+ }
+ break;
+ case 'U':
+ i = 8;
+ if (/* CONSTCOND */ 0)
+ /* FALLTHROUGH */
+ case 'u':
+ i = 4;
+ 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: UCS if >0xFF)
+ * u/U: look for a hexadecimal number with up to
+ * four (U: eight) digits; convert to UCS
+ */
+ wc = 0;
+ n = 0;
+ while (n < i || i == -1) {
+ wc <<= 4;
+ if (!ctype((c = (*fg)()), C_SEDEC)) {
+ wc >>= 4;
+ (*fp)(c);
+ break;
+ }
+ if (ctype(c, C_DIGIT))
+ wc += ksh_numdig(c);
+ else if (ctype(c, C_UPPER))
+ wc += ksh_numuc(c) + 10;
+ else
+ wc += ksh_numlc(c) + 10;
+ ++n;
+ }
+ if (!n)
+ goto unknown_escape;
+ if ((cstyle && wc > 0xFF) || fc != 'x')
+ /* UCS marker */
+ wc += 0x100;
+ break;
+ case '\'':
+ if (!cstyle)
+ goto unknown_escape;
+ wc = '\'';
+ break;
+ case '\\':
+ wc = '\\';
+ break;
+ default:
+ unknown_escape:
+ (*fp)(fc);
+ return (-1);
+ }
+
+ return (wc);
+}
diff --git a/shells/mksh/files/mksh.1 b/shells/mksh/files/mksh.1
new file mode 100644
index 00000000000..1f1a121bb43
--- /dev/null
+++ b/shells/mksh/files/mksh.1
@@ -0,0 +1,7102 @@
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.491 2020/05/16 22:12:36 tg Exp $
+.\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
+.\"-
+.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+.\" 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+.\" 2018, 2019, 2020
+.\" mirabilos <m@mirbsd.org>
+.\"
+.\" Provided that these terms and disclaimer and all copyright notices
+.\" are retained or reproduced in an accompanying document, permission
+.\" is granted to deal in this work without restriction, including unâ€
+.\" limited rights to use, publicly perform, distribute, sell, modify,
+.\" merge, give away, or sublicence.
+.\"
+.\" This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+.\" the utmost extent permitted by applicable law, neither express nor
+.\" implied; without malicious intent or gross negligence. In no event
+.\" may a licensor, author or contributor be held liable for indirect,
+.\" direct, other damage, loss, or other issues arising in any way out
+.\" of dealing in the work, even if advised of the possibility of such
+.\" damage or existence of a defect, except proven that it results out
+.\" of said person’s immediate fault when using the work as intended.
+.\"-
+.\" 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 for a solo en dash
+.\" * and \*(EM for a correctly spaced em dash
+.\" * <>| are problematic, so redefine and use \*(Lt\*(Gt\*(Ba
+.\" Also make sure to use \& *before* a punctuation char that is to not
+.\" be interpreted as punctuation, and especially with two-letter words
+.\" but also (after) a period that does not end a sentence (“e.g.\&”).
+.\" The section after the "doc" macropackage has been loaded contains
+.\" additional code to convene between the UCB mdoc macropackage (and
+.\" its variant as BSD mdoc in groff) and the GNU mdoc macropackage.
+.\"
+.ie \n(.g \{\
+. if \*[.T]ascii .tr \-\N'45'
+. if \*[.T]latin1 .tr \-\N'45'
+. if \*[.T]utf8 .tr \-\N'45'
+. ds <= \[<=]
+. ds >= \[>=]
+. ds Rq \[rq]
+. ds Lq \[lq]
+. ds sL \(aq
+. ds sR \(aq
+. if \*[.T]utf8 .ds sL `
+. if \*[.T]ps .ds sL `
+. if \*[.T]utf8 .ds sR '
+. if \*[.T]ps .ds sR '
+. ds aq \(aq
+. ds TI \(ti
+. ds ha \(ha
+. ds en \(en
+.\}
+.el \{\
+. ds aq '
+. ds TI ~
+. ds ha ^
+. ds en \(em
+.\}
+.ie n \{\
+. ds EM \ \*(en\ \&
+.\}
+.el \{\
+. ds EM \f(TR\^\(em\^\fP
+.\}
+.\"
+.\" Implement .Dd with the Mdocdate RCS keyword
+.\"
+.rn Dd xD
+.de Dd
+.ie \\$1$Mdocdate: \{\
+. xD \\$2 \\$3, \\$4
+.\}
+.el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
+..
+.\"
+.\" .Dd must come before definition of .Mx, because when called
+.\" with -mandoc, it might implement .Mx itself, but we want to
+.\" use our own definition. And .Dd must come *first*, always.
+.\"
+.Dd $Mdocdate: May 16 2020 $
+.\"
+.\" Check which macro package we use, and do other -mdoc setup.
+.\"
+.ie \n(.g \{\
+. if \*[.T]utf8 .tr \[la]\*(Lt
+. if \*[.T]utf8 .tr \[ra]\*(Gt
+. ie d volume-ds-1 .ds tT gnu
+. el .ie d doc-volume-ds-1 .ds tT gnp
+. 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]\%MirBSD\*[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 .ie "\*(tT"gnp" \{\
+. eo
+. de Mx
+. nr doc-curr-font \n[.f]
+. nr doc-curr-size \n[.ps]
+. ds doc-str-Mx \f[\n[doc-curr-font]]\s[\n[doc-curr-size]u]
+. ds doc-str-Mx1 \*[doc-Tn-font-size]\%MirBSD\*[doc-str-Mx]
+. if !\n[doc-arg-limit] \
+. if \n[.$] \{\
+. ds doc-macro-name Mx
+. doc-parse-args \$@
+. \}
+. if (\n[doc-arg-limit] > \n[doc-arg-ptr]) \{\
+. nr doc-arg-ptr +1
+. ie (\n[doc-type\n[doc-arg-ptr]] == 2) \
+. as doc-str-Mx1 \~\*[doc-arg\n[doc-arg-ptr]]
+. el \
+. nr doc-arg-ptr -1
+. \}
+. ds doc-arg\n[doc-arg-ptr] "\*[doc-str-Mx1]
+. nr doc-type\n[doc-arg-ptr] 2
+. ds doc-space\n[doc-arg-ptr] "\*[doc-space]
+. nr doc-num-args (\n[doc-arg-limit] - \n[doc-arg-ptr])
+. nr doc-arg-limit \n[doc-arg-ptr]
+. if \n[doc-num-args] \
+. doc-parse-space-vector
+. doc-print-recursive
+..
+. ec
+. ds sP \s0
+. ds tN \*[doc-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 \&MirBSD\\*(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 \&MirBSD\ #\&\\*(A\\n(aP\\*(aa
+. ie \\n(aC>\\n(aP \{\
+. nr aP \\n(aP+1
+. nR
+. \}
+. el .aZ
+. \}
+. el \{\
+. as b1 \&MirBSD\\*(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
+.Oo
+.Fl T Oo Ar \&! Oc Ns Ar tty
+\*(Ba
+.Ar \&\-
+.Oc
+.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.
+At times, this manual page may give scripting advice; while it
+sometimes does take portable shell scripting or various standards
+into account all information is first and foremost presented with
+.Nm
+in mind and should be taken as such.
+.Ss I use Android, OS/2, etc. so what...?
+Please refer to:
+.Pa http://www.mirbsd.org/mksh\-faq.htm#sowhatismksh
+.Ss Invocation
+Most builtins can be called directly, for example if a link points from its
+name to the shell; not all make sense, have been tested or work at all though.
+.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 that reads commands from standard input 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 name or 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 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 initial real user ID (group ID).
+For further implications, see
+.Sx Startup files .
+If the shell is privileged and this flag is not explicitly set, the
+.Dq privileged
+option is cleared automatically after processing the startup files.
+.It Fl r
+Restricted shell.
+A shell is
+.Dq restricted
+if the basename the shell is called with, after
+.Ql \-
+processing, starts with
+.Ql r
+or if this option is used.
+The following restrictions come into effect after the shell processes any
+profile and
+.Ev ENV
+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.\&
+.Dq Li \*(Gt ,
+.Dq Li \*(Gt\*(Ba ,
+.Dq Li \*(Gt\*(Gt ,
+.Dq Li \*(Lt\*(Gt ) .
+.El
+.It Fl s
+The shell reads commands from standard input; all non-option arguments
+are positional parameters.
+.It Fl T Ar name
+Spawn
+.Nm
+on the
+.Xr tty 4
+device given.
+The paths
+.Ar name ,
+.Pa /dev/ttyC Ns Ar name
+and
+.Pa /dev/tty Ns Ar name
+are attempted in order.
+Unless
+.Ar name
+begins with an exclamation mark
+.Pq Ql \&! ,
+this is done in a subshell and returns immediately.
+If
+.Ar name
+is a dash
+.Pq Ql \&\- ,
+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 name the shell was called with (i.e. argv[0]) is used.
+.Pp
+The exit status of the shell is 127 if the command file specified on the
+command line could not be opened, or non-zero if a fatal syntax error
+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 Ql \*(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.
+A privileged shell then drops privileges if neither was the
+.Fl p
+option given on the command line nor set during execution of the startup files.
+.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 :
+.Dq Li \*(Lt ,
+.Dq Li \*(Lt& ,
+.Dq Li \*(Lt\*(Lt ,
+.Dq Li \*(Lt\*(Lt\*(Lt ,
+.Dq Li \*(Gt ,
+.Dq Li \*(Gt& ,
+.Dq Li \*(Gt\*(Gt ,
+.Dq Li &\*(Gt ,
+etc. are used to specify redirections (see
+.Sx Input/output redirection
+below);
+.Dq Li \*(Ba
+is used to create pipelines;
+.Dq Li \*(Ba&
+is used to create co-processes (see
+.Sx Co-processes
+below);
+.Dq Li \&;
+is used to separate commands;
+.Dq Li &
+is used to create asynchronous pipelines;
+.Dq Li &&
+and
+.Dq Li \*(Ba\*(Ba
+are used to specify conditional execution;
+.Dq Li \&;; ,
+.Dq Li \&;&
+and
+.Dq Li \&;\*(Ba
+are used in
+.Ic case
+statements;
+.Dq Li \&(( ... \&))
+is used in arithmetic expressions;
+and lastly,
+.Dq Li \&( ... \&)
+is used to create subshells.
+.Pp
+Whitespace and meta-characters can be quoted individually using a backslash
+.Pq Ql \e ,
+or in groups using double
+.Pq Ql \&"
+or single
+.Pq Dq Li \*(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 \&" ,
+.Dq Li \*(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\*(EMeverything 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
+alternations (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
+.Dq Li \*(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, unless the
+.Ic pipefail
+option is set (see there).
+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
+.Dq Li \&!
+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:
+.Dq Li && ,
+.Dq Li \*(Ba\*(Ba ,
+.Dq Li & ,
+.Dq Li \*(Ba&
+and
+.Dq Li \&; .
+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;
+.Dq Li \*(Ba\*(Ba
+.No is the opposite\*(EM Ns Ar cmd2
+is executed only if the exit status of
+.Ar cmd1
+is non-zero.
+.Dq Li &&
+and
+.Dq Li \*(Ba\*(Ba
+have equal precedence which is higher than that of
+.Dq Li & ,
+.Dq Li \*(Ba&
+and
+.Dq Li \&; ,
+which also have equal precedence.
+Note that the
+.Dq Li &&
+and
+.Dq Li \*(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
+.Dq Li &
+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
+.Dq Li \*(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
+.Dq Li &&
+and
+.Dq Li \*(Ba\*(Ba
+operators, while it need not follow
+.Dq Li & ,
+.Dq Li \*(Ba&
+or
+.Dq Li \&; .
+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 Xo Ic case Ar word Ic in
+.Oo Op \&(
+.Ar pattern
+.Op \*(Ba Ar pattern
+.No ... Ns )
+.Ar list
+.Aq terminator
+.Oc No ... Ic 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 ,
+for example:
+.Dq Li case $foo { (ba[rz]\*(Bablah) date ;; }
+.Pp
+The list
+.Ao terminator Ac Ns s
+are:
+.Bl -tag -width 4n
+.It Dq Li ;;
+Terminate after the list.
+.It Dq Li \&;&
+Fall through into the next list.
+.It Dq Li \&;\*(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 Ic for Ar name
+.Oo Ic in Ar word ... Oc Ic ;
+.Ic do Ar list ; Ic 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.
+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.
+If
+.Ic in
+is not used to specify a word list, the positional parameters ($1, $2,
+etc.) are used instead; in this case, use a newline instead of the semicolon
+.Pq Sq Ic ;\&
+for portability.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done ,
+as in
+.Dq Li for i; { echo $i; }
+.Pq not portable .
+.It Xo Ic function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+All redirections specified after a function definition are performed whenever
+the function is executed, not when the function definition is executed.
+.It Ar name Ns \&() Ar command
+Mostly the same as
+.Ic function
+(see above and
+.Sx Functions
+below).
+Most amounts of space and tab after
+.Ar name
+will be ignored.
+.It Xo Ic function Ar name Ns \&()
+.No { Ar list ; No }
+.Xc
+.Nm bash Ns ism for
+.Ar name Ns Li ()\ {
+.Ar list Ns Li ;\ }
+.Pq the Ic function No keyword is ignored .
+.It Xo Ic if Ar list ;
+.Ic then Ar list ;
+.Oo Ic elif Ar list ;
+.Ic then Ar list ; Oc
+.No ...
+.Oo Ic else Ar list ; Oc
+.Ic 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 whatever non-conditional
+.Pq not the first
+.Ar list
+that is executed; if no non-conditional
+.Ar list
+is executed, the exit status is zero.
+.It Xo Ic select Ar name
+.Oo Ic in Ar word No ... Oc ;
+.Ic do Ar list ; Ic done
+.Xc
+The
+.Ic select
+statement provides an automatic method of presenting the user with a menu and
+selecting from it.
+An enumerated list of the specified
+.Ar word Ns s
+is printed on standard error, followed by a prompt
+.Po
+.Ev PS3 :
+normally
+.Dq Li #?\ \&
+.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 and 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 empty, 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.
+The exit status of a
+.Ic select
+statement is zero if a
+.Ic break
+statement is used to exit the loop, non-zero otherwise.
+If
+.Dq Ic in Ar word ...
+is omitted, the positional parameters are used.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done ,
+as in:
+.Dq Li select i; { echo $i; }
+.It Xo Ic time Op Fl p
+.Op Ar pipeline
+.Xc
+The
+.Sx Command execution
+section describes the
+.Ic time
+reserved word.
+.It Xo Ic until Ar list ;
+.Ic do Ar list ; Ic done
+.Xc
+This works like
+.Ic while Pq see below ,
+except that the body
+.Ar list
+is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo Ic while Ar list ;
+.Ic do Ar list ; Ic done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body
+.Ar list
+is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
+.It Bq Bq Ar \ \&expression\ \&
+Similar to the
+.Ic test
+and
+.Ic \&[ ... \&]
+commands (described later), with the following exceptions:
+.Bl -bullet
+.It
+Field splitting and globbing are not performed on arguments.
+.It
+The
+.Fl a
+.Pq AND
+and
+.Fl o
+.Pq OR
+operators are replaced, respectively, with
+.Dq Ic &&
+and
+.Dq Ic \*(Ba\*(Ba .
+.It
+Operators (e.g.\&
+.Dq Ic \-f ,
+.Dq Ic = ,
+.Dq Ic \&! )
+must be unquoted.
+.It
+Parameter, command and arithmetic substitutions are performed as expressions
+are evaluated and lazy expression evaluation is used for the
+.Dq Ic &&
+and
+.Dq Ic \*(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
+.Dq Ic =
+and
+.Dq Ic !=
+expressions is a pattern (e.g. the comparison
+.Ic \&[[ foobar = f*r ]]
+succeeds).
+This even works indirectly, while quoting forces literal interpretation:
+.Bd -literal -offset indent
+$ bar=foobar; baz=\*(aqf*r\*(aq # or: baz=\*(aqf+(o)b?r\*(aq
+$ [[ $bar = $baz ]]; echo $? # 0
+$ [[ $bar = \&"$baz" ]]; echo $? # 1
+.Ed
+.El
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+.br
+Note that
+.Dq Li {
+and
+.Dq Li }
+are reserved words, not meta-characters.
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell, forking.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It \&(( Ar expression No ))
+The arithmetic expression
+.Ar expression
+is evaluated; equivalent to
+.Sq Li let \&" Ns Ar expression Ns \&"
+in a compound construct.
+.br
+See the
+.Ic let
+command and
+.Sx Arithmetic expressions
+below.
+.El
+.Ss Quoting
+Quoting is used to prevent the shell from treating characters or words
+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 Dq Li \*(aq
+quotes everything up to the next single quote (this may span lines).
+Third, a double quote
+.Pq Ql \&"
+quotes all characters, except
+.Ql $ ,
+.Ql \e
+and
+.Ql \` ,
+up to the next unescaped double quote.
+.Ql $
+and
+.Ql \`
+inside double quotes have their usual meaning (i.e. parameter, arithmetic
+or command substitution) except no field splitting is carried out on the
+results of double-quoted substitutions, and the old-style form of command
+substitution has backslash-quoting for double quotes enabled.
+If a
+.Ql \e
+inside a double-quoted string is followed by
+.Ql \&" ,
+.Ql $ ,
+.Ql \e
+or
+.Ql \` ,
+only the
+.Ql \e
+is removed, i.e. the combination 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
+.Ql $
+is simply 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
+.Dq Li \ea ,
+.Dq Li \eb ,
+.Dq Li \ef ,
+.Dq Li \en ,
+.Dq Li \er ,
+.Dq Li \et ,
+.Dq Li \eU######## ,
+.Dq Li \eu####
+and
+.Dq Li \ev .
+For
+.Dq Li \eU########
+and
+.Dq Li \eu#### ,
+.Sq Li #
+means a hexadecimal digit (up to 4 or 8); these translate a
+Universal Coded Character Set codepoint to UTF-8 (see
+.Sx CAVEATS
+on UCS limitations).
+Furthermore,
+.Dq Li \eE
+and
+.Dq Li \ee
+expand to the escape character.
+.Pp
+In the
+.Ic print
+builtin mode,
+octal sequences must have the optional up to three octal digits
+.Sq Li #
+prefixed with the digit zero
+.Pq Dq Li \e0### ;
+hexadecimal sequences
+.Dq Li \ex##
+are limited to up to two hexadecimal digits
+.Sq Li # ;
+both octal and hexadecimal sequences convert to raw octets;
+.Dq Li \e% ,
+where
+.Sq Li %
+is none of the above, translates to
+.Li \e%
+.Pq backslashes are retained .
+.Pp
+In C style mode, raw octet-yielding octal sequences
+.Dq Li \e###
+must not have the one up to three octal digits prefixed with the
+digit zero; hexadecimal sequences
+.Dq Li \ex##
+greedily eat up as many hexadecimal digits
+.Sq Li #
+as they can and terminate with the first non-xdigit; below
+.Li \ex100
+these produce raw octets; above, they are equivalent to
+.Dq Li \eU# .
+The sequence
+.Dq Li \ec% ,
+where
+.Sq Li %
+is any octet, translates to
+.Ic Ctrl- Ns Li % ,
+that is,
+.Dq Li \ec?
+becomes DEL, everything else is bitwise ANDed with 0x9F.
+.Dq Li \e% ,
+where
+.Sq Li %
+is none of the above, translates to
+.Li % :
+backslashes are trimmed even before newlines.
+.Ss Aliases
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long or often used
+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.
+Aliases are specifically an interactive feature: while they do happen
+to work in scripts and on the command line in some cases, aliases are
+expanded during lexing, so their use must be in a separate command tree
+from their definition; otherwise, the alias will not be found.
+Noticeably, command lists (separated by semicolon, in command substitutions
+also by newline) may be one same parse tree.
+.Pp
+The following command aliases are defined automatically by the shell:
+.Bd -literal -offset indent
+autoload=\*(aq\e\ebuiltin typeset \-fu\*(aq
+functions=\*(aq\e\ebuiltin typeset \-f\*(aq
+hash=\*(aq\e\ebuiltin alias \-t\*(aq
+history=\*(aq\e\ebuiltin fc \-l\*(aq
+integer=\*(aq\e\ebuiltin typeset \-i\*(aq
+local=\*(aq\e\ebuiltin typeset\*(aq
+login=\*(aq\e\ebuiltin exec login\*(aq
+nameref=\*(aq\e\ebuiltin typeset \-n\*(aq
+nohup=\*(aqnohup \*(aq
+r=\*(aq\e\ebuiltin fc \-e \-\*(aq
+type=\*(aq\e\ebuiltin whence \-v\*(aq
+.Ed
+.Pp
+Tracked aliases allow the shell to remember where it found a particular
+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 Fl 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 Fl o Ic trackall
+or
+.Ic set Fl 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 \`
+or (executed in the current environment)
+.Pf ${\ \& Ar command Ns \&;}
+and strip trailing newlines;
+and arithmetic substitutions take the form
+.Pf $(( Ns Ar expression Ns )) .
+Parsing the current-environment command substitution requires a space,
+tab or newline after the opening brace and that the closing brace be
+recognised as a keyword (i.e. is preceded by a newline or semicolon).
+They are also called funsubs (function substitutions) and behave like
+functions in that
+.Ic local
+and
+.Ic return
+work, and in that
+.Ic exit
+terminates the parent shell; shell options are shared.
+.Pp
+Another variant of substitution are the valsubs (value substitutions)
+.Pf ${\*(Ba\& Ns Ar command Ns \&;}
+which are also executed in the current environment, like funsubs, but
+share their I/O with the parent; instead, they evaluate to whatever
+the, initially empty, expression-local variable
+.Ev REPLY
+is set to within the
+.Ar command Ns s .
+.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 is stripped (i.e. no leading or trailing empty field
+is created by it); leading or trailing
+.Pf non- Ev IFS
+whitespace does create an empty field.
+.Pp
+Example: If
+.Ev IFS
+is set to
+.Dq Li \*(Ltspace\*(Gt:
+and VAR is set to
+.Dq Li \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D ,
+the substitution for $VAR results in four fields:
+.Dq Li A ,
+.Dq Li B ,
+.Dq
+(an empty field) and
+.Dq Li D .
+Note that if the
+.Ev IFS
+parameter is set to the empty string, no field splitting is done;
+if it 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:
+.Dq Li A ,
+.Dq Li B ,
+.Dq
+and
+.Dq Li D:E ,
+not
+.Dq Li A ,
+.Dq Li B ,
+.Dq ,
+.Dq Li D
+and
+.Dq Li 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 \&)
+and
+.Pf ${\*(Ba\& Ns Ar command Ns \&;}
+and
+.Pf ${\ \& 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 (as is
+.Ql \&"
+when the substitution is part of a double-quoted string); a backslash
+.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
+.Dq Li x=$(cat) \*(Lt\*(Lt\eEOF
+(or the newline-keeping
+.Dq Li x=\*(Lt\*(Lt\eEOF
+extension) instead to merely slurp the string.
+.St -p1003.1
+recommends using case statements of the form
+.Li "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\eEOF
+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.
+Substitutions of an an array in scalar context, i.e. without an
+.Ar expr
+in the latter form mentioned above, expand the element with the key
+.Dq 0 .
+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, an empty string is substituted unless the
+.Ic nounset
+option
+.Pq Ic set Fl u
+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
+.Dq Li # ,
+.Dq Li PWD
+and
+.Dq Li $ ;
+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 Li FOO
+to
+.Dq Li 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 string-concatenated with no separator.
+The fourth way of setting a parameter is with the
+.Ic export ,
+.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 Fl 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 empty,
+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 empty,
+.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 empty,
+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 empty,
+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 a script sourced using the
+.Dq Li \&.
+built-in).
+If
+.Ar word
+is omitted, the string
+.Dq Li parameter null or not set
+is used instead.
+.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 Ql \&"
+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 empty).
+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:
+.Pp
+.Bl -tag -width Ds -compact
+.It Pf ${# Ns Ar name Ns \&}
+The number of positional parameters if
+.Ar name
+is
+.Dq Li * ,
+.Dq Li @
+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 ) .
+.Ar name
+cannot be one of most special parameters (see below).
+.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.
+.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 ${...#...} but deletes from the end of the value.
+.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
+.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
+The longest match of
+.Ar pattern
+in the value of parameter
+.Ar name
+is replaced with
+.Ar string
+(deleted if
+.Ar string
+is empty; the trailing slash
+.Pq Ql /
+may be omitted in that case).
+A leading slash followed by
+.Ql #
+or
+.Ql %
+causes the pattern to be anchored at the beginning or end of
+the value, respectively; empty unanchored
+.Ar pattern Ns s
+cause no replacement; a single leading slash or use of a
+.Ar pattern
+that matches the empty string causes the replacement to
+happen only once; two leading slashes cause all occurrences
+of matches in the value to be replaced.
+May be slow on long strings.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf @/ Ar pattern / Ar string No }
+.Xc
+.Sm on
+The same as
+.Sm off
+.Xo
+.Pf ${ Ar name
+.Pf // Ar pattern / Ar string No } ,
+.Xc
+.Sm on
+except that both
+.Ar pattern
+and
+.Ar string
+are expanded anew for each iteration.
+Use with
+.Ev KSH_MATCH .
+.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.
+.Pp
+.It Pf ${ Ns Ar name Ns @#}
+The hash (using the BAFH algorithm) of the expansion of
+.Ar name .
+This is also used internally for the shell's hashtables.
+.Pp
+.It Pf ${ Ns Ar name Ns @Q}
+A quoted expression safe for re-entry, whose value is the value of the
+.Ar name
+parameter, is substituted.
+.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, if it is a subshell, the PID of the original shell.
+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, but at most 255.
+.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 name the shell was invoked with
+.Pq i.e.\& Li argv[0] .
+.Ev $0
+is also set to the name of the current script,
+or to the name of the current function if it was defined with the
+.Ic function
+keyword (i.e. a Korn shell style function).
+.It Ev 1 No .. Ev 9
+The first nine positional parameters that were supplied to the shell, function,
+or script sourced using the
+.Dq Li \&.
+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 unset.
+.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
+empty arguments or splitting arguments with spaces (IFS, actually).
+.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 BASHPID
+The PID of the shell or subshell.
+.It Ev CDPATH
+Like
+.Ev PATH ,
+but used to resolve the argument to the
+.Ic cd
+built-in command.
+Note that if
+.Ev CDPATH
+is set and does not contain
+.Dq Li \&.
+or an empty string element, 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.
+If never unset and not imported, always set dynamically;
+unless the value as reported by
+.Xr stty 1
+is non-zero and sane enough (minimum is 12x3), defaults to 80; similar for
+.Ev LINES .
+This parameter is used by the interactive line editing modes and by the
+.Ic select ,
+.Ic set Fl o
+and
+.Ic kill Fl l
+commands to format information columns.
+Importing from the environment or unsetting this parameter removes the
+binding to the actual terminal size in favour of the provided value.
+.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 EPOCHREALTIME
+Time since the epoch, as returned by
+.Xr gettimeofday 2 ,
+formatted as decimal
+.Va tv_sec
+followed by a dot
+.Pq Ql \&.
+and
+.Va tv_usec
+padded to exactly six decimal digits.
+.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 Li #! 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 or unset, the file is opened, history is truncated
+then loaded from the file; subsequent new commands (possibly consisting
+of several lines) are appended once they successfully compiled.
+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
+is unset or empty, 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 2047.
+The maximum is 65535.
+.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 at startup.
+.It Ev KSHGID
+The real group id of the shell at startup.
+.It Ev KSHUID
+The real user id of the shell at startup.
+.It Ev KSH_MATCH
+The last matched string.
+In a future version, this will be an indexed array,
+with indexes 1 and up capturing matching groups.
+Set by string comparisons (= and !=) in double-bracket test
+expressions when a match is found (when != returns false), by
+.Ic case
+when a match is encountered, and by the substitution operations
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf # Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf ## Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf % Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf %% Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf / Ar pat / Ar rpl No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf /# Ar pat / Ar rpl No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf /% Ar pat / Ar rpl No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf // Ar pat / Ar rpl No } ,
+.Sm on
+.Xc
+and
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf @/ Ar pat / Ar rpl No } .
+.Sm on
+.Xc
+See the end of the Emacs editing mode documentation for an example.
+.It Ev KSH_VERSION
+The name (self-identification) 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.
+Defaults to 24; always set, unless imported or unset.
+See
+.Ev COLUMNS .
+.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 (semicolon on OS/2) separated list of directories that are
+searched when looking for commands and files sourced using the
+.Dq Li \&.
+command (see below).
+An empty string resulting from a leading or trailing
+(semi)colon, or two adjacent ones, is treated as a
+.Dq Li \&.
+(the current directory).
+.It Ev PATHSEP
+A colon (semicolon on OS/2), for the user's convenience.
+.It Ev PGRP
+The process ID of the shell's process group leader.
+.It Ev PIPESTATUS
+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
+.Dq Li !!
+in
+.Ev PS1 .
+.Pp
+The default prompt is
+.Dq Li $\ \&
+for non-root users,
+.Dq Li #\ \&
+for root.
+If
+.Nm
+is invoked by root and
+.Ev PS1
+does not contain a
+.Ql #
+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 occurrences 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) # otherwise unused char
+PS1="$x$(print \e\er)$x$(tput so)$x\e$PWD$x$(tput se)$x\*(Gt "
+.Ed
+.Pp
+Due to a strong suggestion from David G. Korn,
+.Nm
+now also supports the following form:
+.Bd -literal -offset indent
+PS1=$\*(aq\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt \*(aq
+.Ed
+.It Ev PS2
+Secondary prompt string, by default
+.Dq Li \*(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
+.Dq Li #?\ \& .
+.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
+.Dq Li +\ \& .
+You may want to set it to
+.Dq Li \&[$EPOCHREALTIME]\ \&
+instead, to include timestamps.
+.It Ev PWD
+The current working directory.
+May be unset or empty 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 at startup.
+.El
+.Ss Tilde expansion
+Tilde expansion, which is done in parallel with parameter substitution,
+is applied to words starting with an unquoted
+.Ql \*(TI .
+In parameter assignments (such as those preceding a simple-command or those
+occurring in the arguments of a declaration utility), tilde expansion is done
+after any assignment (i.e. after the equals sign) or after an unquoted colon
+.Pq Ql \&: ;
+login names are also delimited by colons.
+The Korn shell, except in POSIX mode, always expands tildes after unquoted
+equals signs, not just in assignment context (see below), and enables tab
+completion for tildes after all unquoted colons during command line editing.
+.Pp
+The characters following the tilde, up to the first
+.Ql / ,
+if any, are assumed to be a login name.
+If the login name is empty,
+.Ql +
+or
+.Ql \- ,
+the simplified 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
+The home directory of previously expanded login names are cached and re-used.
+The
+.Ic alias Fl 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 (alternation)
+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 Li a{c,b{X,Y},d}e
+expands to four words:
+.Dq Li ace ,
+.Dq Li abXe ,
+.Dq Li abYe
+and
+.Dq Li 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 Ql \&,
+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 Li \&[...]
+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 Li \&[a0\-9]
+matches the letter
+.Ql 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 Li foo ,
+.Dq Li bar ,
+.Dq Li 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 Li foo ,
+.Dq Li bar ,
+.Dq Li 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 Li foo
+and
+.Dq Li 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 Li foo
+and
+.Dq Li 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 Li foo
+and
+.Dq Li bar ;
+the pattern
+.Ic \&!(*)
+matches no strings; the pattern
+.Ic \&!(?)*
+matches all strings (think about it).
+.El
+.Pp
+Note that complicated globbing, especially with alternatives,
+is slow; using separate comparisons may (or may not) be faster.
+.Pp
+Note that
+.Nm mksh
+.Po and Nm pdksh Pc
+never matches
+.Dq Li \&.
+and
+.Dq Li .. ,
+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 Ql \&.
+at the start of a file name or a slash
+.Pq Ql / ,
+even if they are explicitly used in a [...] sequence; also, the names
+.Dq Li \&.
+and
+.Dq Li ..
+are never matched, even by the pattern
+.Dq Li .* .
+.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
+.Pa /dev/null ,
+and commands for which any of the following redirections have been specified:
+.Bl -tag -width XXxxmarker
+.It \*(Gt Ns 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 Ns Ar file
+Same as
+.Ic \*(Gt ,
+except the file is truncated, even if the
+.Ic noclobber
+option is set.
+.It \*(Gt\*(Gt Ns 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 Ns Ar file
+Standard input is redirected from
+.Ar file ,
+which is opened for reading.
+.It \*(Lt\*(Gt Ns Ar file
+Same as
+.Ic \*(Lt ,
+except the file is opened for reading and writing.
+.It \*(Lt\*(Lt Ns 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 Ql \e
+escapes for
+.Ql $ ,
+.Ql \` ,
+.Ql \e
+and
+.Dq Li \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 Li \*(aq\*(aq
+or double
+.Ql \&""
+quotes with nothing in between, the here document ends at the next empty line
+and substitution will not be performed.
+.It \*(Lt\*(Lt\- Ns Ar marker
+Same as
+.Ic \*(Lt\*(Lt ,
+except leading tabs are stripped from lines in the here document.
+.It \*(Lt\*(Lt\*(Lt Ns Ar word
+Same as
+.Ic \*(Lt\*(Lt ,
+except that
+.Ar word
+.Em is
+the here document.
+This is called a here string.
+.It \*(Lt& Ns Ar fd
+Standard input is duplicated from file descriptor
+.Ar fd .
+.Ar fd
+can be a single digit, 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.
+.It \*(Gt& Ns Ar fd
+Same as
+.Ic \*(Lt& ,
+except the operation is done on standard output.
+.It &\*(Gt Ns Ar file
+Same as
+.Ic \*(Gt Ns Ar file 2\*(Gt&1 .
+This is a deprecated (legacy) GNU
+.Nm bash
+extension supported by
+.Nm
+which also supports the preceding explicit fd digit, for example,
+.Ic 3&\*(Gt Ns Ar file
+is the same as
+.Ic 3\*(Gt Ns Ar file 2\*(Gt&3
+in
+.Nm
+but a syntax error in GNU
+.Nm bash .
+.It Xo
+.No &\*(Gt\*(Ba Ns Ar file ,
+.No &\*(Gt\*(Gt Ns Ar file ,
+.No &\*(Gt& Ns Ar fd
+.Xc
+Same as
+.Ic \*(Gt\*(Ba Ns Ar file ,
+.Ic \*(Gt\*(Gt Ns Ar file
+or
+.Ic \*(Gt& Ns 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 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
+.Dl $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
+.Pp
+File descriptors created by I/O redirections are private to the shell.
+.Ss Arithmetic expressions
+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.
+.Em Warning :
+This also affects implicit conversion to integer, for example as done by the
+.Ic let
+command.
+.Em Never
+use unchecked user input, e.g. from the environment, in an arithmetic context!
+.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
+.Ql #
+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\*(Lt= \*(ha\*(Gt= &= \*(ha= \*(Ba=
+\*(Ba\*(Ba
+&&
+\*(Ba
+\*(ha
+&
+== !=
+\*(Lt \*(Lt= \*(Gt \*(Gt=
+\*(Lt\*(Lt \*(Gt\*(Gt \*(ha\*(Lt \*(ha\*(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 an exactly 32-bit
+wide, signed or unsigned, type with silent wraparound on integer overflow.
+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 (up to 36), and
+.Ar number
+is a number in the specified base.
+Additionally, base-16 integers may be specified by prefixing them with
+.Dq Li 0x
+.Pq case-insensitive
+in all forms of arithmetic expressions, except as numeric arguments to the
+.Ic test
+built-in utility.
+Prefixing numbers with a sole digit zero
+.Pq Dq Li 0
+does not cause interpretation as octal (except in POSIX mode,
+as required by the standard), as that's unsafe to do.
+.Pp
+As a special
+.Nm mksh
+extension, numbers to the base of one are treated as either (8-bit
+transparent) ASCII or Universal Coded Character Set codepoints,
+depending on the shell's
+.Ic utf8\-mode
+flag (current setting).
+The
+.At
+.Nm ksh93
+syntax of
+.Dq Li \*(aqx\*(aq
+instead of
+.Dq Li 1#x
+is also supported.
+Note that NUL bytes (integral value of zero) cannot be used.
+An unset or empty parameter evaluates to 0 in integer context.
+If
+.Sq Li x
+isn't comprised of exactly one valid character, the behaviour is undefined
+(usually, the shell aborts with a parse error, but rarely, it succeeds,
+e.g. on the sequence C2 20); users of this feature (as opposed to
+.Ic read Fl a )
+must validate the input first.
+See
+.Sx CAVEATS
+for UTF-8 mode handling.
+.Pp
+The operators are evaluated as follows:
+.Bl -tag -width Ds -offset indent
+.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= \*(Gt\*(Gt=
+.No \*(ha\*(Lt= \*(ha\*(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 Li var1 *= 5 + 3
+is the same as specifying
+.Dq Li 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, greater than or equal.
+See
+.Ic \*(Lt .
+.It \*(Lt\*(Lt \*(Gt\*(Gt
+Shift left (right); the result is the left argument with its bits
+arithmetically (signed operation) or logically (unsigned expression)
+shifted left (right) by the amount given in the right argument.
+.It \*(ha\*(Lt \*(ha\*(Gt
+Rotate left (right); the result is similar to shift,
+except that the bits shifted out at one end are shifted in
+at the other end, instead of zero or sign bits.
+.It + \- * /
+Addition, subtraction, multiplication and division.
+.It %
+Remainder; the result is the symmetric remainder of the division of the left
+argument by the right.
+To get the mathematical modulus of
+.Dq a Ic mod No b ,
+use the formula
+.Do
+.Pq a % b + b
+.No % b
+.Dc .
+.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 .
+The non-result argument is not evaluated.
+.El
+.Ss Co-processes
+A co-process (which is a pipeline created with the
+.Dq Li \*(Ba&
+operator) is an asynchronous process that the shell can both write to (using
+.Ic print Fl p )
+and read from (using
+.Ic read Fl 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 Fl 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 Fl 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
+.Dq Li \&.
+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 builtins and 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 Fl f .
+The
+.Ic autoload
+command (which is an alias for
+.Ic typeset Fl 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 Fl ft
+and
+.Ic typeset Fl 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.
+.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)
+and allows local variables only on Korn style functions, 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.
+Note that when called in a subshell,
+.Ic return
+will only exit that subshell and will not cause the original shell to exit
+a running function (see the
+.Ic while Ns Li \&... Ns Ic read
+loop FAQ).
+.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
+.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
+Shell options
+.Pq Ic set Fl o
+have local scope, i.e. changes inside a function are reset upon its exit.
+.El
+.Pp
+In the future, the following differences may 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 command,
+a function, a normal builtin 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
+POSIX special built-in utilities:
+.Pp
+.Ic \&. , \&: , break , continue ,
+.Ic eval , exec , exit , export ,
+.Ic readonly , return , set , shift ,
+.Ic times , trap , unset
+.Pp
+Additional
+.Nm
+commands keeping assignments:
+.Pp
+.Ic source , typeset
+.Pp
+All other builtins are not special; these are at least:
+.Pp
+.Ic [\& , alias , bg , bind ,
+.Ic builtin , cat , cd , command ,
+.Ic echo , false , fc , fg ,
+.Ic getopts , jobs , kill , let ,
+.Ic print , pwd , read , realpath ,
+.Ic rename , sleep , suspend , test ,
+.Ic true , ulimit , umask , unalias ,
+.Ic wait , whence
+.Pp
+Once the type of command has been determined, any command-line parameter
+assignments are performed and exported for the duration of the command.
+.Pp
+The following describes the special and regular built-in commands and
+builtin-like reserved words, as well as some optional utilities:
+.Pp
+.Bl -tag -width false -compact
+.It Ic \&. Ar file Op Ar arg ...
+.Pq keeps assignments , special
+This is called the
+.Dq dot
+command.
+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 ...
+.Pq keeps assignments , special
+The null command.
+.br
+Exit status is set to zero.
+.Pp
+.It Ic Lb64decode Op Ar string
+.Pq Li dot.mkshrc No function
+Decode
+.Ar string
+or standard input to binary.
+.Pp
+.It Ic Lb64encode Op Ar string
+.Pq Li dot.mkshrc No function
+Encode
+.Ar string
+or standard input as base64.
+.Pp
+.It Ic Lbafh_init
+.It Ic Lbafh_add Op Ar string
+.It Ic Lbafh_finish
+.Pq Li dot.mkshrc No functions
+Implement the Better Avalance for the Jenkins Hash.
+This is the same hash
+.Nm
+currently uses internally.
+.No "After calling" Ic Lbafh_init , No call Ic Lbafh_add
+multiple times until all input is read, then call
+.Ic Lbafh_finish ,
+which writes the result to the unsigned integer
+.Va Lbafh_v
+variable for your consumption.
+.Pp
+.It Ic Lstripcom Op Ar
+.Pq Li dot.mkshrc No function
+Same as
+.Ic cat
+but strips any empty lines and comments (from any
+.Sq #
+character onwards, no escapes)
+and reduces any amount of whitespace to one space character.
+.Pp
+.It Ic \&[ Ar expression Ic \&]
+.Pq regular
+See
+.Ic test .
+.Pp
+.It Xo Ic alias
+.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
+.Fl +x Oc
+.Op Fl p
+.Op Cm +
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq regular
+Without arguments,
+.Ic alias
+lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias; see
+.Sx Aliases
+above.
+.Li \&[][A\-Za\-z0\-9_!%+,.@:\-]
+are valid in names, except they may not begin with a plus or hyphen-minus, and
+.Ic \&[[
+is not a valid alias name.
+.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 as necessary.
+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
+With
+.Fl p ,
+each alias is listed with the string
+.Dq Li alias\ \&
+prefixed.
+.Pp
+The
+.Fl t
+option indicates that tracked aliases are to be listed/set (values given
+with the command are ignored for tracked aliases).
+.Pp
+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 autoload
+.Pq built-in alias
+See
+.Sx Functions
+above.
+.Pp
+.It Ic bg Op Ar job ...
+.Pq regular , needs job control
+Resume the specified stopped job(s) in the background.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic bind Fl l
+.Pq regular
+The names of editing commands strings can be bound to are listed.
+See
+.Sx Emacs editing mode
+for more information.
+.Pp
+.It Ic bind Op Ar string ...
+The current bindings, for
+.Ar string ,
+if given, else all, are listed.
+.Em Note:
+Default prefix bindings
+.Pq 1=Esc , 2=\*(haX , 3=NUL
+assumed.
+.Pp
+.It Xo Ic bind
+.Ar string Ns = Ns Op Ar editing-command
+.Op Ar ...
+.Xc
+.It Xo Ic bind Fl m
+.Ar string Ns = Ns Ar substitute
+.Op Ar ...
+.Xc
+To
+.Ar string ,
+which should consist of a control character
+optionally preceded by one of the three prefix characters
+and optionally succeeded by a tilde character, the
+.Ar editing-command
+is bound so that future input of the
+.Ar string
+will immediately invoke that editing command.
+If a tilde postfix is given, a tilde trailing the control character is ignored.
+If
+.Fl m
+.Pq macro
+is given, future input of the
+.Ar string
+will be replaced by the given NUL-terminated
+.Ar substitute
+string, wherein prefix/control/tilde characters mapped to editing commands
+.Pq but not those mapped to other macros
+will be processed.
+.Pp
+Prefix and control characters may be written using caret notation, i.e.\&
+.No \*(ha Ns Li Z
+represents
+.No Ctrl- Ns Li Z .
+Use a backslash to escape the caret, an equals sign or another backslash.
+Note that, although only three prefix characters
+.Pq usually Esc, \*(haX and NUL
+are supported, some multi-character sequences can be supported.
+.Pp
+.It Ic break Op Ar level
+.Pq keeps assignments , special
+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
+.Pq regular
+Execute the built-in command
+.Ar command .
+.Pp
+.It Xo
+.Ic \ebuiltin
+.Ar command Op Ar arg ...
+.Xc
+.Pq regular , decl-forwarder
+Same as
+.Ic builtin .
+Additionally acts as declaration utility forwarder, i.e. this is a
+declaration utility (see
+.Sx Tilde expansion )
+.No iff Ar command
+is a declaration utility.
+.Pp
+.It Xo
+.Ic cat
+.Op Fl u
+.Op Ar
+.Xc
+.Pq defer with flags
+Copy files in command line order to standard output.
+If a
+.Ar file
+is a single dash
+.Pq Dq Li \-
+or absent, read from standard input.
+For direct builtin calls, the
+.Tn POSIX
+.Fl u
+option is supported as a no-op.
+For calls from shell, if any options are given, an external
+.Xr cat 1
+utility is preferred over the builtin.
+.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
+.Pq regular
+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 .
+An unset or empty path means the current directory.
+If
+.Ar dir
+is found in any component of the
+.Ev CDPATH
+search path other than an unset or empty 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
+.Dq Li \- ,
+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
+.Dq Li ..
+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,
+.Dq Li ..
+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
+.Pq regular
+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 Ic cls
+.Pq Li dot.mkshrc No alias
+Reinitialise the display (hard reset).
+.Pp
+.It Xo
+.Ic command
+.Op Fl pVv
+.Ar cmd
+.Op Ar arg ...
+.Xc
+.Pq regular , decl-forwarder
+If neither the
+.Fl v
+nor
+.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, whose actual value is
+system-dependent, is used instead of the current
+.Ev PATH .
+.Pp
+If the
+.Fl v
+option is given, instead of executing
+.Ar cmd ,
+information about what would be executed is given for each argument.
+For builtins, functions and keywords, their names are simply printed;
+for aliases, a command that defines them is printed;
+for utilities found by searching the
+.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, but more verbose.
+.Pp
+.It Ic continue Op Ar level
+.Pq keeps assignments , special
+Jumps to the beginning of the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Ic dirs Op Fl lnv
+.Pq Li dot.mkshrc No function
+Print the directory stack.
+.Fl l
+causes tilde expansion to occur in the output.
+.Fl n
+causes line wrapping before 80 columns, whereas
+.Fl v
+causes numbered vertical output.
+.Pp
+.It Ic doch
+.Pq Li dot.mkshrc No alias
+Execute the last command with
+.Xr sudo 8 .
+.Pp
+.It Xo
+.Ic echo
+.Op Fl Een
+.Op Ar arg ...
+.Xc
+.Pq regular
+.Em Warning:
+this utility is not portable; use the standard Korn shell built-in utility
+.Ic print
+in new code instead.
+.Pp
+Print arguments, separated by spaces, followed by a newline, to
+standard output.
+The newline is suppressed if any of the arguments contain the
+backslash sequence
+.Dq Li \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 E
+option suppresses backslash interpretation,
+.Fl e
+enables it (normally default),
+.Fl n
+suppresses the trailing newline,
+and anything else causes the word to be printed as argument instead.
+.Pp
+If the
+.Ic posix
+or
+.Ic sh
+option is set or this is a direct builtin call or
+.Ic print
+.Fl R ,
+only the first argument is treated as an option, and only if it is exactly
+.Dq Li \-n .
+Backslash interpretation is disabled.
+.Pp
+.It Xo
+.Ic enable
+.Op Fl anps
+.Op Ar name ...
+.Xc
+.Pq Li dot.mkshrc No function
+Hide and unhide built-in utilities, aliases and functions and those defined in
+.Li dot.mkshrc .
+.Pp
+If no
+.Ar name
+is given or the
+.Fl p
+option is used, builtins are printed (behind the string
+.Dq Li enable\ \& ,
+followed by
+.Dq Li \-n\ \&
+if the builtin is currently disabled), otherwise, they are disabled (if
+.Fl n
+is given) or re-enabled.
+.Pp
+When printing, only enabled builtins are printed by default; the
+.Fl a
+options prints all builtins, while
+.Fl n
+prints only disabled builtins instead;
+.Fl s
+limits the list to POSIX special builtins.
+.Pp
+.It Ic eval Ar command ...
+.Pq keeps assignments , special
+The arguments are concatenated, with a space between each,
+to form a single string which the shell then parses
+and executes in the current execution environment.
+.Pp
+.It Xo
+.Ic exec
+.Op Fl a Ar argv0
+.Op Fl c
+.Op Ar command Op Ar arg ...
+.Xc
+.Pq keeps assignments , special
+The command (with arguments) is executed without forking,
+fully replacing the shell process; this is absolute, i.e.\&
+.Ic exec
+never returns, even if the
+.Ar command
+is not found.
+The
+.Fl a
+option permits setting a different
+.Li argv[0]
+value, and
+.Fl c
+clears the environment before executing the child process, except for the
+.Ev _
+parameter and direct assignments.
+.Pp
+If no command is given except for I/O redirection, the I/O redirection is
+permanent and the shell is
+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
+.Pq keeps assignments , special
+The shell or subshell exits with the specified errorlevel
+(or the current value of the
+.Va $?\&
+parameter).
+.Pp
+.It Xo
+.Ic export
+.Op Fl p
+.Op Ar parameter Ns Op = Ns Ar value
+.Xc
+.Pq keeps assignments , special, decl-util
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters are also assigned.
+This is a declaration utility.
+.Pp
+If no parameters are specified, all parameters with the export attribute
+set are printed one per line: either their names, or, if a
+.Dq Li \-
+with no option letter is specified, name=value pairs, or, with the
+.Fl p
+option,
+.Ic export
+commands suitable for re-entry.
+.Pp
+.It Ic extproc
+.Pq OS/2
+Null command required for shebang-like functionality.
+.Pp
+.It Ic false
+.Pq regular
+A command that exits with a non-zero status.
+.Pp
+.It Xo
+.Ic fc
+.Oo Fl e Ar editor \*(Ba
+.Fl l Op Fl n Oc
+.Op Fl r
+.Op Ar first Op Ar last
+.Xc
+.Pq regular
+.Ar first
+and
+.Ar last
+select commands from the history.
+Commands can be selected by history number
+(negative numbers go backwards from the current, most recent, line)
+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 the result is 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
+.Pq regular
+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
+.Pp
+.It Ic fg Op Ar job ...
+.Pq regular , needs job control
+Resume the specified job(s) in the foreground.
+If no jobs are specified,
+.Ic %+
+is assumed.
+.br
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic functions Op Ar name ...
+.Pq built-in alias
+Display the function definition commands corresponding to the listed,
+or all defined, functions.
+.Pp
+.It Xo
+.Ic getopts
+.Ar optstring name
+.Op Ar arg ...
+.Xc
+.Pq regular
+Used by shell procedures to parse the specified arguments (or positional
+parameters, if no arguments are given) and to check for legal options.
+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 word it is found in, the remainder of the word is taken to
+be the option's argument; otherwise, the next word is the option's argument.
+.Pp
+.Ar optstring
+contains the option letters to be recognised.
+If a letter is followed by a colon, the option takes an argument.
+.Pp
+Each time
+.Ic getopts
+is invoked, it places the next option in the shell parameter
+.Ar name .
+If the option was introduced with a
+.Ql + ,
+the character placed in
+.Ar name
+is prefixed with a
+.Ql + .
+If the option takes an argument, it is placed in the shell parameter
+.Ev OPTARG.
+.Pp
+When an illegal option or a missing option argument is encountered, a question
+mark or a colon is placed in
+.Ar name
+(indicating an illegal option or missing argument, respectively) and
+.Ev OPTARG
+is set to the option letter that caused the problem.
+Furthermore, unless
+.Ar optstring
+begins with a colon, a question mark is placed in
+.Ar name ,
+.Ev OPTARG
+is unset and a diagnostic is shown on standard error.
+.Pp
+.Ic getopts
+records the index of the argument to be processed by the next call in
+.Ev OPTIND .
+When the end of the options is encountered,
+.Ic getopts
+returns a non-zero exit status.
+Options end at the first argument that does not start with a
+.Ql \-
+.Pq non-option argument
+or when a
+.Dq Li \-\-
+argument is encountered.
+.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
+.Em Warning:
+Changing the value of the shell parameter
+.Ev OPTIND
+to a value other than 1 or parsing different sets of arguments without
+resetting
+.Ev OPTIND
+may lead to unexpected results.
+.Pp
+.It Xo
+.Ic hash
+.Op Fl r
+.Op Ar name ...
+.Xc
+.Pq built-in alias
+Without arguments, any hashed executable command paths are listed.
+The
+.Fl r
+option causes all hashed commands to be removed from the cache.
+Each
+.Ar name
+is searched as if it were a command name
+and added to the cache if it is an executable command.
+.Pp
+.It Ic hd Op Ar
+.Pq Li dot.mkshrc No alias or function
+Hexdump stdin or arguments legibly.
+.Pp
+.It Xo
+.Ic history
+.Op Fl nr
+.Op Ar first Op Ar last
+.Xc
+.Pq built-in alias
+Same as
+.Ic fc Fl l Pq see above .
+.Pp
+.It Xo
+.Ic integer
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Fl i Pq see below .
+.Pp
+.It Xo
+.Ic jobs
+.Op Fl lnp
+.Op Ar job ...
+.Xc
+.Pq regular
+Display information about the specified job(s); if no jobs are specified, all
+jobs are displayed.
+The
+.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
+.Pq regular
+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
+.Pq regular
+Print the signal name corresponding to
+.Ar exit-status .
+If no arguments are specified, a list of all the signals with their numbers
+and a short description of each are printed.
+.Pp
+.It Ic let Op Ar expression ...
+.Pq regular
+Each expression is evaluated (see
+.Sx Arithmetic expressions
+above).
+If all expressions evaluate successfully, the exit status is
+0 (1) if the last expression evaluated to non-zero (zero).
+If an error occurs during
+the parsing or evaluation of an expression, the exit status is greater than 1.
+Since expressions may need to be quoted,
+.Li \&(( Ar expr Li ))
+is syntactic sugar for:
+.Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns Li "\*(aq; }"
+.Pp
+.It Xo
+.Ic local
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Pq see below .
+.Pp
+.It Xo
+.Ic mknod
+.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
+.Pq optional
+Create a device special file.
+The file type may be one of
+.Cm b
+(block type device),
+.Cm c
+(character type device)
+or
+.Cm p
+.Pq named pipe , Tn FIFO .
+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).
+This is not normally part of
+.Nm mksh ;
+however, distributors may have added this as builtin as a speed hack.
+.Pp
+.It Xo
+.Ic nameref
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Fl n Pq see below .
+.Pp
+.It Xo
+.Ic popd
+.Op Fl lnv
+.Op Ic + Ns Ar n
+.Xc
+.Pq Li dot.mkshrc No function
+Pops the directory stack and returns to the new top directory.
+The flags are as in
+.Ic dirs Pq see above .
+A numeric argument
+.Ic + Ns Ar n
+selects the entry in the stack to discard.
+.Pp
+.It Xo
+.Ic print
+.Oo Fl AcelNnprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl n Oc
+.Op Ar argument ...
+.Xc
+.Pq regular
+Print the specified argument(s) on the standard output,
+separated by spaces, terminated with a newline.
+The escapes mentioned in
+.Sx Backslash expansion
+above, as well as
+.Dq Li \ec ,
+which is equivalent to using the
+.Fl n
+option, are interpreted.
+.Pp
+The options are as follows:
+.Bl -tag -width XuXnX
+.It Fl A
+Each
+.Ar argument
+is arithmetically evaluated; the character corresponding to the
+resulting value is printed.
+Empty
+.Ar argument Ns s
+separate input words.
+.It Fl c
+The output is printed columnised, line by line, similar to how the
+.Xr rs 1
+utility, tab completion, the
+.Ic kill Fl l
+built-in utility and the
+.Ic select
+statement do.
+.It Fl e
+Restore backslash expansion after a previous
+.Fl r .
+.It Fl l
+Change the output word separator to newline.
+.It Fl N
+Change the output word and line separator to ASCII NUL.
+.It Fl n
+Do not print the trailing line separator.
+.It Fl p
+Print to the co-process (see
+.Sx Co-processes
+above).
+.It Fl r
+Inhibit backslash expansion.
+.It Fl s
+Print to the history file instead of standard output.
+.It Fl u Ns Op Ar n
+Print to the file descriptor
+.Ar n Pq defaults to 1 if omitted
+instead of standard output.
+.El
+.Pp
+The
+.Fl R
+option mostly emulates the
+.Bx
+.Xr echo 1
+command which does not expand backslashes and interprets
+its first argument as option only if it is exactly
+.Dq Li \-n
+.Pq to suppress the trailing newline .
+.Pp
+.It Ic printf Ar format Op Ar arguments ...
+.Pq optional , defer always
+If compiled in, format and print the arguments, supporting the bare
+.Tn POSIX Ns -mandated
+minimum.
+If an external utility of the same name is found, it is deferred to,
+unless run as direct builtin call or from the
+.Ic builtin
+utility.
+.Pp
+.It Ic pushd Op Fl lnv
+.Pq Li dot.mkshrc No function
+Rotate the top two elements of the directory stack.
+The options are the same as for
+.Ic dirs Pq see above ,
+and
+.Ic pushd
+changes to the topmost directory stack entry after acting.
+.Pp
+.It Xo
+.Ic pushd
+.Op Fl lnv
+.Ic + Ns Ar n
+.Xc
+.Pq Li dot.mkshrc No function
+Rotate the element number
+.Ar n
+to the top.
+.Pp
+.It Xo
+.Ic pushd
+.Op Fl lnv
+.Ar name
+.Xc
+.Pq Li dot.mkshrc No function
+Push
+.Ar name
+on top of the stack.
+.Pp
+.It Ic pwd Op Fl LP
+.Pq regular
+Print the present working directory.
+If no options are given,
+.Ic pwd
+behaves as if the
+.Fl P
+option (print physical path) was used if the
+.Ic physical
+shell option is set, the
+.Fl L
+option (print logical path) otherwise.
+The logical path is the path used to
+.Ic cd
+to the current directory;
+the physical path is determined from the filesystem (by following
+.Dq Li ..\&
+directories to the root directory).
+.Pp
+.It Xo
+.Ic r
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+.Pq built-in alias
+Same as
+.Ic fc Fl e \& Pq see above .
+.Pp
+.It Xo
+.Ic read
+.Op Fl A \*(Ba 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
+.Pq regular
+Reads a line of input, separates the input into fields using the
+.Ev IFS
+parameter (see
+.Sx Substitution
+above) or other specified means,
+and assigns each field to the specified parameters
+.Ar p .
+If no parameters are specified, the
+.Ev REPLY
+parameter is used to store the result.
+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.
+Only no or one parameter is accepted.
+.It Fl a
+Store the result, without applying IFS 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); the codepoints are
+encoded as decimal numbers by default.
+Only no or one parameter is accepted.
+.It Fl d Ar x
+Use the first byte of
+.Ar x ,
+.Dv NUL
+if empty, instead of the ASCII newline character to delimit input lines.
+.It Fl N Ar z
+Instead of reading till end-of-line, read exactly
+.Ar z
+bytes.
+Upon EOF, a partial read is returned with exit status 1.
+After timeout, a partial read is returned with an exit status as if
+.Dv SIGALRM
+were caught.
+.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) instead of from a file descriptor.
+.It Fl u Ns Op Ar n
+Read from the file descriptor number
+.Ar n
+(defaults to 0, i.e.\& standard input).
+.br
+The argument must immediately follow the option character.
+.It Fl t Ar n
+Interrupt reading after
+.Ar n
+seconds (specified as positive decimal value with an optional fractional part).
+The exit status of
+.Nm read
+is the same as if
+.Dv SIGALRM
+were caught if the timeout occurred, but partial reads may still be returned.
+.It Fl r
+Normally,
+.Ic read
+strips backslash-newline sequences and any remaining backslashes from input.
+This option enables raw mode, in which backslashes are retained and ignored.
+.It Fl s
+The input line is saved to the history.
+.El
+.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
+.It Xo
+.Ic readonly
+.Op Fl p
+.Oo Ar parameter
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq keeps assignments , special, decl-util
+Sets the read-only attribute of the named parameters.
+If values are given,
+parameters are assigned these before disallowing writes.
+Once a parameter is made read-only,
+it cannot be unset and its value cannot be changed.
+.Pp
+If no parameters are specified, the names of all parameters with the read-only
+attribute are printed one per line, unless the
+.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
+.Pq defer with flags
+Resolves an absolute pathname corresponding to
+.Ar name .
+If the resolved pathname either exists or can be created immediately,
+.Ic realpath
+returns 0 and prints the resolved pathname,
+otherwise or if an error occurs, it issues a diagnostic and returns nonzero.
+If
+.Ar name
+ends with a slash
+.Pq Ql / ,
+resolving to an extant non-directory is also treated as error.
+.Pp
+.It Xo
+.Ic rename
+.Op Fl \-
+.Ar from to
+.Xc
+.Pq defer always
+Renames the file
+.Ar from
+to
+.Ar to .
+Both must be complete pathnames and on the same device.
+Intended for emergency situations
+.Pq where Pa /bin/mv No becomes unusable ;
+directly calls
+.Xr rename 2 .
+.Pp
+.It Ic return Op Ar status
+.Pq keeps assignments , special
+Returns from a function or
+.Ic \&.
+script with errorlevel
+.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 treated profiles as
+.Ic \&.
+scripts.
+.Pp
+.It Ic rot13
+.Pq Li dot.mkshrc No alias
+ROT13-encrypts/-decrypts stdin to stdout.
+.Pp
+.It Xo
+.Ic set Op Fl +abCefhiklmnprsUuvXx
+.Op Fl +o Ar option
+.Op Fl +A Ar name
+.Op Fl \-
+.Op Ar arg ...
+.Xc
+.Pq keeps assignments , special
+The
+.Ic set
+command can be used to show all shell parameters
+.Pq like Ic typeset \- ,
+set
+.Pq Ic \-
+or clear
+.Pq Ic +
+shell options, set an array parameter or the positional parameters.
+.Pp
+Options can be changed using the
+.Fl +o Ar option
+syntax, where
+.Ar option
+is the long name of an option, or using the
+.Fl + Ns Ar letter
+syntax, where
+.Ar letter
+is the option's single letter name (not all options have a single letter name).
+The following table lists short (if extant) and long names
+along with a description of what each option does:
+.Bl -tag -width 3n
+.It Fl A Ar name
+Sets the elements of the array parameter
+.Ar name
+to
+.Ar arg ...
+.Pp
+If
+.Fl A
+is used, the array is reset (i.e. emptied) first; if
+.Ic +A
+is used, the first N elements are set (where N is the number of arguments);
+the rest are left untouched.
+If
+.Ar name
+ends with a
+.Sq + ,
+the array is appended to instead.
+.Pp
+An alternative syntax for the command
+.Ic set \-A foo \-\- a b c;
+.Ic set \-A foo+ \-\- d e
+which is compatible to
+.Tn GNU
+.Nm bash
+and also supported by
+.At
+.Nm ksh93
+is:
+.Ic foo=(a b c); foo+=(d e)
+.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 with job control
+.Pq Fl m .
+.It Fl C \*(Ba Fl o Ic noclobber
+Prevent \*(Gt redirection from overwriting existing files.
+Instead, \*(Gt\*(Ba must be used to force an overwrite.
+.Em Note:
+This is not safe to use for creation of temporary files or
+lockfiles due to a TOCTOU in a check allowing one to redirect output to
+.Pa /dev/null
+or other device files even in
+.Ic noclobber
+mode.
+.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 !\& ,
+.Ic if ,
+.Ic until
+or
+.Ic while
+statements.
+For
+.Ic && ,
+.Ic \*(Ba\*(Ba
+and pipelines (but mind
+.Fl o Ic pipefail ) ,
+only the status of the last command is tested.
+.It Fl f \*(Ba Fl o Ic noglob
+Do not expand file name patterns.
+.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 details.
+.It Fl k \*(Ba Fl o Ic keyword
+Parameter assignments are recognised anywhere in a command.
+.It Fl l \*(Ba Fl o Ic login
+The shell is a login shell.
+This option can only be used when the shell is invoked.
+See above for 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 reading commands from a tty.
+.It Fl p \*(Ba Fl o Ic privileged
+The shell is a privileged shell.
+It is set automatically if, when the shell starts,
+the real UID or GID does not match
+the effective UID (EUID) or GID (EGID), respectively.
+See above for a description of what this means.
+.Pp
+If the shell is privileged, setting this flag after startup files
+have been processed let it go full setuid and/or setgid.
+Clearing this flag makes the shell drop privileges.
+Changing this flag resets the groups vector.
+.It Fl r \*(Ba Fl o Ic restricted
+The shell is a restricted shell.
+This option can only be used when the shell is invoked.
+See above for 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 ASCIIbetically
+before assigning them to the positional parameters (or to array
+.Ar name ,
+with
+.Fl A ) .
+.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.
+.Pp
+In near future, locale tracking will be implemented, which means that
+.Ic set Fl +U
+is changed whenever one of the
+.Tn POSIX
+locale-related environment variables changes.
+.It Fl u \*(Ba Fl o Ic nounset
+Referencing of an unset parameter, other than
+.Dq Li $@
+or
+.Dq Li $* ,
+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 globbing.
+.It Fl x \*(Ba Fl o Ic xtrace
+Print commands when they are executed, preceded by
+.Ev PS4 .
+.It Fl o Ic bgnice
+Background jobs are run with lower priority.
+.It Fl o Ic braceexpand
+Enable brace expansion.
+This is enabled by default.
+.It Fl o Ic emacs
+Enable BRL emacs-like command-line editing (interactive shells only); see
+.Sx Emacs editing mode .
+Enabled by default.
+.It Fl o Ic gmacs
+Enable gmacs-like command-line editing (interactive shells only).
+Currently identical to emacs editing except that
+.Li transpose\-chars Pq \*(haT
+acts slightly differently.
+.It Fl o Ic ignoreeof
+The shell will not (easily) exit when end-of-file is read;
+.Ic exit
+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 inherit\-xtrace
+Do not reset
+.Fl o Ic xtrace
+upon entering functions (default).
+.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 prevented 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)
+.Dq Li ..
+directories instead of
+.Dq logical
+directories (i.e. the shell handles
+.Dq Li .. ,
+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
+.Ic cd
+and
+.Ic pwd
+above for more details.
+.It Fl o Ic pipefail
+Make the exit status of a pipeline the rightmost non-zero errorlevel,
+or zero if all commands exited with zero.
+.It Fl o Ic posix
+Behave closer to the standards
+(see
+.Sx POSIX mode
+for details).
+Automatically enabled if the shell invocation basename, after
+.Sq \-
+and
+.Sq r
+processing, begins with
+.Dq sh
+and
+.Pq often used for the Nm lksh No binary
+this autodetection feature is compiled in.
+As a side effect, setting this flag turns off the
+.Ic braceexpand
+and
+.Ic utf8\-mode
+flags, which can be turned back on manually, and
+.Pq unless both are set in the same command
+.Ic sh
+mode.
+.It Fl o Ic sh
+Enable kludge
+.Pa /bin/sh
+compatibility mode (see
+.Sx SH mode
+below for details).
+Automatically enabled if the basename of the shell invocation, after
+.Sq \-
+and
+.Sq r
+processing, begins with
+.Dq sh
+and this autodetection feature is compiled in
+.Pq rather uncommon .
+As a side effect, setting this flag turns off the
+.Ic braceexpand
+flag, which can be turned back on manually, and
+.Ic posix
+mode (unless both are set in the same command).
+.It Fl o Ic vi
+Enable
+.Xr vi 1 Ns -like
+command-line editing (interactive shells only).
+See
+.Sx Vi editing mode
+for documentation and limitations.
+.It Fl o Ic vi\-esccomplete
+In vi command-line editing, do command and file name completion when Esc
+.Pq \*(ha[
+is entered in command mode.
+.It Fl o Ic vi\-tabcomplete
+In vi command-line editing, do command and file name completion when Tab
+.Pq \*(haI
+is entered in insert mode (default).
+.It Fl o Ic viraw
+No effect.
+In the original Korn shell, unless
+.Ic viraw
+was set, the vi command-line mode would let the
+.Xr tty 4
+driver do the work until Esc 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
+.Dq Li $\- .
+.Ic set Fl o
+with no option name will list all the options and whether each is on or off;
+.Ic set +o
+prints a command to restore the current option set, using the internal
+.Ic set Fl o Ic .reset
+construct, which is an implementation detail; these commands are transient
+.Pq only valid within the current shell session .
+.Pp
+Remaining arguments, if any, are positional parameters and are assigned, in
+order, to the positional parameters (i.e. $1, $2, etc.).
+If options end with
+.Dq Li \-\-
+and there are no remaining arguments, all positional parameters are cleared.
+For unknown historical reasons, a lone
+.Dq Li \-
+option is treated specially\*(EMit clears both the
+.Fl v
+and
+.Fl x
+options.
+If no options or arguments are given, the values of all parameters are printed
+.Pq suitably quoted .
+.Pp
+.It Xo
+.Ic setenv
+.Op Ar name Op Ar value
+.Xc
+.Pq Li dot.mkshrc No function
+Without arguments, display the names and values of all exported parameters.
+Otherwise, set
+.Ar name Ns 's
+export attribute, and its value to
+.Ar value Pq empty string if none given .
+.Pp
+.It Ic shift Op Ar number
+.Pq keeps assignments , special
+The positional parameters
+.Ar number Ns +1 ,
+.Ar number Ns +2 ,
+etc.
+.Pq Ar number No defaults to 1
+are renamed to 1, 2, etc.
+.Pp
+.It Ic sleep Ar seconds
+.Pq regular , needs Xr select 2
+Suspends execution for a minimum of the
+.Ar seconds
+(specified as positive decimal value with an optional fractional part).
+Signal delivery may continue execution earlier.
+.Pp
+.It Ic smores Op Ar
+.Pq Li dot.mkshrc No function
+Simple pager:
+.Aq Enter
+next;
+.So q Sc Ns + Ns Aq Enter
+quit
+.Pp
+.It Ic source Ar file Op Ar arg ...
+.Pq keeps assignments
+Like
+.Ic \&.
+.Pq Dq dot ,
+except that the current working directory is appended to the
+search path.
+.Pq GNU Nm bash No extension
+.Pp
+.It Ic suspend
+.Pq needs job control and Xr getsid 2
+Stops the shell as if it had received the suspend character from
+the terminal.
+.Pp
+It is not possible to suspend a login shell unless the parent process
+is a member of the same terminal session but is a member of a different
+process group.
+As a general rule, if the shell was started by another shell or via
+.Xr su 1 ,
+it can be suspended.
+.Pp
+.It Ic test Ar expression
+.It Ic \&[ Ar expression Ic \&]
+.Pq regular
+.Ic test
+evaluates the
+.Ar expression
+and exits with status code 0 if true, 1 if false,
+or greater than 1 if there was an error.
+It is often used as the condition command of
+.Ic if
+and
+.Ic while
+statements.
+All
+.Ar file
+expressions, except
+.Fl h
+and
+.Fl L ,
+follow symbolic links.
+.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 7
+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 p Ar file
+.Ar file
+is a named pipe
+.Pq Tn FIFO .
+.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 Fl v Ar name
+The shell parameter
+.Ar name
+is set.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option
+.Ar foo
+exists).
+The same can be achieved with [ \-o ?foo ] like in
+.At
+.Nm ksh93 .
+.Ar option
+can also be the short flag prefixed with either
+.Ql \-
+or
+.Ql +
+.Pq no logical negation ,
+for example
+.Dq Li \-x
+or
+.Dq Li +x
+instead of
+.Dq Li xtrace .
+.It Ar string No = Ar string
+Strings are equal.
+In double brackets, pattern matching
+.Pq R59+ using extglobs
+occurs if the right-hand string isn't quoted.
+.It Ar string No == Ar string
+Same as
+.Sq =
+.Pq deprecated .
+.It Ar string No != Ar string
+Strings are not equal.
+See
+.Sq =
+regarding pattern matching.
+.It Ar string No \*(Gt Ar string
+First string operand is greater than second string operand.
+.It Ar string No \*(Lt Ar string
+First string operand is less than second string operand.
+.It Ar 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
+.Px
+) if the number of arguments to
+.Ic test
+or inside the brackets
+.Ic \&[ ... \&]
+is less than five: if leading
+.Dq Li \&!
+arguments can be stripped such that only one to three arguments remain,
+then the lowered comparison is executed; (thanks to XSI) parentheses
+.Ic \e( ... \e)
+lower four- and three-argument forms to two- and one-argument forms,
+respectively; three-argument forms ultimately prefer binary operations,
+followed by negation and parenthesis lowering; two- and four-argument forms
+prefer negation followed by parenthesis; the one-argument form always implies
+.Fl n .
+To assume this is not necessarily portable.
+.Pp
+.Sy Note :
+A common mistake is to use
+.Dq Li if \&[ $foo = bar \&]
+which fails if parameter
+.Dq foo
+is empty or unset, if it has embedded spaces (i.e.\&
+.Ev IFS
+octets) or if it is a unary operator like
+.Dq Li \&!
+or
+.Dq Li \-n .
+Use tests like
+.Dq Li if \&[ x\&"$foo\&" = x"bar" \&]
+instead, or the double-bracket operator (see
+.Ic \&[[
+above):
+.Dq Li if \&[[ $foo = bar \&]]
+or, to avoid pattern matching,
+.Dq Li if \&[[ $foo = \&"$bar" \&]] ;
+the
+.Ic \&[[ ... \&]]
+construct is not only more secure to use but also often faster.
+.Pp
+.It Xo
+.Ic time
+.Op Fl p
+.Op Ar pipeline
+.Xc
+.Pq reserved word
+If a
+.Ar pipeline
+is given, the times used to execute the pipeline are reported.
+If no pipeline
+is given, then the user and system time used by the shell itself, and all the
+commands it has run since it was started, are reported.
+.Pp
+The times reported are the real time (elapsed time from start to finish),
+the user CPU time (time spent running in user mode), and the system CPU time
+(time spent running in kernel mode).
+.Pp
+Times are reported to standard error; the format of the output is:
+.Pp
+.Dl "0m0.03s real 0m0.02s user 0m0.01s system"
+.Pp
+If the
+.Fl p
+option is given (which is only permitted if
+.Ar pipeline
+is a simple command), the output is slightly longer:
+.Bd -literal -offset indent
+real 0.03
+user 0.02
+sys 0.01
+.Ed
+.Pp
+Simple redirections of standard error do not affect
+.Ic time Ns 's output :
+.Pp
+.Dl $ time sleep 1 2\*(Gtafile
+.Dl $ { time sleep 1; } 2\*(Gtafile
+.Pp
+Times for the first command do not go to
+.Dq afile ,
+but those of the second command do.
+.Pp
+.It Ic times
+.Pq keeps assignments , special
+Print the accumulated user and system times (see above) used both
+by the shell and by processes that the shell started which have exited.
+The format of the output is:
+.Bd -literal -offset indent
+0m0.01s 0m0.00s
+0m0.04s 0m0.02s
+.Ed
+.Pp
+.It Ic trap Ar n Op Ar signal ...
+.Pq keeps assignments , special
+If the first operand is a decimal unsigned integer, this resets all
+specified signals to the default action, i.e. is the same as calling
+.Ic trap
+with a dash
+.Pq Dq Li \-
+as
+.Ar handler ,
+followed by the arguments (interpreted as signals).
+.Pp
+.It Ic trap Op Ar handler signal ...
+.Pq keeps assignments , special
+Sets a trap handler that is to be executed when any of the specified
+.Ar signal Ns s
+are received.
+.Ar handler
+is either an empty string, indicating the signals are to be ignored, a dash
+.Pq Dq Li \- ,
+indicating that the default action is to be taken for the signals
+.Pq see Xr signal 3 ,
+or a string comprised of shell commands to be executed at the first opportunity
+(i.e. when the current command completes or before printing the next
+.Ev PS1
+prompt) after receipt of one of the signals.
+.Ar signal
+is the name, possibly prefixed with
+.Dq Li SIG ,
+of a signal
+.Po
+e.g.\&
+.Dv PIPE ,
+.Dv ALRM
+or
+.Dv SIGINT
+.Pc
+or the number of the signal (see the
+.Ic kill Fl l
+command above).
+.Pp
+There are two special signals:
+.Dv EXIT
+.Pq 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
+.Ic set Fl e
+or
+.Ic set Fl o Ic errexit
+option were set.
+.Dv EXIT
+handlers are executed in the environment of the last executed command.
+The original Korn shell's
+.Dv DEBUG
+trap and handling of
+.Dv ERR
+and
+.Dv EXIT
+in functions are not yet implemented.
+.Pp
+Note that, for non-interactive shells, the trap handler cannot be changed
+for signals that were ignored when the shell started.
+.Pp
+With no arguments, the current state of the traps that have been set since
+the shell started is shown as a series of
+.Ic trap
+commands.
+Note that the output of
+.Ic trap
+cannot be usefully captured or piped to another process (an artifact
+of the fact that traps are cleared when subprocesses are created).
+.Pp
+.It Ic true
+.Pq regular
+A command that exits with a zero status.
+.Pp
+.It Ic type Ar name ...
+.Pq built-in alias
+Reveal how
+.Ar name
+would be interpreted as command.
+.Pp
+.It Xo
+.Ic typeset
+.Op Fl +aglpnrtUux
+.Oo Fl L Ns Op Ar n
+.No \*(Ba Fl R Ns Op Ar n
+.No \*(Ba Fl Z Ns Op Ar n Oc
+.Op Fl i Ns Op Ar n
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.It Xo
+.Ic typeset
+.Fl f Op Fl tux
+.Op Ar name ...
+.Xc
+.Pq keeps assignments , decl-util
+Display or set attributes of shell parameters or functions.
+With no
+.Ar name
+arguments, parameter attributes are shown;
+if no options are used, the current attributes of all parameters are printed as
+.Ic typeset
+commands; if an option is given (or
+.Dq Li \-
+with no option letter), all parameters and their values with the specified
+attributes are printed; if options are introduced with
+.Ql +
+.Po
+or
+.Dq Li +
+alone
+.Pc ,
+only names are printed.
+.Pp
+If any
+.Ar name
+arguments are given, the attributes of the so named parameters are set
+.Pq Ic \&\-
+or cleared
+.Pq Ic \&+ ;
+inside a function, this will cause the parameters to be created (and set to
+.Dq
+if no value is given) in the local scope
+.Pq except if Fl g No is used .
+Values for parameters may optionally be specified.
+For
+.Ar name Ns \&[*] ,
+the change affects all elements of the array, and no value may be specified.
+.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 names are displayed.
+.Bl -tag -width Ds
+.It Fl a
+Indexed array attribute.
+.It Fl f
+Function mode.
+Display or set shell functions and their attributes,
+instead of shell parameters.
+.It Fl g
+.Dq global
+mode.
+Do not cause named parameters to be created in
+the local scope when called inside a function.
+.It Fl i Ns Op Ar n
+Integer attribute.
+.Ar n
+specifies the base to use when stringifying the integer
+(if not specified, the base given in the first assignment is used).
+Parameters with this attribute
+may be assigned arithmetic expressions for values.
+.It Fl L Ns Op Ar n
+Left justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of the parameter
+(or the width of its first assigned value) is used.
+Leading whitespace (and digit zeros, if used with the
+.Fl Z
+option) is stripped.
+If necessary, values are either truncated
+or padded with space to fit the field width.
+.It Fl l
+Lower case attribute.
+All upper case ASCII characters in values are converted to lower case.
+(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 parameters, instead of resorting to
+.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 the parameter
+(or the width of its first assigned value) is used.
+Trailing whitespace is stripped.
+If necessary, values are either stripped of leading characters
+or padded with space to fit the field width.
+.It Fl r
+Read-only attribute.
+Parameters with this attribute may not be assigned to or unset.
+Once this attribute is set, it cannot be turned off.
+.It Fl t
+Tag attribute.
+This attribute has no meaning to the shell for parameters
+and is provided for application use.
+.Pp
+For functions,
+.Fl t
+is the trace attribute.
+When functions with the trace attribute are executed, the
+.Fl o Ic xtrace
+.Pq Fl x
+shell option is temporarily turned on.
+.It Fl U
+Unsigned integer attribute.
+Integers are printed as unsigned values (combined with the
+.Fl i
+option).
+.It Fl u
+Upper case attribute.
+All lower case ASCII 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
+.Fl U
+above.)
+.Pp
+For functions,
+.Fl u
+is the undefined attribute, used with
+.Ev FPATH .
+See
+.Sx Functions
+above for the implications of this.
+.It Fl x
+Export attribute.
+Parameters are placed in the environment of any executed commands.
+Functions cannot be exported for security reasons
+.Pq Dq shellshock .
+.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 is padded, not the base.
+.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 aBCcdefHilMmnOPpqrSsTtVvwx
+.Op Ar value
+.Xc
+.Pq regular
+Display or set process limits.
+If no options are used, the file size limit
+.Pq Fl f
+is assumed.
+.Ar value ,
+if specified, may be either an arithmetic expression or the word
+.Dq Li unlimited .
+The limits affect the shell and any processes created by the shell after a
+limit is imposed.
+Note that systems may not allow some limits to be increased
+once they are set.
+Also note that the types of limits available are system
+dependent\*(EMsome systems have only the
+.Fl f
+limit, or not even that, or can set only the soft limits, etc.
+.Bl -tag -width 5n
+.It Fl a
+Display all limits (soft limits unless
+.Fl H
+is used).
+.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
+Limit the size of the data area to
+.Ar n
+kibibytes.
+.It Fl e Ar n
+Set the maximum niceness to
+.Ar n .
+.It Fl f Ar n
+Impose a size limit of
+.Ar n
+blocks on files written by the shell and its child processes
+.Pq any size may be read .
+.It Fl H
+Set the hard limit only (the default is to set both hard and soft limits).
+With
+.Fl a ,
+display all hard limits.
+.It Fl i Ar n
+Set the number of pending signals to
+.Ar n .
+.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 .
+.Pp
+This option mostly matches
+.At
+.Nm ksh93 Ns 's
+.Fl T ;
+.br
+on
+.Tn AIX ,
+see
+.Fl r
+as used by its
+.Nm ksh
+though.
+.It Fl p Ar n
+Impose a limit of
+.Ar n
+processes that can be run by the user (uid) 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
+.Pq Cm AIX
+Limit the number of threads per process to
+.Ar n .
+.br
+.Pq Cm Linux
+Set the maximum real-time priority to
+.Ar n .
+.It Fl S
+Set the soft limit only (the default is to set both hard and soft limits).
+With
+.Fl a ,
+display soft limits (default).
+.It Fl s Ar n
+Limit the size of the stack area to
+.Ar n
+kibibytes.
+.It Fl T Ar n
+Impose a time limit of
+.Ar n
+real seconds
+.Pq Dq humantime
+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
+Limit the amount of swap space used to at most
+.Ar n
+kibibytes.
+.It Fl x Ar n
+Set the maximum number of file locks to
+.Ar n .
+.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
+.Pq regular
+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 Li 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 Li 007 .
+.Pp
+.It Xo
+.Ic unalias
+.Op Fl adt
+.Op Ar name ...
+.Xc
+.Pq regular
+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
+.Pq keeps assignments , special
+Unset the named parameters
+.Pq Fl v , No the default
+or functions
+.Pq Fl f .
+With
+.Ar parameter Ns \&[*] ,
+attributes are retained, only values are unset.
+The exit status is non-zero if any of the parameters are read-only,
+zero otherwise (not portable).
+.Pp
+.It Ic wait Op Ar job ...
+.Pq regular
+Wait for the specified job(s) to finish.
+The exit status of
+.Ic wait
+is that of the last specified job; if the last job is killed by a signal, the
+exit status is 128 + the signal number (see
+.Ic kill Fl l Ar exit-status
+above); if the last specified job cannot be found (because it never existed
+or had already finished), the exit status is 127.
+See
+.Sx Job control
+below for the format of
+.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
+.Pq regular
+Without the
+.Fl v
+option, it is the same as
+.Ic command Fl v ,
+except aliases are printed as their definition only.
+With the
+.Fl v
+option, it is exactly identical to
+.Ic command Fl V .
+In either case, with the
+.Fl p
+option the search is restricted to the (current)
+.Ev PATH .
+.Pp
+.It Xo Ic which
+.Op Fl a
+.Op Ar name ...
+.Xc
+.Pq Li dot.mkshrc No function
+Without
+.Fl a ,
+behaves like
+.Ic whence Fl p
+(does a
+.Ev PATH
+search for each
+.Ar name
+printing the resulting pathname if found); with
+.Fl a ,
+matches in all
+.Ev PATH
+components are printed, i.e. the search is not stopped after a match.
+If no
+.Ar name
+was matched, the exit status is 2; if every name was matched, it is zero,
+otherwise it is 1.
+No diagnostics are produced on failure to match.
+.El
+.Ss Job control
+Job control refers to the shell's ability to monitor and control jobs which
+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 Fl m
+or
+.Ic set Fl o Ic 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 commands
+.Ic fg
+and
+.Ic bg .
+.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 Li \&[...] ,
+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 Ql % .
+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\*(EMthe 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 Fl l
+for a list of signal descriptions.
+The
+.Dq Li 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 Terminal state
+The state of the controlling terminal can be modified by a command
+executed in the foreground, whether or not job control is enabled, but
+the modified terminal state is only kept past the job's lifetime and used
+for later command invocations if the command exits successfully (i.e.\&
+with an exit status of 0).
+When such a job is momentarily stopped or restarted, the terminal state
+is saved and restored, respectively, but it will not be kept afterwards.
+In interactive mode, when line editing is enabled, the terminal state is
+saved before being reconfigured by the shell for the line editor, then
+restored before running a command.
+.Ss POSIX mode
+Entering
+.Ic set Fl o Ic posix
+mode will cause
+.Nm
+to behave even more
+.Tn POSIX
+compliant in places where the defaults or opinions differ.
+Note that
+.Nm mksh
+will still operate with unsigned 32-bit arithmetic; use
+.Nm lksh
+if arithmetic on the host
+.Vt long
+data type, complete with ISO C Undefined Behaviour, is required;
+refer to the
+.Xr lksh 1
+manual page for details.
+Most other historic,
+.At
+.Nm ksh Ns -compatible
+or opinionated differences can be disabled by using this mode; these are:
+.Bl -bullet
+.It
+The incompatible GNU
+.Nm bash
+I/O redirection
+.Ic &\*(Gt Ns Ar file
+is not supported.
+.It
+File descriptors created by I/O redirections are inherited by
+child processes.
+.It
+Numbers with a leading digit zero are interpreted as octal.
+.It
+The
+.Nm echo
+builtin does not interpret backslashes and only supports the exact option
+.Fl n .
+.It
+Alias expansion with a trailing space only reruns on command words.
+.It
+Tilde expansion follows POSIX instead of Korn shell rules.
+.It
+The exit status of
+.Ic fg
+is always 0.
+.It
+.Ic kill
+.Fl l
+only lists signal names, all in one line.
+.It
+.Ic getopts
+does not accept options with a leading
+.Ql + .
+.It
+.Ic exec
+skips builtins, functions and other commands and uses a
+.Ev PATH
+search to determine the utility to execute.
+.El
+.Ss SH mode
+Compatibility mode; intended for use with legacy scripts that
+cannot easily be fixed; the changes are as follows:
+.Bl -bullet
+.It
+The incompatible GNU
+.Nm bash
+I/O redirection
+.Ic &\*(Gt Ns Ar file
+is not supported.
+.It
+File descriptors created by I/O redirections are inherited by
+child processes.
+.It
+The
+.Nm echo
+builtin does not interpret backslashes and only supports the exact option
+.Fl n ,
+unless built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
+.It
+The substitution operations
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf # Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf ## Ar pat No } ,
+.Sm on
+.Xc
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf % Ar pat No } ,
+.Sm on
+.Xc
+and
+.Sm off
+.Xo
+.Pf ${ Ar x
+.Pf %% Ar pat No }
+.Sm on
+.Xc
+wrongly do not require a parenthesis to be escaped and do not parse extglobs.
+.It
+The getopt construct from
+.Xr lksh 1
+passes through the errorlevel.
+.It
+.Nm sh
+.Fl c
+eats a leading
+.Fl \-
+if built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
+.El
+.Ss Interactive input line editing
+The shell supports three modes of reading command lines from a
+.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 user's
+.Xr tty 4
+characters (e.g.\&
+.Dv ERASE )
+are bound to
+reasonable substitutes and override the default bindings;
+their customary values are shown in parentheses below.
+The default bindings were chosen to resemble corresponding
+Emacs key bindings:
+.Bl -tag -width Ds
+.It Xo abort:
+.No INTR Pq \*(haC ,
+.No \*(haG
+.Xc
+Abort the current command, save it to the history, 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 , PC-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 Ql _
+and dollar sign
+.Pq Ql $
+characters.
+.It beginning\-of\-history: \*(ha[\*(Lt
+Moves to the beginning of the history.
+.It beginning\-of\-line: \*(haA, ANSI-Home, PC-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 ASCII 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 last line of the prompt string 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 Pq \*(haH ,
+.No \*(ha? , \*(haH
+.Xc
+Deletes
+.Ar n
+characters before the cursor.
+.It Xo delete\-char\-forward:
+.Op Ar n
+.No ANSI-Del , PC-Del
+.Xc
+Deletes
+.Ar n
+characters after the cursor.
+.It Xo delete\-word\-backward:
+.Op Ar n
+.No Pfx1+ERASE Pq \*(ha[\*(haH ,
+.No WERASE Pq \*(haW ,
+.No \*(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 , PC-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, PC-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 canonicalisation.
+.It Xo eot\-or\-delete:
+.Op Ar n
+.No EOF Pq \*(haD
+.Xc
+If alone on a line, same as
+.Ic eot ,
+otherwise,
+.Ic delete\-char\-forward .
+.It error: (not bound)
+Error (ring the bell).
+.It evaluate\-region: \*(ha[\*(haE
+Evaluates the text between the mark and the cursor position
+.Pq the entire line if no mark is set
+as function substitution (if it cannot be parsed, the editing state is
+unchanged and the bell is rung to signal an error); $? is updated accordingly.
+.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 , PC-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 Xo kill\-line:
+.No KILL Pq \*(haU
+.Xc
+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 Xo no\-op:
+.No QUIT Pq \*(ha\e
+.Xc
+This does nothing.
+.It prefix\-1: \*(ha[
+Introduces a 2-character command sequence.
+.It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O
+Introduces a multi-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 quote\-region: \*(ha[Q
+Escapes the text between the mark and the cursor position
+.Pq the entire line if no mark is set
+into a shell command argument.
+.It redraw: \*(haL
+Reprints the last line of the prompt string and the current input line
+on a new line.
+.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, PC-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, PC-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 , PC-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
+.Pp
+The tab completion escapes characters the same way as the following code:
+.Bd -literal
+print \-nr \-\- "${x@/[\e"\-\e$\e&\-*:\-?[\e\e\e`\e{\-\e}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
+.Ed
+.Ss Vi editing mode
+.Em Note:
+The vi command-line editing mode has not yet been brought up to the
+same quality and feature set as the emacs mode.
+It is 8-bit clean but specifically does not support UTF-8 or MBCS.
+.Pp
+The vi command-line editor in
+.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 Fl o Ic 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 Li 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 Ql #
+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)\*(EMthe big-word is replaced
+with the resulting words.
+If the current big-word is the first on the line
+or follows one of the characters
+.Ql \&; ,
+.Ql \*(Ba ,
+.Ql & ,
+.Ql \&(
+or
+.Ql \&)
+and does not contain a slash
+.Pq Ql / ,
+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 Fl 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
+.Li _ Ns 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.
+.It Ar ANSI-CurUp , PC-PgUp
+Take the characters from the beginning of the line to the current
+cursor position as search string and do a history search, backwards,
+for lines beginning with this string; keep the cursor position.
+This works only in insert mode and keeps it enabled.
+.It Ar ANSI-CurDown , PC-PgDn
+Take the characters from the beginning of the line to the current
+cursor position as search string and do a history search, forwards,
+for lines beginning with this string; keep the cursor position.
+This works only in insert mode and keeps it enabled.
+.El
+.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 PC Home, End, Del and cursor keys
+They move as expected, both in insert and command mode.
+.It Ar intr No and Ar quit
+The interrupt and quit terminal characters cause the current line to be
+removed to the history 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 (e.g. 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
+Privileged shells' profile (sugid); 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 lksh 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
+The FAQ at
+.Pa http://www.mirbsd.org/mksh\-faq.htm
+or in the
+.Pa mksh.faq
+file.
+.Pp
+.Pa http://www.mirbsd.org/ksh\-chan.htm
+.Rs
+.%A Morris Bolsky
+.%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 "3rd Edition"
+.%D 2003
+.%I "Sams"
+.%P "xiii\ +\ 437 pages"
+.%O "ISBN 978\-0\-672\-32490\-1 (0\-672\-32490\-3)"
+.Re
+.Rs
+.%A "IEEE Inc."
+.%T "\\*(tNIEEE\\*(sP Standard for Information Technology\*(EMPortable 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
+.An -nosplit
+.Nm "The MirBSD Korn Shell"
+is developed by
+.An mirabilos Aq Mt m@mirbsd.org
+as part of The MirOS Project.
+This shell is based on the public domain 7th edition Bourne shell clone by
+.An Charles Forsyth ,
+who kindly agreed to, in countries where the Public Domain status of the work
+may not be valid, grant a copyright licence to the general public to deal in
+the work without restriction and permission to sublicence derivatives under
+the terms of any (OSI approved) Open Source licence,
+and parts of the BRL shell by
+.An Doug A. Gwyn ,
+.An Doug Kingston ,
+.An Ron Natalie ,
+.An Arnold Robbins ,
+.An Lou Salkind
+and others.
+The first release of
+.Nm pdksh
+was created by
+.An Eric Gisin ,
+and it was subsequently maintained by
+.An John R. MacMillan ,
+.An Simon J. Gerraty
+and
+.An Michael Rendell .
+The effort of several projects, such as Debian and OpenBSD, and other
+contributors including our users, to improve the shell is appreciated.
+See the documentation, website and source code (CVS) for details.
+.Pp
+.Nm mksh\-os2
+is developed by
+.An KO Myung-Hun Aq Mt komh@chollian.net .
+.Pp
+.Nm mksh\-w32
+is developed by
+.An Michael Langguth Aq Mt lan@scalaris.com .
+.Pp
+.Nm mksh Ns / Ns Tn z/OS
+is contributed by
+.An Daniel Richard G. Aq Mt skunk@iSKUNK.ORG .
+.Pp
+The BSD daemon is Copyright \(co Marshall Kirk McKusick.
+The complete legalese is at:
+.Pa http://www.mirbsd.org/TaC\-mksh.txt
+.\"
+.\" This boils down to: feel free to use mksh.ico as application icon
+.\" or shortcut for mksh or mksh/Win32 or OS/2; distro patches are ok
+.\" (but we request they amend $KSH_VERSION when modifying mksh).
+.\" Authors are Marshall Kirk McKusick (UCB), Rick Collette (ekkoBSD),
+.\" mirabilos, Benny Siegert (MirBSD), Michael Langguth (mksh/Win32),
+.\" KO Myung-Hun (mksh for OS/2).
+.\"
+.\" As far as MirBSD is concerned, the files themselves are free
+.\" to modification and distribution under BSD/MirOS Licence, the
+.\" restriction on use stems only from trademark law's requirement
+.\" to protect it or lose it, which McKusick almost did.
+.\"
+.Sh CAVEATS
+.Nm mksh
+provides a consistent, clear interface normally.
+This may deviate from POSIX in historic or opinionated places.
+.Ic set Fl o Ic posix
+(see
+.Sx POSIX mode
+for details)
+will make the shell more conformant, but mind the FAQ (see
+.Sx SEE ALSO ) ,
+especially regarding locales.
+.Nm mksh
+.Pq but not Nm lksh
+provides a consistent 32-bit integer arithmetic implementation, both
+signed and unsigned, with sign of the result of a remainder operation
+and wraparound defined, even (defying POSIX) on 36-bit and 64-bit systems.
+.Pp
+.Nm mksh
+currently uses OPTU-16 internally, which is the same as UTF-8 and CESU-8
+with 0000..FFFD being valid codepoints; raw octets are mapped into the
+PUA range EF80..EFFF, which is assigned by CSUR for this purpose.
+.Sh BUGS
+Suspending (using \*(haZ) pipelines like the one below will only suspend
+the currently running part of the pipeline; in this example,
+.Dq Li fubar
+is immediately printed on suspension (but not later after an
+.Ic fg ) .
+.Bd -literal -offset indent
+$ /bin/sleep 666 && echo fubar
+.Ed
+.Pp
+The truncation process involved when changing
+.Ev HISTFILE
+does not free old history entries (leaks memory) and leaks
+old entries into the new history if their line numbers are
+not overwritten by same-number entries from the persistent
+history file; truncating the on-disc file to
+.Ev HISTSIZE
+lines has always been broken and prone to history file corruption
+when multiple shells are accessing the file; the rollover process
+for the in-memory portion of the history is slow, should use
+.Xr memmove 3 .
+.Pp
+This document attempts to describe
+.Nm mksh\ R59b
+and up,
+.\" with vendor patches from insert-your-name-here,
+compiled without any options impacting functionality, such as
+.Dv MKSH_SMALL ,
+when not called as
+.Pa /bin/sh
+which, on some systems only, enables
+.Ic set Fl o Ic posix
+or
+.Ic set Fl o Ic sh
+automatically (whose behaviour differs across targets),
+for an operating environment supporting all of its advanced needs.
+.Pp
+Please report bugs in
+.Nm
+to the public development mailing list at
+.Aq Mt miros\-mksh@mirbsd.org
+(please note the EU-DSGVO/GDPR notice on
+.Pa http://www.mirbsd.org/rss.htm#lists
+and in the SMTP banner!) or in the
+.Li \&#\&!/bin/mksh
+.Pq or Li \&#ksh
+IRC channel at
+.Pa irc.freenode.net
+.Pq Port 6697 SSL, 6667 unencrypted ,
+or at:
+.Pa https://launchpad.net/mksh
diff --git a/shells/mksh/files/mksh.faq b/shells/mksh/files/mksh.faq
new file mode 100644
index 00000000000..83e019160f5
--- /dev/null
+++ b/shells/mksh/files/mksh.faq
@@ -0,0 +1,620 @@
+RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.7 2020/04/25 12:09:55 tg Exp $
+ToC: spelling
+Title: How do you spell <tt>mksh</tt>? How do you pronounce it?
+
+<p>This <a href="@@RELPATH@@mksh.htm">shell</a> is spelt either
+ “<tt>mksh</tt>” (with, even at the beginning of a sentence, <a
+ href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Capital_letters#Items_that_require_initial_lower_case">an
+ initial lowercase letter</a>; this is important) or “MirBSD Korn Shell”,
+ possibly with “the”.</p>
+<p>I usually pronounce it as “<span xml:lang="de-DE-1901">em-ka-es-ha</span>”,
+ that is, the letters individually in my native German, or say “MirBSD Korn
+ Shell”, although it is manageable, mostly for Slavic speakers, to actually
+ say “mksh” as if it were a word âş</p>
+<p>Oh… I’ve run into this one, didn’t I? “MirBSD” is pronounced “<span
+ xml:lang="de-DE-1901">Mir-Be-Es-De</span>” germanically, for anglophones
+ “Mir-beas’tie” is fine.</p>
+----
+ToC: sowhatismksh
+Title: I’m a $OS (<i>Android, OS/2, …</i>) user, so what’s mksh?
+
+<p>mksh is a so-called (Unix) “shell” or “command interpreter”, similar to
+ <tt>COMMAND.COM</tt>, <tt>CMD.EXE</tt> or PowerShell on other operating
+ systems you might know. Basically, it runs in a terminal (“console” or
+ “DOS box”) window, taking user input and running that as commands. It’s
+ also used to write so-called (shell) “script”s, short programs made by
+ putting several of those commands into a “batch file”.</p>
+<p>On Android, mksh is used as the system shell — basically, the one
+ running commands at system startup, in the background, and on user
+ behalf (but never of its own). Any privilege pop-ups you might <a
+ href="https://forum.xda-developers.com/showthread.php?t=1963976">be
+ encountering</a> are therefore <a
+ href="https://forum.xda-developers.com/showpost.php?p=33550523&amp;postcount=1553">not
+ caused by mksh</a> but by some other code invoking mksh to do something
+ on its behalf.</p>
+----
+ToC: os2
+Title: I’m an OS/2 user, what else do I need to know?
+
+<p>Unlike the native command prompt, the current working directory is,
+ for security reasons common on Unix systems which the shell is designed
+ for, not in the search path at all; if you really need this, run the
+ command <tt>PATH=.$PATHSEP$PATH</tt> or add that to a suitable
+ initialisation file (<tt>~/.mkshrc</tt>).</p>
+<p>There are two different newline modes for mksh-os2: standard (Unix)
+ mode, in which only LF (0A hex) is supported as line separator, and
+ “textmode”, which also accepts ASCII newlines (CR+LF), like most other
+ tools on OS/2, but creating an incompatibility with standard mksh. If
+ you compiled mksh from source, you will get the standard Unix mode unless
+ <tt>-T</tt> is added during compilation; however, you will most likely
+ have gotten this shell through komh’s port on Hobbes, or from his OS/2
+ Factory on eComStation Korea, which uses “textmode”, though. Most OS/2
+ users will want to use “textmode” unless they need absolute compatibility
+ with Unix mksh and other Unix shells and tools.</p>
+----
+ToC: kornshell
+Title: How does this relate to ksh or the Korn Shell?
+
+<p>The Korn Shell (AT&amp;T ksh) was authored by David Korn; two major
+ flavours exist (ksh88 and ksh93), the latter having been maintained
+ until 2012 (last formal release) and 2014 (last beta snapshot, buggy).
+ A ksh86 did exist.</p>
+<p>There’s now <tt>ksh2020</tt>, a project having restarted development
+ around November 2017 forking the last <tt>ksh93 v-</tt> (beta) snapshot
+ and continuing to develop it, presented at FOSDEM.</p>
+<p>AT&amp;T ksh88 is “the (original) Korn Shell”. Other implementations,
+ of varying quality (MKS Toolkit’s MKS ksh being named as an example of
+ the lower end, MirBSD’s mksh at the upper end). They are all <em>not</em>
+ “Korn Shell” or “ksh”. However, mksh got blessed by David Korn, as long
+ as it cannot be confused with the original Korn Shell.</p>
+<p>The POSIX shell standard, while lacking most Korn Shell features, was
+ largely based on AT&amp;T ksh88, with some from the Bourne shell.</p>
+<p>mksh is the currently active development of what started as the Public
+ Domain Bourne Shell in the mid-1980s with ksh88-compatibl-ish extensions
+ having been added later, making the Public Domain Korn Shell (pdksh),
+ which, while never officially blessed, was the only way for most to get
+ a Korn Shell-like command interpreter for AT&amp;T’s was proprietary,
+ closed-source code for a very long time. pdksh’s development ended in
+ 1999, with some projects like Debian and NetBSD® creating small bug fixes
+ (which often introduced new bugs) as part of maintenance. Around 2003,
+ OpenBSD started cleaning up their shipped version of pdksh, removing old
+ and compatibility code and modernising it. In 2002, development of what
+ is now mksh started as the system shell of MirBSD, which took over almost
+ all of OpenBSD’s cleanup, adding compatibility to other operating systems
+ back on top of it, and after 2004, independent, massive development of
+ bugfixes including a complete reorganisation of the way the parser works,
+ and of new features both independent and compatible with other shells
+ (ksh93, GNU bash, zsh, BSD csh) started and was followed by working with
+ the group behind POSIX to fix issues both in the standard and in mksh.
+ mksh became the system shell in several other operating systems and Linux
+ distributions and Android and thus is likely the Korn shell, if not Unix
+ shell, flavour with the largest user base. It has replaced pdksh in all
+ contemporary systems except QNX, NetBSD® and OpenBSD (who continue to
+ maintain their variant on “low flame”).</p>
+<p>dtksh is the “Desktop Korn Shell”, a build of AT&amp;T ksh93 with some
+ additional built-in utilities for graphics programming (windows, menu
+ bars, dialogue boxes, etc.) utilising Motif bindings.</p>
+<p>MKS ksh is a proprietary reimplemention aiming for, but not quite
+ getting close to, ksh88 compatibility.</p>
+<p>SKsh is an AmigaOS-specific Korn Shell-lookalike by Steve Koren.</p>
+<p>The <a href="@@RELPATH@@ksh-chan.htm">Homepage of the <tt>#ksh</tt>
+ channel on Freenode IRC</a> contains more information about the Korn
+ Shell in general and its flavours.</p>
+----
+ToC: packaging
+Title: How should I package mksh? (common cases)
+
+<p>Export a few environment variables, namely <tt>CC</tt> (the C compiler),
+ <tt>CPPFLAGS</tt> (all C præprocessor definitions), <tt>CFLAGS</tt> (only
+ compiler flags, <em>no</em> <tt>-Dfoo</tt> or anything!), <tt>LDFLAGS</tt>
+ (for anything to pass to the C compiler while linking) and <tt>LIBS</tt>
+ (appended to the linking command line after everything else. You might
+ wish to <tt>export LDSTATIC=-static</tt> for a static build as well.</p>
+<p>When cross-compiling, <tt>CC</tt> is the <em>cross</em> compiler (mksh
+ currently does not require a compiler targetting the build system), but
+ you <em>must</em> also export <tt>TARGET_OS</tt> to whatever system you
+ are compiling for, e.g. “Linux”. For most operating systems, that’s just
+ the uname(1) output. Some very rare systems also need <tt>TARGET_OSREV</tt>;
+ consult the source code of <tt>Build.sh</tt> for details.</p>
+<p>Create two subdirectories, say <tt>build-mksh</tt> and <tt>build-lksh</tt>.
+ In each of them, start a compilation by issuing <tt>sh ../Build.sh -r</tt>
+ followed by running the testsuite<a href="#packaging-fn1">Âą</a> via
+ <tt>./test.sh</tt>. For lksh(1) add <tt>-DMKSH_BINSHPOSIX</tt> to
+ <tt>CPPFLAGS</tt> and use <tt>sh ../Build.sh -r -L</tt> to compile.</p>
+<p>See <a href="#testsuite-fails">below</a> if the testsuite fails.</p>
+<p>Install <tt>build-mksh/mksh</tt> as <tt>/bin/mksh</tt> (or similar),
+ <tt>build-lksh/lksh</tt> as <tt>/bin/lksh</tt> with a symlink(7) to it
+ from <tt>/bin/sh</tt> (if desred), and <tt>mksh.1</tt> and <tt>lksh.1</tt>
+ as manpages (mdoc macropackage required). Install <tt>dot.mkshrc</tt>
+ either as <tt>/etc/skel/.mkshrc</tt> (meaning your users will have to
+ manually resynchronise their home directories’ copies after every package
+ upgrade) or as <tt>/etc/mkshrc</tt>, in which case you install a <a
+ href="https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=alioth/mksh.git;a=blob;f=debian/.mkshrc;hb=HEAD">redirection
+ script like Debian’s</a> into <tt>/etc/skel/.mkshrc</tt>. You may need a <a
+ href="@@RELPATH@@TaC-mksh.txt">summary of the licence information</a>.</p>
+<p>At runtime, the presence of <tt>/bin/ed</tt> as default history editor
+ is recommended, as well as a manpage formatter; you can also install
+ preformatted manpages from <tt>build-*ksh/*ksh.cat1</tt> if nroff(1) (or
+ <tt>$NROFF</tt>) is available at build time by removing the <tt>-r</tt>
+ flag from either <tt>Build.sh</tt> invocation.</p>
+<p>Some shell features require the ability to create temporary files and
+ FIFOS (cf. mkfifo(2))/pipes at runtime. Set <tt>TMPDIR</tt> to a suitable
+ location if <tt>/tmp</tt> isn’t it; if this is known ahead of time, you
+ can add <tt>-DMKSH_DEFAULT_TMPDIR=\"/path/to/tmp\"</tt> to CPPFLAGS. We
+ currently are unable to determine one on Android because its bionic libc
+ does not expose any method suitable to do so in the generic case.</p>
+<p id="packaging-fn1">â‘  To run the testsuite, ed(1) must be available as
+ <tt>/bin/ed</tt>, and perl(1) is needed. When cross-compiling, the version
+ of the first <tt>ed</tt> binary on the <tt>PATH</tt> <em>must</em> be the
+ same as that in the target system on which the tests are to be run, in
+ order to be able to detect which flavour of ed to adjust the tests for.
+ Busybox ed is broken beyond repair, and all three ed-related tests will
+ always fail with it.</p>
+----
+ToC: mkshrc
+Title: How does mksh load configuration files?
+
+<p>The shell loads first <tt>/etc/profile</tt> then <tt>~/.profile</tt>
+ if called as login shell or with the <tt>-l</tt> flag, then loads the file
+ <tt>$ENV</tt> points to (defaulting to <tt>~/.mkshrc</tt>) for interactive
+ shells (that includes login shells).</p>
+<p>Distributors should take care to either install the <tt>dot.mkshrc</tt>
+ example provided into <tt>/etc/skel/.mkshrc</tt> (so that it’s available
+ for newly created user accounts) and ensure it can propagate to existing
+ accounts or, if upgrading these is difficult, install the shipped file
+ as, for example, <tt>/etc/mkshrc</tt> and install a skeleton file, such
+ as the one in Debian, that sources the file in <tt>/etc</tt>.</p>
+<p>It’s vital that users can change the configuration, so do not force a
+ root-provided config file onto them; the shipped file, after all, is just
+ an example.</p>
+<p>If you need central user and configuration management and cannot use
+ something that installs skeleton files upon home directory creation
+ (like pam_mkhomedir), you can <tt>export ENV</tt> in <tt>/etc/profile</tt>
+ to a file (say <tt>/etc/shellrc</tt>) that sources the per-shell file.
+ Users can, this way, still override it by setting a different <tt>$ENV</tt>
+ in their <tt>~/.profile</tt>.</p>
+----
+ToC: testsuite-fails
+Title: The testsuite fails!
+
+<p>The mksh testsuite has uncovered numerous bugs in operating systems
+ (kernels, libraries), compilers and toolchains. It is likely that you
+ just ran into one. If you’re using LTO (the <tt>Build.sh</tt> option
+ <tt>-c lto</tt>) try to disable it first — especially GCC is a repeat
+ offender breaking LTO and its antecessor <tt>-fwhole-program --combine</tt>
+ and tends to do wrong code generation quite a bit. Otherwise, try
+ lowering the optimisation levels, bisecting, etc.</p>
+----
+ToC: selinux-androidiocy
+Title: I forbid stat(2) in my SELinux policy, and some things do not work!
+
+Don’t break Unix. Read up on the GIGO principle. Duh.
+----
+ToC: makefile
+Title: Why doesn’t this use a Makefile to build?
+
+<p>Not all supported target operating environments have a make utility
+ available, and shell was required for “mirtoconf” (like autoconf)
+ already anyway, so it was chosen to run the make part as well.</p>
+<p>You can, however, add the <tt>-M</tt> flag to your <tt>Build.sh</tt>
+ invocations to let it produce a <tt>Makefrag.inc</tt> file <em>tailored
+ for this specific build</em> which you can then include in a Makefile,
+ such as with the BSD make(1) “.include” command or <a
+ href="https://www.gnu.org/software/make/manual/make.html#Include">GNU
+ make</a> equivalent. It even contains, for the user to start out with,
+ a commented-out example of how to do that in the most basic manner.</p>
+----
+ToC: oldbsd
+Title: Why do other BSDs and QNX still use pdksh instead of mksh?
+
+<p>Some systems are resistent to change, mostly due to bikeshedding
+ (some people would, for example, rather see all shells banned to
+ ports/pkgsrc®) and hysterial raisins (historical reasons â»). Most
+ BSDs have mksh packages available, and it works on all of them and
+ QNX just fine.</p>
+<p>In fact, on all of these systems, you can replace their 1999-era
+ <tt>/bin/ksh</tt> (which is a pdksh) with mksh. On at least NetBSD®
+ 1.6 and up (not 1.5) and OpenBSD, even <tt>/bin/sh</tt> is fair game.</p>
+<p>MidnightBSD notably has adopted mksh as system shell (thanks laffer1).</p>
+----
+ToC: openbsd
+Title: Why is there no mksh in OpenBSD’s ports tree?
+
+OpenBSD don’t like people who fork off their project at all; heck,
+they don’t even like the people they themselves forked off (NetBSD®).
+Several people tried over the years to get one committed, but nobody
+dared so as to not lose their commit bit. If you try, succeed, and
+survive Theo, however, kudos to you! See also <a href="#oldbsd">the
+“other BSDs” FAQ entry</a>.
+----
+ToC: book
+Title: I’d like an introduction.
+
+Unfortunately, nobody has written a book about mksh yet, although
+other shells have received (sometimes decent) attention from authors
+and publishers. This FAQ lists a subset of things packagers and
+generic people ask, and the mksh(1) manpage is more of a reference,
+so you are probably best off starting with a shell-agnostic, POSIX
+or ksh88 reference such as the first edition (the second one deals
+with ksh93 which differs far more from mksh than ksh88, as ancient
+as it is, does) of the O’Reilly book (⚠ disclaimer: only an example,
+not a recommendation) and going forward by reading scripts (the
+“shellsnippets” repository referenced in the <tt>#ksh</tt> channel
+homepage (see the top of this document) has many examples) and
+trying to understand them and the mksh specifics from the manpage.
+----
+ToC: ps1conv
+Title: My prompt from &lt;<i>some other shell</i>&gt; does not work!
+
+<a href="#contact">Contact</a> us on the mailing list or on IRC,
+we’ll convert it for you. Also have a look at the PS1 section in
+the mksh(1) manpage (search for “otherwise unused char”, e.g. with
+<tt>/</tt> in less(1), to spot it quickly).
+----
+ToC: ps1weird
+Title: My prompt is weird!
+
+<p>There are several reasons why your <tt>PS1</tt> might be not
+ what you’d expect:</p><ul>
+<li><tt>$PS1</tt> is <tt>export</tt>ed. <strong>Do not export PS1!</strong>
+ (This was agreed upon as suggestion in a discussion between bash, zsh and
+ Korn shell developers.) The feature set of different shells vastly differs
+ and each shell should use its default PS1 or from its startup files.</li>
+<li><tt>$ENV</tt> <a href="#env">is set and/or <tt>export</tt>ed</a>.</li>
+<li>Your prompt is just “<tt># </tt>”: you’re entering a root shell, and
+ <tt>$PS1</tt> does not contain the â€#’ character, in which case the shell
+ forces this prompt, making extra privileges obvious.</li>
+<li>Your prompt is just “<tt>$ </tt>”: perhaps your system administrator
+ did not install the shipped <tt>dot.mkshrc</tt> file, or you did not copy
+ <tt>/etc/skel/.mkshrc</tt> into your home directory (perhaps it was created
+ before <tt>mksh</tt> was installed?). Without another idea for a fix, get <a
+ href="http://www.mirbsd.org/cvs.cgi/~checkout~/src/bin/mksh/dot.mkshrc?rev=HEAD;content-type=text%2Fplain">this
+ file</a> and store it as <tt>~/.mkshrc</tt> then run <tt>mksh</tt>; this
+ will at the very least install our sample (“user@host:path $ ”) prompt.</li>
+<li>Your prompt contains things like “\u” or “\w”: it is for another shell
+ and <a href="#ps1conv">needs converting</a>.</li>
+<li>Your prompt contains colours, and when the command line is long the
+ cursor position or screen contents, especially using the history, is off:
+ terminal escapes must be escaped from the shell; check the PS1 section in
+ the manpage: search for “otherwise unused char” (see above).</li>
+<li>If the prompt doesn’t leave enough space on the right, the shell inserts
+ a line break after it when rendering.</li>
+</ul>
+----
+ToC: env
+Title: On startup files and <tt>$ENV</tt> across and detecting various shells
+
+Interactive shells look at <tt>~/.mkshrc</tt> (or <tt>/system/etc/mkshrc</tt>
+on Android and <tt>/etc/mkshrc</tt> on FreeWRT and OpenWrt) by default. This
+location can, however, be overridden by setting the <tt>ENV</tt> environment
+variable. (FreeBSD is rumoured to set it in their system profile.) It’s better
+to not set <tt>$ENV</tt> if possible and let every shell user their native
+startup files; otherwise, you must ensure that it runs under all shells. Check
+<tt>$BASH_VERSION</tt> (GNU bash), <tt>$KSH_VERSION</tt> (contains “LEGACY KSH”
+or “MIRBSD KSH” for mksh, “PD KSH” for ancient mirbsdksh/oksh/pdksh, “Version”
+for ksh93); <tt>$NETBSD_SHELL</tt> (NetBSD ash); <tt>POSH_VERSION</tt> (posh, a
+pdksh derivative); <tt>$SH_VERSION</tt> (“PD KSH” as sh), <tt>$YASH_VERSION</tt>
+(yash), <tt>$ZSH_VERSION</tt> (or if <tt>$VERSION</tt> begins with “zsh”); a <a
+href="@@RELPATH@@ksh-chan.htm#which-shell">list of more approaches</a> exists.
+----
+ToC: ctrl-l-cls
+Title: ^L (Ctrl-L) does not clear the screen
+
+Use ^[^L (Escape+Ctrl-L) or rebind it:<br />
+<tt>bind '^L=clear-screen'</tt>
+----
+ToC: ctrl-u-pico
+Title: ^U (Ctrl-U) clears the entire line
+
+If it should only delete the line up to the cursor, use:<br />
+<tt>bind -m ^U='^[0^K'</tt>
+----
+ToC: cur-up-zsh
+Title: Cursor Up behaves differently from zsh
+
+Some shells make Cursor Up search in the history only for commands
+starting with what was already entered. mksh separates the shortcuts:
+Cursor Up goes up one command and PgUp searches the history as described
+above. You can, of course, rebind:<br />
+<tt>bind '^XA=search-history-up'</tt>
+----
+ToC: current
+Title: Can mksh set the title of the window according to the command running?
+
+There’s no such thing as “the command currently running”; consider
+pipelines and delays (<tt>cmd1 | (cmd2; sleep 3; cmd3) | cmd4</tt>).
+There is, however, a way to make the shell display the command <em>line</em>
+during the time it is executed; for testing, you will need to download <a
+href="https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=shellsnippets/shellsnippets.git;a=blob;f=mksh/terminal-title;hb=HEAD">this
+script</a> and <tt>source</tt> it. For merging into your <tt>~/.mkshrc</tt>
+you should first understand how it works: lines 4–18 set a <tt>PS1</tt>
+(prompt) equivalent to lines 84–96 of the stock <tt>dot.mkshrc</tt>, with
+one change: line 15 (<tt>print &gt;/dev/tty …</tt>) is new, inserted just
+before the <tt>return</tt> command of the function substitution in the
+default prompt; this is what you’ll need to merge into your own, custom,
+prompt (if you have one; otherwise pull this adaption to the default
+one). Line 19 is the only other thing in this script rebinding the Ctrl-M
+key (which is normally produced by the Enter/Return key) to code that…
+does <em>something crazy</em>. This trick however <em>does funny things with
+multiline commands</em>, so if you type something out in multiple lines,
+for example <strong>here documents</strong> or <strong>loops</strong> press
+<strong>Ctrl-J instead of Enter/Return</strong> after <em>each</em> line
+including the first (at PS1) and final (at PS2) one.
+----
+ToC: other-tty
+Title: How do I start mksh on a specific terminal?
+
+<p>Normally: <tt>mksh -T<i>/dev/tty2</i></tt></p>
+<p>However, if you want for it to return (e.g. for an embedded system rescue
+ shell), use this on your real console device instead:
+ <tt>mksh -T!<i>/dev/ttyACM0</i></tt></p>
+<p>mksh can also daemonise (send to the background):
+ <tt>mksh -T- -c 'exec cdio lock'</tt></p>
+----
+ToC: completion
+Title: What about programmable tab completion?
+
+The shell itself provides static deterministic tab completion.
+However, you can use hooks like reprogramming the Tab key to a
+command line editor macro, and using the <tt>evaluate-region</tt>
+editor command (modulo a bugfix) together with <tt>quote-region</tt> and shell functions to
+implement a programmable completion engine. Multiple people have
+been considering doing so in our IRC channel; we’ll hyperlink to
+these engines when they are available.
+----
+ToC: posix-mode
+Title: How POSIX compliant is mksh? Also, UTF-8 vs. locales?
+
+<p>You’ll need to use the <tt>lksh</tt> binary, unless your C <tt>long</tt>
+ type is 32 bits wide, for POSIX-compliant arithmetic in the shell. This is
+ because <tt>mksh</tt> provides consistent, wraparound-defined, 32-bit
+ arithmetics on all platforms normally. You’ll also need to enable POSIX mode
+ (<tt>set -o posix</tt>) explicitly, which also disables brace expansion upon
+ being enabled (use <tt>set -o braceexpand</tt> to reenable if needed).</p>
+<p>For the purpose of POSIX, mksh supports only the <tt>C</tt> locale. mksh’s
+ <tt>utf8-mode</tt> (which only supports the BMP (Basic Multilingual Plane) of
+ UCS and maps raw octets into the U+EF80‥U+EFFF wide character range; see
+ <tt>Arithmetic expressions</tt> in mksh(1) for details) <em>must</em> stay
+ disabled in POSIX mode (it is disabled upon enabling POSIX mode in R56+).</p>
+<p class="boxhead">The following POSIX sh-compatible code toggles the
+ <tt>utf8-mode</tt> option dependent on the current POSIX locale, for mksh
+ to allow using the UTF-8 mode, within the constraints outlined above, in
+ code portable across various shell implementations:</p>
+<div class="boxtext">
+ <pre>
+ case ${KSH_VERSION:-} in
+ *MIRBSD KSH*|*LEGACY KSH*)
+ case ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} in
+ *[Uu][Tt][Ff]8*|*[Uu][Tt][Ff]-8*) set -U ;;
+ *) set +U ;;
+ esac ;;
+ esac
+ </pre>
+</div><p class="boxfoot">In near future, (UTF-8) locale tracking will
+ be implemented, though.</p>
+<p>The shell is pretty close to POSIX, when run as <tt>lksh -o posix</tt>
+ under the "C" locale it is intended to match. It does not do everything
+ like other POSIX-compatible or ‑compliant shells, though.</p>
+----
+ToC: function-local-scopes
+Title: What differences in function-local scopes are there?
+
+<p><tt>mksh</tt> has a different scope model from AT&amp;T <tt>ksh</tt>,
+ which leads to subtle differences in semantics for identical builtins.
+ This can cause issues with a <tt>nameref</tt> to suddenly point to a
+ local variable by accident. (Other common shells share mksh’s scoping
+ model.)</p>
+<p class="boxhead">GNU <tt>bash</tt> allows unsetting local variables; in
+ <tt>mksh</tt>, doing so in a function allows back access to the global
+ variable (actually the one in the next scope up) with the same name. The
+ following code, when run before function definitions, changes the behaviour
+ of <tt>unset</tt> to behave like other shells (the alias can be removed
+ after the definitions):</p>
+<div class="boxtext">
+ <pre>
+ case ${KSH_VERSION:-} in
+ *MIRBSD KSH*|*LEGACY KSH*)
+ function unset_compat {
+ \\builtin typeset unset_compat_x
+
+ for unset_compat_x in "$@"; do
+ eval "\\\\builtin unset $unset_compat_x[*]"
+ done
+ }
+ \\builtin alias unset=unset_compat
+ ;;
+ esac
+ </pre>
+</div><p class="boxfoot">When a local variable is created (e.g. using
+ <tt>local</tt>, <tt>typeset</tt>, <tt>integer</tt> or
+ <tt>\\builtin typeset</tt>) it does not, like in other shells, inherit
+ the value from the global (next scope up) variable with the same name;
+ it is rather created without any value (unset but defined).</p>
+----
+ToC: regex-comparison
+Title: I get an error in this regex comparison
+
+<p>Use extglobs instead of regexes:<br />
+ <tt>[[ foo =~ (foo|bar).*baz ]]</tt><br />
+ … becomes…<br />
+ <tt>[[ foo = *@(foo|bar)*baz* ]]</tt></p>
+----
+ToC: extensions-to-avoid
+Title: Are there any extensions to avoid?
+
+<p>GNU <tt>bash</tt> supports “<tt>&amp;&gt;</tt>” (and “|&amp;”) to redirect
+ both stdout and stderr in one go, but this breaks POSIX and Korn Shell syntax;
+ use POSIX redirections instead:</p>
+<table border="1" cellpadding="3">
+ <tr><td>GNU bash</td><td>
+ <tt>foo |&amp; bar |&amp; baz &amp;&gt;log</tt>
+ </td></tr>
+ <tr><td>POSIX</td><td>
+ <tt>foo 2&gt;&amp;1 | bar 2&gt;&amp;1 | baz &gt;log 2&gt;&amp;1</tt>
+ </td></tr>
+</table>
+----
+ToC: while-read-pipe
+Title: Something is going wrong with my while...read loop
+
+<p class="boxhead">Most likely, you’ve encountered the problem in which
+ the shell runs all parts of a pipeline as subshell. The inner loop will
+ be executed in a subshell and variable changes cannot be propagated if
+ run in a pipeline:</p>
+<div class="boxtext">
+ <pre>
+ bar | baz | while read foo; do ...; done
+ </pre>
+</div><p class="boxfoot">Note that <tt>exit</tt> in the inner loop will
+ also only exit the subshell and not the original shell. Likewise, if the
+ code is inside a function, <tt>return</tt> in the inner loop will only
+ exit the subshell and won’t terminate the function.</p>
+<p class="boxhead">Use co-processes instead:</p>
+<div class="boxtext">
+ <pre>
+ bar | baz |&amp;
+ while read -p foo; do ...; done
+ exec 3&gt;&amp;p; exec 3&gt;&amp;-
+ </pre>
+</div><p class="boxfoot">If <tt>read</tt> is run in a way such as
+ <tt>while read foo; do ...; done</tt> then leading whitespace will be
+ removed (IFS) and backslashes processed. You might want to use
+ <tt>while IFS= read -r foo; do ...; done</tt> for pristine I/O.</p>
+<p class="boxhead">Similarly, when using the <tt>-a</tt> option, use of the
+ <tt>-r</tt> option might be prudent (<tt>read -raN-1 arr &lt;file</tt>);
+ the same applies for NUL-terminated lines:</p>
+<div class="boxtext">
+ <pre>
+ find . -type f -print0 |&amp; \
+ while IFS= read -d '' -pr filename; do
+ print -r -- "found &lt;${filename#./}&gt;"
+ done
+ </pre>
+</div>
+----
+ToC: command-alias
+Title: “command” doesn’t expand aliases as in ksh93
+
+This is because AT&amp;T ksh93 ships a predefined alias enabling this:<br />
+<tt>alias command='command '</tt><br />
+put this into your <tt>~/.mkshrc</tt>
+(note the space before the closing single quote)
+----
+ToC: builtin-rename
+Title: “rename” doesn’t work as expected!
+
+<p>There’s a <tt>rename</tt> built-in utility in mksh, which is a very
+ thin wrapper around the rename(2) syscall. It receives two pathnames,
+ source and destination where the first is then atomically renamed to
+ the latter. It does not move, i.e. fails for different filesystems.</p>
+<p>The GNU package <tt>util-linux</tt> has a different <tt>rename</tt>
+ command. If you wish to invoke an external utility (in favour over a
+ builtin), you can use <tt>dot.mkshrc</tt>’s function <tt>enable</tt>
+ or put the following into your <tt>~/.mkshrc</tt>:</p>
+<pre>alias rename="$(whence -p rename)"</pre>
+----
+ToC: builtin-sleep
+Title: “sleep” does not accept â€m’ for minutes!
+
+<p>mksh contains a <tt>sleep</tt> built-in utility, in order to be
+ able to offer sub-second sleep to shell scripts for most platforms.
+ (It does not exist if the platform lacks select(2)â€â€” which should
+ be rare.)</p>
+<p>GNU coreutils contains a sleep implementation accepting suffixed
+ numbers. If you wish to invoke an external utility (in favour over a
+ builtin), you can use <tt>dot.mkshrc</tt>’s function <tt>enable</tt>
+ or put something along the following lines into <tt>~/.mkshrc</tt>:</p>
+<pre>alias sleep="$(whence -p sleep)"</pre>
+<pre>timer() { sleep $(($1*60${2:++$2})); } # timer mins [secs]</pre>
+<pre>timer() {
+ local arg=${1/m/'*60+'}
+ [[ $arg = *+ ]] &amp;&amp; arg+=0
+ sleep $(($arg)
+}</pre>
+----
+ToC: string-concat
+Title: “+=” behaves differently from other shells
+
+<p>In POSIX shell, “=” in code like <tt>var=content</tt> is a string
+ assignment, always. You can use <tt>var=$((content))</tt> for an
+ arithmetic assignment that mostly uses C language rules.</p>
+<p>It stands to consider that the common shell extension “+=” as in
+ <tt>var+=content</tt> would always do string concatenation; it does
+ in mksh, but not in some other shells, in which, when <tt>var</tt> has
+ been declared integer, addition is done instead.</p>
+<p>You can make the code portable by using “((…))” (a.k.a. <tt>let</tt>)
+ instead: <tt>(( var += content ))</tt> does arithmetic addition in
+ all shells involved.</p>
+----
+ToC: set-e
+Title: I use “set -e” and my code unexpectedly errors out
+
+<p>I personally recommend people to not use “<tt>set -e</tt>”, as it
+makes error handling more difficult. However, some insist. There have
+been bugfixes (relative to e.g. oksh/loksh and posh) in this aspect,
+and the user has to make sure <tt>$?</tt> is always 0 ASAP even after
+a command that doesn’t check it.</p>
+<pre>istwo() {
+ for i in "$@"; do
+ test x"$i" = x"2" &amp;&amp; echo two
+ done
+}
+set -e
+istwo 1
+echo END</pre>
+<p>This can be fixed by either adding an explicit “<tt>:</tt>” (or
+“<tt>true</tt>”) after the comparison, or even…</p>
+<pre>test x"$i" = x"2" &amp;&amp; echo two || :</pre>
+<p>… or right after the <tt>done</tt> inside the function, but…</p>
+<pre>test x"$i" != x"2" || echo two</pre>
+<p>… negating the condition and using “<tt>||</tt>” is preferable.</p>
+
+<p>Remember that Korn shell-style functions (with <tt>function</tt>
+ keyword and <strong>without</strong> parenthesēs) in AT&amp;T ksh93
+ and mksh R51 and up have their own shell option scope, but while…</p>
+<pre>function istwo {
+ set +e
+ …
+}</pre>
+<p>… might help in error handling, the return status of a function is
+ still the last errorlevel inside, so an explicit true (“<tt>:</tt>”)
+ or, more explicitly, “<tt>return 0</tt>” at its end is still needed
+ if the <em>caller</em> runs under <tt>set -e</tt>.</p>
+----
+ToC: set-eo-pipefail
+Title: I use “set -eo pipefail” and my code unexpectedly errors out
+
+<p class="boxhead">Related to the above FAQ entry, using
+ <tt>set -o pipefail</tt> makes the following construct error out:</p>
+<div class="boxtext">
+ <pre>
+ set -e
+ for x in 1 2; do
+ false &amp;&amp; echo $x
+ done | cat
+ </pre>
+</div><p class="boxfoot">This is because, while the <tt>&amp;&amp;</tt>
+ ensures that the inner command’s failure is not taken, it sets the entire
+ <tt>for</tt>‥<tt>done</tt> loop’s errorlevel, which is passed on by
+ <tt>-o pipefail</tt>.</p>
+<p>Invert the inner command:<br />
+ <tt>true || echo $x</tt></p>
+----
+ToC: faq
+Title: My question is not answered here!
+
+Do read the mksh(1) manual page. You might also wish to read the <a
+ href="@@RELPATH@@ksh-chan.htm">homepage of the <tt>#ksh</tt> IRC channel
+on Freenode</a> which lists several resources for Korn or POSIX-compatible
+shells in general. Or, <a href="#contact">contact</a> us (developer and
+users), for example via IRC.
+----
+ToC: contact
+Title: How do I contact you (to say thanks)?
+
+You can say hi in the <tt>#!/bin/mksh</tt> channel on Freenode <a
+ href="@@RELPATH@@irc.htm">IRC</a>, although a <a
+ href="@@RELPATH@@danke.htm">donation</a> wouldn’t be amiss ⺠The <a
+ href="http://www.mail-archive.com/miros-mksh@mirbsd.org/">mailing
+list</a> can also be used.
+----
diff --git a/shells/mksh/files/os2.c b/shells/mksh/files/os2.c
new file mode 100644
index 00000000000..8a8078274b1
--- /dev/null
+++ b/shells/mksh/files/os2.c
@@ -0,0 +1,585 @@
+/*-
+ * Copyright (c) 2015, 2017, 2020
+ * KO Myung-Hun <komh@chollian.net>
+ * Copyright (c) 2017
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#define INCL_KBD
+#define INCL_DOS
+#include <os2.h>
+
+#include "sh.h"
+
+#include <klibc/startup.h>
+#include <errno.h>
+#include <io.h>
+#include <unistd.h>
+#include <process.h>
+
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.10 2020/04/07 11:13:45 tg Exp $");
+
+static char *remove_trailing_dots(char *);
+static int access_stat_ex(int (*)(), const char *, void *);
+static int test_exec_exist(const char *, char *);
+static void response(int *, const char ***);
+static char *make_response_file(char * const *);
+static void add_temp(const char *);
+static void cleanup_temps(void);
+static void cleanup(void);
+
+#define RPUT(x) do { \
+ if (new_argc >= new_alloc) { \
+ new_alloc += 20; \
+ if (!(new_argv = realloc(new_argv, \
+ new_alloc * sizeof(char *)))) \
+ goto exit_out_of_memory; \
+ } \
+ new_argv[new_argc++] = (x); \
+} while (/* CONSTCOND */ 0)
+
+#define KLIBC_ARG_RESPONSE_EXCLUDE \
+ (__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL)
+
+static void
+response(int *argcp, const char ***argvp)
+{
+ int i, old_argc, new_argc, new_alloc = 0;
+ const char **old_argv, **new_argv;
+ char *line, *l, *p;
+ FILE *f;
+
+ old_argc = *argcp;
+ old_argv = *argvp;
+ for (i = 1; i < old_argc; ++i)
+ if (old_argv[i] &&
+ !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) &&
+ old_argv[i][0] == '@')
+ break;
+
+ if (i >= old_argc)
+ /* do nothing */
+ return;
+
+ new_argv = NULL;
+ new_argc = 0;
+ for (i = 0; i < old_argc; ++i) {
+ if (i == 0 || !old_argv[i] ||
+ (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) ||
+ old_argv[i][0] != '@' ||
+ !(f = fopen(old_argv[i] + 1, "rt")))
+ RPUT(old_argv[i]);
+ else {
+ long filesize;
+
+ fseek(f, 0, SEEK_END);
+ filesize = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ line = malloc(filesize + /* type */ 1 + /* NUL */ 1);
+ if (!line) {
+ exit_out_of_memory:
+ fputs("Out of memory while reading response file\n", stderr);
+ exit(255);
+ }
+
+ line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE;
+ l = line + 1;
+ while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) {
+ p = strchr(l, '\n');
+ if (p) {
+ /*
+ * if a line ends with a backslash,
+ * concatenate with the next line
+ */
+ if (p > l && p[-1] == '\\') {
+ char *p1;
+ int count = 0;
+
+ for (p1 = p - 1; p1 >= l &&
+ *p1 == '\\'; p1--)
+ count++;
+
+ if (count & 1) {
+ l = p + 1;
+
+ continue;
+ }
+ }
+
+ *p = 0;
+ }
+ p = strdup(line);
+ if (!p)
+ goto exit_out_of_memory;
+
+ RPUT(p + 1);
+
+ l = line + 1;
+ }
+
+ free(line);
+
+ if (ferror(f)) {
+ fputs("Cannot read response file\n", stderr);
+ exit(255);
+ }
+
+ fclose(f);
+ }
+ }
+
+ RPUT(NULL);
+ --new_argc;
+
+ *argcp = new_argc;
+ *argvp = new_argv;
+}
+
+static void
+init_extlibpath(void)
+{
+ const char *vars[] = {
+ "BEGINLIBPATH",
+ "ENDLIBPATH",
+ "LIBPATHSTRICT",
+ NULL
+ };
+ char val[512];
+ int flag;
+
+ for (flag = 0; vars[flag]; flag++) {
+ DosQueryExtLIBPATH(val, flag + 1);
+ if (val[0])
+ setenv(vars[flag], val, 1);
+ }
+}
+
+void
+os2_init(int *argcp, const char ***argvp)
+{
+ KBDINFO ki;
+
+ response(argcp, argvp);
+
+ init_extlibpath();
+
+ if (!isatty(STDIN_FILENO))
+ setmode(STDIN_FILENO, O_BINARY);
+ if (!isatty(STDOUT_FILENO))
+ setmode(STDOUT_FILENO, O_BINARY);
+ if (!isatty(STDERR_FILENO))
+ setmode(STDERR_FILENO, O_BINARY);
+
+ /* ensure ECHO mode is ON so that read command echoes. */
+ memset(&ki, 0, sizeof(ki));
+ ki.cb = sizeof(ki);
+ ki.fsMask |= KEYBOARD_ECHO_ON;
+ KbdSetStatus(&ki, 0);
+
+ atexit(cleanup);
+}
+
+void
+setextlibpath(const char *name, const char *val)
+{
+ int flag;
+ char *p, *cp;
+
+ if (!strcmp(name, "BEGINLIBPATH"))
+ flag = BEGIN_LIBPATH;
+ else if (!strcmp(name, "ENDLIBPATH"))
+ flag = END_LIBPATH;
+ else if (!strcmp(name, "LIBPATHSTRICT"))
+ flag = LIBPATHSTRICT;
+ else
+ return;
+
+ /* convert slashes to backslashes */
+ strdupx(cp, val, ATEMP);
+ for (p = cp; *p; p++) {
+ if (*p == '/')
+ *p = '\\';
+ }
+
+ DosSetExtLIBPATH(cp, flag);
+
+ afree(cp, ATEMP);
+}
+
+/* remove trailing dots */
+static char *
+remove_trailing_dots(char *name)
+{
+ char *p = strnul(name);
+
+ while (--p > name && *p == '.')
+ /* nothing */;
+
+ if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
+ p[1] = '\0';
+
+ return (name);
+}
+
+#define REMOVE_TRAILING_DOTS(name) \
+ remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
+
+/* alias of stat() */
+extern int _std_stat(const char *, struct stat *);
+
+/* replacement for stat() of kLIBC which fails if there are trailing dots */
+int
+stat(const char *name, struct stat *buffer)
+{
+ return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
+}
+
+/* alias of access() */
+extern int _std_access(const char *, int);
+
+/* replacement for access() of kLIBC which fails if there are trailing dots */
+int
+access(const char *name, int mode)
+{
+ /*
+ * On OS/2 kLIBC, X_OK is set only for executable files.
+ * This prevents scripts from being executed.
+ */
+ if (mode & X_OK)
+ mode = (mode & ~X_OK) | R_OK;
+
+ return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
+}
+
+#define MAX_X_SUFFIX_LEN 4
+
+static const char *x_suffix_list[] =
+ { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
+
+/* call fn() by appending executable extensions */
+static int
+access_stat_ex(int (*fn)(), const char *name, void *arg)
+{
+ char *x_name;
+ const char **x_suffix;
+ int rc = -1;
+ size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
+
+ /* otherwise, try to append executable suffixes */
+ x_name = alloc(x_namelen, ATEMP);
+
+ for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
+ strlcpy(x_name, name, x_namelen);
+ strlcat(x_name, *x_suffix, x_namelen);
+
+ rc = fn(x_name, arg);
+ }
+
+ afree(x_name, ATEMP);
+
+ return (rc);
+}
+
+/* access()/search_access() version */
+int
+access_ex(int (*fn)(const char *, int), const char *name, int mode)
+{
+ /*XXX this smells fishy --mirabilos */
+ return (access_stat_ex(fn, name, (void *)mode));
+}
+
+/* stat()/lstat() version */
+int
+stat_ex(int (*fn)(const char *, struct stat *),
+ const char *name, struct stat *buffer)
+{
+ return (access_stat_ex(fn, name, buffer));
+}
+
+static int
+test_exec_exist(const char *name, char *real_name)
+{
+ struct stat sb;
+
+ if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
+ return (-1);
+
+ /* safe due to calculations in real_exec_name() */
+ memcpy(real_name, name, strlen(name) + 1);
+
+ return (0);
+}
+
+const char *
+real_exec_name(const char *name)
+{
+ char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
+ const char *real_name = name;
+
+ if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
+ /*XXX memory leak */
+ strdupx(real_name, x_name, ATEMP);
+
+ return (real_name);
+}
+
+/* make a response file to pass a very long command line */
+static char *
+make_response_file(char * const *argv)
+{
+ char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
+ char *rsp_name = &rsp_name_arg[1];
+ int i;
+ int fd;
+ char *result;
+
+ if ((fd = mkstemp(rsp_name)) == -1)
+ return (NULL);
+
+ /* write all the arguments except a 0th program name */
+ for (i = 1; argv[i]; i++) {
+ write(fd, argv[i], strlen(argv[i]));
+ write(fd, "\n", 1);
+ }
+
+ close(fd);
+ add_temp(rsp_name);
+ strdupx(result, rsp_name_arg, ATEMP);
+
+ return (result);
+}
+
+/* alias of execve() */
+extern int _std_execve(const char *, char * const *, char * const *);
+
+/* replacement for execve() of kLIBC */
+int
+execve(const char *name, char * const *argv, char * const *envp)
+{
+ const char *exec_name;
+ FILE *fp;
+ char sign[2];
+ int pid;
+ int status;
+ int fd;
+ int rc;
+ int saved_mode;
+ int saved_errno;
+
+ /*
+ * #! /bin/sh : append .exe
+ * extproc sh : search sh.exe in PATH
+ */
+ exec_name = search_path(name, path, X_OK, NULL);
+ if (!exec_name) {
+ errno = ENOENT;
+ return (-1);
+ }
+
+ /*-
+ * kLIBC execve() has problems when executing scripts.
+ * 1. it fails to execute a script if a directory whose name
+ * is same as an interpreter exists in a current directory.
+ * 2. it fails to execute a script not starting with sharpbang.
+ * 3. it fails to execute a batch file if COMSPEC is set to a shell
+ * incompatible with cmd.exe, such as /bin/sh.
+ * And ksh process scripts more well, so let ksh process scripts.
+ */
+ errno = 0;
+ if (!(fp = fopen(exec_name, "rb")))
+ errno = ENOEXEC;
+
+ if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
+ errno = ENOEXEC;
+
+ if (fp && fclose(fp))
+ errno = ENOEXEC;
+
+ if (!errno &&
+ !((sign[0] == 'M' && sign[1] == 'Z') ||
+ (sign[0] == 'N' && sign[1] == 'E') ||
+ (sign[0] == 'L' && sign[1] == 'X')))
+ errno = ENOEXEC;
+
+ if (errno == ENOEXEC)
+ return (-1);
+
+ /*
+ * Normal OS/2 programs expect that standard IOs, especially stdin,
+ * are opened in text mode at the startup. By the way, on OS/2 kLIBC
+ * child processes inherit a translation mode of a parent process.
+ * As a result, if stdin is set to binary mode in a parent process,
+ * stdin of child processes is opened in binary mode as well at the
+ * startup. In this case, some programs such as sed suffer from CR.
+ */
+ saved_mode = setmode(STDIN_FILENO, O_TEXT);
+
+ pid = spawnve(P_NOWAIT, exec_name, argv, envp);
+ saved_errno = errno;
+
+ /* arguments too long? */
+ if (pid == -1 && saved_errno == EINVAL) {
+ /* retry with a response file */
+ char *rsp_name_arg = make_response_file(argv);
+
+ if (rsp_name_arg) {
+ char *rsp_argv[3] = { argv[0], rsp_name_arg, NULL };
+
+ pid = spawnve(P_NOWAIT, exec_name, rsp_argv, envp);
+ saved_errno = errno;
+
+ afree(rsp_name_arg, ATEMP);
+ }
+ }
+
+ /* restore translation mode of stdin */
+ setmode(STDIN_FILENO, saved_mode);
+
+ if (pid == -1) {
+ cleanup_temps();
+
+ errno = saved_errno;
+ return (-1);
+ }
+
+ /* close all opened handles */
+ for (fd = 0; fd < NUFILE; fd++) {
+ if (fcntl(fd, F_GETFD) == -1)
+ continue;
+
+ close(fd);
+ }
+
+ while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+ /* nothing */;
+
+ cleanup_temps();
+
+ /* Is this possible? And is this right? */
+ if (rc == -1)
+ return (-1);
+
+ if (WIFSIGNALED(status))
+ _exit(ksh_sigmask(WTERMSIG(status)));
+
+ _exit(WEXITSTATUS(status));
+}
+
+static struct temp *templist = NULL;
+
+static void
+add_temp(const char *name)
+{
+ struct temp *tp;
+
+ tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
+ memcpy(tp->tffn, name, strlen(name) + 1);
+ tp->next = templist;
+ templist = tp;
+}
+
+/* alias of unlink() */
+extern int _std_unlink(const char *);
+
+/*
+ * Replacement for unlink() of kLIBC not supporting to remove files used by
+ * another processes.
+ */
+int
+unlink(const char *name)
+{
+ int rc;
+
+ rc = _std_unlink(name);
+ if (rc == -1 && errno != ENOENT)
+ add_temp(name);
+
+ return (rc);
+}
+
+static void
+cleanup_temps(void)
+{
+ struct temp *tp;
+ struct temp **tpnext;
+
+ for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
+ if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
+ *tpnext = tp->next;
+ afree(tp, APERM);
+ } else {
+ tpnext = &tp->next;
+ }
+ }
+}
+
+static void
+cleanup(void)
+{
+ cleanup_temps();
+}
+
+int
+getdrvwd(char **cpp, unsigned int drvltr)
+{
+ PBYTE cp;
+ ULONG sz;
+ APIRET rc;
+ ULONG drvno;
+
+ if (DosQuerySysInfo(QSV_MAX_PATH_LENGTH, QSV_MAX_PATH_LENGTH,
+ &sz, sizeof(sz)) != 0) {
+ errno = EDOOFUS;
+ return (-1);
+ }
+
+ /* allocate 'X:/' plus sz plus NUL */
+ checkoktoadd((size_t)sz, (size_t)4);
+ cp = aresize(*cpp, (size_t)sz + (size_t)4, ATEMP);
+ cp[0] = ksh_toupper(drvltr);
+ cp[1] = ':';
+ cp[2] = '/';
+ drvno = ksh_numuc(cp[0]) + 1;
+ /* NUL is part of space within buffer passed */
+ ++sz;
+ if ((rc = DosQueryCurrentDir(drvno, cp + 3, &sz)) == 0) {
+ /* success! */
+ *cpp = cp;
+ return (0);
+ }
+ afree(cp, ATEMP);
+ *cpp = NULL;
+ switch (rc) {
+ case 15: /* invalid drive */
+ errno = ENOTBLK;
+ break;
+ case 26: /* not dos disk */
+ errno = ENODEV;
+ break;
+ case 108: /* drive locked */
+ errno = EDEADLK;
+ break;
+ case 111: /* buffer overflow */
+ errno = ENAMETOOLONG;
+ break;
+ default:
+ errno = EINVAL;
+ }
+ return (-1);
+}
diff --git a/shells/mksh/files/rlimits.opt b/shells/mksh/files/rlimits.opt
new file mode 100644
index 00000000000..db2a53107e7
--- /dev/null
+++ b/shells/mksh/files/rlimits.opt
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (c) 2013, 2015
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+@RLIMITS_DEFNS
+__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.4 2019/04/24 20:56:31 tg Exp $");
+@RLIMITS_ITEMS
+#define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid),
+@@
+
+/* generic options for the ulimit builtin */
+
+<a|
+<H|
+<S|
+
+/* do not use options -H, -S or -a or change the order */
+
+>t|RLIMIT_CPU
+FN("time(cpu-seconds)", RLIMIT_CPU, 1
+
+>f|RLIMIT_FSIZE
+FN("file(blocks)", RLIMIT_FSIZE, 512
+
+>c|RLIMIT_CORE
+FN("coredump(blocks)", RLIMIT_CORE, 512
+
+>d|RLIMIT_DATA
+FN("data(KiB)", RLIMIT_DATA, 1024
+
+>s|RLIMIT_STACK
+FN("stack(KiB)", RLIMIT_STACK, 1024
+
+>l|RLIMIT_MEMLOCK
+FN("lockedmem(KiB)", RLIMIT_MEMLOCK, 1024
+
+>n|RLIMIT_NOFILE
+FN("nofiles(descriptors)", RLIMIT_NOFILE, 1
+
+>p|RLIMIT_NPROC
+FN("processes", RLIMIT_NPROC, 1
+
+>w|RLIMIT_SWAP
+FN("swap(KiB)", RLIMIT_SWAP, 1024
+
+>T|RLIMIT_TIME
+FN("humantime(seconds)", RLIMIT_TIME, 1
+
+>V|RLIMIT_NOVMON
+FN("vnodemonitors", RLIMIT_NOVMON, 1
+
+>i|RLIMIT_SIGPENDING
+FN("sigpending", RLIMIT_SIGPENDING, 1
+
+>q|RLIMIT_MSGQUEUE
+FN("msgqueue(bytes)", RLIMIT_MSGQUEUE, 1
+
+>M|RLIMIT_AIO_MEM
+FN("AIOlockedmem(KiB)", RLIMIT_AIO_MEM, 1024
+
+>O|RLIMIT_AIO_OPS
+FN("AIOoperations", RLIMIT_AIO_OPS, 1
+
+>C|RLIMIT_TCACHE
+FN("cachedthreads", RLIMIT_TCACHE, 1
+
+>B|RLIMIT_SBSIZE
+FN("sockbufsiz(KiB)", RLIMIT_SBSIZE, 1024
+
+>P|RLIMIT_PTHREAD
+FN("threadsperprocess", RLIMIT_PTHREAD, 1
+
+>r|RLIMIT_THREADS
+FN("threadsperprocess", RLIMIT_THREADS, 1
+
+>e|RLIMIT_NICE
+FN("maxnice", RLIMIT_NICE, 1
+
+>r|RLIMIT_RTPRIO
+FN("maxrtprio", RLIMIT_RTPRIO, 1
+
+>m|ULIMIT_M_IS_RSS
+FN("resident-set(KiB)", RLIMIT_RSS, 1024
+>m|ULIMIT_M_IS_VMEM
+FN("memory(KiB)", RLIMIT_VMEM, 1024
+
+>v|ULIMIT_V_IS_VMEM
+FN("virtual-memory(KiB)", RLIMIT_VMEM, 1024
+>v|ULIMIT_V_IS_AS
+FN("address-space(KiB)", RLIMIT_AS, 1024
+
+>x|RLIMIT_LOCKS
+FN("filelocks", RLIMIT_LOCKS, 1
+
+|RLIMITS_OPTCS
diff --git a/shells/mksh/files/sh.h b/shells/mksh/files/sh.h
new file mode 100644
index 00000000000..8394c89f6d0
--- /dev/null
+++ b/shells/mksh/files/sh.h
@@ -0,0 +1,2904 @@
+/* $OpenBSD: sh.h,v 1.35 2015/09/10 22:48:58 nicm Exp $ */
+/* $OpenBSD: shf.h,v 1.6 2005/12/11 18:53:51 deraadt Exp $ */
+/* $OpenBSD: table.h,v 1.8 2012/02/19 07:52:30 otto Exp $ */
+/* $OpenBSD: tree.h,v 1.10 2005/03/28 21:28:22 deraadt Exp $ */
+/* $OpenBSD: expand.h,v 1.7 2015/09/01 13:12:31 tedu Exp $ */
+/* $OpenBSD: lex.h,v 1.13 2013/03/03 19:11:34 guenther Exp $ */
+/* $OpenBSD: proto.h,v 1.35 2013/09/04 15:49:19 millert Exp $ */
+/* $OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $ */
+/* $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $ */
+
+/*-
+ * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including unâ€
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person’s immediate fault when using the work as intended.
+ */
+
+#ifdef __dietlibc__
+/* XXX imake style */
+#define _BSD_SOURCE /* live, BSD, live❣ */
+#endif
+
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/types.h>
+#if HAVE_BOTH_TIME_H
+#include <sys/time.h>
+#include <time.h>
+#elif HAVE_SYS_TIME_H
+#include <sys/time.h>
+#elif HAVE_TIME_H
+#include <time.h>
+#endif
+#include <sys/ioctl.h>
+#if HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+#if HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#if HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#if HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#if HAVE_IO_H
+#include <io.h>
+#endif
+#if HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+#if HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#include <limits.h>
+#if HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#if HAVE_TERMIOS_H
+#include <termios.h>
+#else
+/* shudder… */
+#include <termio.h>
+#endif
+#ifdef _ISC_UNIX
+/* XXX imake style */
+#include <sys/sioctl.h>
+#endif
+#if HAVE_ULIMIT_H
+#include <ulimit.h>
+#endif
+#include <unistd.h>
+#if HAVE_VALUES_H
+#include <values.h>
+#endif
+#ifdef MIRBSD_BOOTFLOPPY
+#include <wchar.h>
+#endif
+
+/* monkey-patch known-bad offsetof versions to quell a warning */
+#if (defined(__KLIBC__) || defined(__dietlibc__)) && \
+ ((defined(__GNUC__) && (__GNUC__ > 3)) || defined(__NWCC__))
+#undef offsetof
+#define offsetof(s,e) __builtin_offsetof(s, e)
+#endif
+
+#undef __attribute__
+#if HAVE_ATTRIBUTE_BOUNDED
+#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)))
+#else
+#define MKSH_A_FORMAT(x,y,z) /* nothing */
+#endif
+#if HAVE_ATTRIBUTE_NORETURN
+#define MKSH_A_NORETURN __attribute__((__noreturn__))
+#else
+#define MKSH_A_NORETURN /* nothing */
+#endif
+#if HAVE_ATTRIBUTE_PURE
+#define MKSH_A_PURE __attribute__((__pure__))
+#else
+#define MKSH_A_PURE /* nothing */
+#endif
+#if HAVE_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__))
+#else
+#define MKSH_A_USED /* nothing */
+#endif
+
+#if defined(MirBSD) && (MirBSD >= 0x09A1) && \
+ defined(__ELF__) && defined(__GNUC__) && \
+ !defined(__llvm__) && !defined(__NWCC__)
+/*
+ * We got usable __IDSTRING __COPYRIGHT __RCSID __SCCSID macros
+ * which work for all cases; no need to redefine them using the
+ * "portable" macros from below when we might have the "better"
+ * gcc+ELF specific macros or other system dependent ones.
+ */
+#else
+#undef __IDSTRING
+#undef __IDSTRING_CONCAT
+#undef __IDSTRING_EXPAND
+#undef __COPYRIGHT
+#undef __RCSID
+#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 */
+#elif defined(__ELF__) && defined(__GNUC__) && \
+ !(defined(__GNUC__) && defined(__mips16) && (__GNUC__ >= 8)) && \
+ !defined(__llvm__) && !defined(__NWCC__) && !defined(NO_ASM)
+#define __IDSTRING(prefix,string) \
+ __asm__(".section .comment" \
+ "\n .ascii \"@(\"\"#)" #prefix ": \"" \
+ "\n .asciz \"" string "\"" \
+ "\n .previous")
+#else
+#define __IDSTRING(prefix,string) \
+ static const char __IDSTRING_EXPAND(__LINE__,prefix) [] \
+ 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.898 2020/05/16 22:38:23 tg Exp $");
+#endif
+#define MKSH_VERSION "R59 2020/05/16"
+
+/* arithmetic types: C implementation */
+#if !HAVE_CAN_INTTYPES
+#if !HAVE_CAN_UCBINTS
+typedef signed int int32_t;
+typedef unsigned int uint32_t;
+#else
+typedef u_int32_t uint32_t;
+#endif
+#endif
+
+/* arithmetic types: shell arithmetics */
+#ifdef MKSH_LEGACY_MODE
+/*
+ * POSIX demands these to be the C environment's long type
+ */
+typedef long mksh_ari_t;
+typedef unsigned long mksh_uari_t;
+#else
+/*
+ * These types are exactly 32 bit wide; signed and unsigned
+ * integer wraparound, even across division and modulo, for
+ * any shell code using them, is guaranteed.
+ */
+typedef int32_t mksh_ari_t;
+typedef uint32_t mksh_uari_t;
+#endif
+
+/* boolean type (no <stdbool.h> deliberately) */
+typedef unsigned char mksh_bool;
+#undef bool
+/* false MUST equal the same 0 as written by static storage initialisation */
+#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)
+
+/* char (octet) type: C implementation */
+#if !HAVE_CAN_INT8TYPE
+#if !HAVE_CAN_UCBINT8
+typedef unsigned char uint8_t;
+#else
+typedef u_int8_t uint8_t;
+#endif
+#endif
+
+/* other standard types */
+
+#if !HAVE_RLIM_T
+typedef unsigned long rlim_t;
+#endif
+
+#if !HAVE_SIG_T
+#undef sig_t
+typedef void (*sig_t)(int);
+#endif
+
+#ifdef MKSH_TYPEDEF_SIG_ATOMIC_T
+typedef MKSH_TYPEDEF_SIG_ATOMIC_T sig_atomic_t;
+#endif
+
+#ifdef MKSH_TYPEDEF_SSIZE_T
+typedef MKSH_TYPEDEF_SSIZE_T ssize_t;
+#endif
+
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define MKSH_SHF_NO_INLINE
+#endif
+
+/* do not merge these conditionals as neatcc’s preprocessor is simple */
+#ifdef __neatcc__
+/* parsing of comma operator <,> in expressions broken */
+#define MKSH_SHF_NO_INLINE
+#endif
+
+/* un-do vendor damage */
+
+#undef BAD /* AIX defines that somewhere */
+#undef PRINT /* LynxOS defines that somewhere */
+#undef flock /* SCO UnixWare defines that to flock64 but ENOENT */
+
+
+#ifndef MKSH_INCLUDES_ONLY
+
+/* compile-time assertions */
+#define cta(name,expr) struct cta_ ## name { char t[(expr) ? 1 : -1]; }
+
+/* EBCDIC fun */
+
+/* see the large comment in shf.c for an EBCDIC primer */
+
+#if defined(MKSH_FOR_Z_OS) && defined(__MVS__) && defined(__IBMC__) && defined(__CHARSET_LIB)
+# if !__CHARSET_LIB && !defined(MKSH_EBCDIC)
+# error "Please compile with Build.sh -E for EBCDIC!"
+# endif
+# if __CHARSET_LIB && defined(MKSH_EBCDIC)
+# error "Please compile without -E argument to Build.sh for ASCII!"
+# endif
+# if __CHARSET_LIB && !defined(_ENHANCED_ASCII_EXT)
+ /* go all-out on ASCII */
+# define _ENHANCED_ASCII_EXT 0xFFFFFFFF
+# endif
+#endif
+
+/* extra types */
+
+/* getrusage does not exist on OS/2 kLIBC */
+#if !HAVE_GETRUSAGE && !defined(__OS2__)
+#undef rusage
+#undef RUSAGE_SELF
+#undef RUSAGE_CHILDREN
+#define rusage mksh_rusage
+#define RUSAGE_SELF 0
+#define RUSAGE_CHILDREN -1
+
+struct rusage {
+ struct timeval ru_utime;
+ struct timeval ru_stime;
+};
+#endif
+
+/* extra macros */
+
+#ifndef timerclear
+#define timerclear(tvp) \
+ do { \
+ (tvp)->tv_sec = (tvp)->tv_usec = 0; \
+ } while (/* CONSTCOND */ 0)
+#endif
+#ifndef timeradd
+#define timeradd(tvp,uvp,vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
+ if ((vvp)->tv_usec >= 1000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_usec -= 1000000; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#endif
+#ifndef timersub
+#define timersub(tvp,uvp,vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
+ if ((vvp)->tv_usec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_usec += 1000000; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#endif
+
+#ifdef MKSH__NO_PATH_MAX
+#undef PATH_MAX
+#else
+#ifndef PATH_MAX
+#ifdef MAXPATHLEN
+#define PATH_MAX MAXPATHLEN
+#else
+#define PATH_MAX 1024
+#endif
+#endif
+#endif
+#ifndef SIZE_MAX
+#ifdef SIZE_T_MAX
+#define SIZE_MAX SIZE_T_MAX
+#else
+#define SIZE_MAX ((size_t)-1)
+#endif
+#endif
+#ifndef S_ISLNK
+#define S_ISLNK(m) ((m & 0170000) == 0120000)
+#endif
+#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
+
+
+/* determine ksh_NSIG: first, use the traditional definitions */
+#undef ksh_NSIG
+#if defined(NSIG)
+#define ksh_NSIG (NSIG)
+#elif defined(_NSIG)
+#define ksh_NSIG (_NSIG)
+#elif defined(SIGMAX)
+#define ksh_NSIG (SIGMAX + 1)
+#elif defined(_SIGMAX)
+#define ksh_NSIG (_SIGMAX + 1)
+#elif defined(NSIG_MAX)
+#define ksh_NSIG (NSIG_MAX)
+#elif defined(MKSH_FOR_Z_OS)
+#define ksh_NSIG 40
+#else
+# error Please have your platform define NSIG.
+#endif
+/* range-check them */
+#if (ksh_NSIG < 1)
+# error Your NSIG value is not positive.
+#undef ksh_NSIG
+#endif
+/* second, see if the new POSIX definition is available */
+#ifdef NSIG_MAX
+#if (NSIG_MAX < 2)
+/* and usable */
+# error Your NSIG_MAX value is too small.
+#undef NSIG_MAX
+#elif (ksh_NSIG > NSIG_MAX)
+/* and realistic */
+# error Your NSIG value is larger than your NSIG_MAX value.
+#undef NSIG_MAX
+#else
+/* since it’s usable, prefer it */
+#undef ksh_NSIG
+#define ksh_NSIG (NSIG_MAX)
+#endif
+/* if NSIG_MAX is now still defined, use sysconf(_SC_NSIG) at runtime */
+#endif
+/* third, for cpp without the error directive, default */
+#ifndef ksh_NSIG
+#define ksh_NSIG 64
+#endif
+
+#define ksh_sigmask(sig) (((sig) < 1 || (sig) > 127) ? 255 : 128 + (sig))
+
+
+/* OS-dependent additions (functions, variables, by OS) */
+
+#ifdef MKSH_EXE_EXT
+#undef MKSH_EXE_EXT
+#define MKSH_EXE_EXT ".exe"
+#else
+#define MKSH_EXE_EXT ""
+#endif
+
+#ifdef __OS2__
+#define MKSH_UNIXROOT "/@unixroot"
+#else
+#define MKSH_UNIXROOT ""
+#endif
+
+#ifdef MKSH_DOSPATH
+#ifndef __GNUC__
+# error GCC extensions needed later on
+#endif
+#define MKSH_PATHSEPS ";"
+#define MKSH_PATHSEPC ';'
+#else
+#define MKSH_PATHSEPS ":"
+#define MKSH_PATHSEPC ':'
+#endif
+
+#if !HAVE_FLOCK_DECL
+extern int flock(int, int);
+#endif
+
+#if !HAVE_GETTIMEOFDAY
+#define mksh_TIME(tv) do { \
+ (tv).tv_usec = 0; \
+ (tv).tv_sec = time(NULL); \
+} while (/* CONSTCOND */ 0)
+#else
+#define mksh_TIME(tv) gettimeofday(&(tv), NULL)
+#endif
+
+#if !HAVE_GETRUSAGE
+extern int getrusage(int, struct rusage *);
+#endif
+
+#if !HAVE_MEMMOVE
+/* we assume either memmove or bcopy exist, at the moment */
+#define memmove(dst,src,len) bcopy((src), (dst), (len))
+#endif
+
+#if !HAVE_REVOKE_DECL
+extern int revoke(const char *);
+#endif
+
+#if defined(DEBUG) || !HAVE_STRERROR
+#undef strerror
+#define strerror /* poisoned */ dontuse_strerror
+#define cstrerror /* replaced */ cstrerror
+extern const char *cstrerror(int);
+#else
+#define cstrerror(errnum) ((const char *)strerror(errnum))
+#endif
+
+#if !HAVE_STRLCPY
+size_t strlcpy(char *, const char *, size_t);
+#endif
+
+#ifdef __INTERIX
+/* XXX imake style */
+#define makedev mkdev
+extern int __cdecl seteuid(uid_t);
+extern int __cdecl setegid(gid_t);
+#endif
+
+#if defined(__COHERENT__)
+#ifndef O_ACCMODE
+/* this need not work everywhere, take care */
+#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
+#endif
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#undef O_MAYEXEC /* https://lwn.net/Articles/820658/ */
+#define O_MAYEXEC 0
+
+#ifdef MKSH__NO_SYMLINK
+#undef S_ISLNK
+#define S_ISLNK(m) (/* CONSTCOND */ 0)
+#define mksh_lstat stat
+#else
+#define mksh_lstat lstat
+#endif
+
+#if HAVE_TERMIOS_H
+#define mksh_ttyst struct termios
+#define mksh_tcget(fd,st) tcgetattr((fd), (st))
+#define mksh_tcset(fd,st) tcsetattr((fd), TCSADRAIN, (st))
+#else
+#define mksh_ttyst struct termio
+#define mksh_tcget(fd,st) ioctl((fd), TCGETA, (st))
+#define mksh_tcset(fd,st) ioctl((fd), TCSETAW, (st))
+#endif
+
+#ifndef ISTRIP
+#define ISTRIP 0
+#endif
+
+#ifdef MKSH_EBCDIC
+#define KSH_BEL '\a'
+#define KSH_ESC 047
+#define KSH_ESC_STRING "\047"
+#define KSH_VTAB '\v'
+#else
+/*
+ * According to the comments in pdksh, \007 seems to be more portable
+ * than \a (HP-UX cc, Ultrix cc, old pcc, etc.) so we avoid the escape
+ * sequence if ASCII can be assumed.
+ */
+#define KSH_BEL 7
+#define KSH_ESC 033
+#define KSH_ESC_STRING "\033"
+#define KSH_VTAB 11
+#endif
+
+
+/* some useful #defines */
+#ifdef EXTERN
+# define E_INIT(i) = i
+#else
+# define E_INIT(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+/* define bit in flag */
+#define BIT(i) (1U << (i))
+#define NELEM(a) (sizeof(a) / sizeof((a)[0]))
+
+/*
+ * 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).
+ *
+ * MAGIC can be followed by MAGIC (to escape the octet itself) or one of:
+ * ' !)*,-?[]{|}' 0x80|' !*+?@' (probably… hysteric raisins abound)
+ *
+ * The |0x80 is likely unsafe on EBCDIC :( though the listed chars are
+ * low-bit7 at least on cp1047 so YMMV
+ */
+#define MAGIC KSH_BEL /* prefix for *?[!{,} during expand */
+#define ISMAGIC(c) (ord(c) == ORD(MAGIC))
+
+EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
+
+#ifdef MKSH_LEGACY_MODE
+#define KSH_VERSIONNAME_ISLEGACY "LEGACY"
+#else
+#define KSH_VERSIONNAME_ISLEGACY "MIRBSD"
+#endif
+#ifdef MKSH_WITH_TEXTMODE
+#define KSH_VERSIONNAME_TEXTMODE " +TEXTMODE"
+#else
+#define KSH_VERSIONNAME_TEXTMODE ""
+#endif
+#ifdef MKSH_EBCDIC
+#define KSH_VERSIONNAME_EBCDIC " +EBCDIC"
+#else
+#define KSH_VERSIONNAME_EBCDIC ""
+#endif
+#ifndef KSH_VERSIONNAME_VENDOR_EXT
+#define KSH_VERSIONNAME_VENDOR_EXT ""
+#endif
+EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME_ISLEGACY \
+ " KSH " MKSH_VERSION KSH_VERSIONNAME_EBCDIC KSH_VERSIONNAME_TEXTMODE \
+ KSH_VERSIONNAME_VENDOR_EXT);
+#define KSH_VERSION (initvsn + /* "KSH_VERSION=@(#)" */ 16)
+
+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
+ */
+union mksh_cchack {
+ char *rw;
+ const char *ro;
+};
+union mksh_ccphack {
+ char **rw;
+ const char **ro;
+};
+
+/*
+ * Evil hack since casting uint to sint is implementation-defined
+ */
+typedef union {
+ mksh_ari_t i;
+ mksh_uari_t u;
+} mksh_ari_u;
+
+/* for const debugging */
+#if defined(DEBUG) && defined(__GNUC__) && !defined(__ICC) && \
+ !defined(__INTEL_COMPILER) && !defined(__SUNPRO_C)
+char *ucstrchr(char *, int);
+char *ucstrstr(char *, const char *);
+#undef strchr
+#define strchr ucstrchr
+#define strstr ucstrstr
+#define cstrchr(s,c) ({ \
+ union mksh_cchack in, out; \
+ \
+ in.ro = (s); \
+ out.rw = ucstrchr(in.rw, (c)); \
+ (out.ro); \
+})
+#define cstrstr(b,l) ({ \
+ union mksh_cchack in, out; \
+ \
+ in.ro = (b); \
+ out.rw = ucstrstr(in.rw, (l)); \
+ (out.ro); \
+})
+#define vstrchr(s,c) (cstrchr((s), (c)) != NULL)
+#define vstrstr(b,l) (cstrstr((b), (l)) != NULL)
+#else /* !DEBUG, !gcc */
+#define cstrchr(s,c) ((const char *)strchr((s), (c)))
+#define cstrstr(s,c) ((const char *)strstr((s), (c)))
+#define vstrchr(s,c) (strchr((s), (c)) != NULL)
+#define vstrstr(b,l) (strstr((b), (l)) != NULL)
+#endif
+
+#if defined(DEBUG) || defined(__COVERITY__)
+#ifndef DEBUG_LEAKS
+#define DEBUG_LEAKS
+#endif
+#endif
+
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 592)
+#error Must run Build.sh to compile this.
+extern void thiswillneverbedefinedIhope(void);
+int
+im_sorry_dave(void)
+{
+ /* I’m sorry, Dave. I’m afraid I can’t do that. */
+ return (thiswillneverbedefinedIhope());
+}
+#endif
+
+/* use this ipv strchr(s, 0) but no side effects in s! */
+#define strnul(s) ((s) + strlen((const void *)s))
+
+#define utf_ptradjx(src,dst) do { \
+ (dst) = (src) + utf_ptradj(src); \
+} while (/* CONSTCOND */ 0)
+
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define strdupx(d,s,ap) do { \
+ (d) = strdup_i((s), (ap)); \
+} while (/* CONSTCOND */ 0)
+#define strndupx(d,s,n,ap) do { \
+ (d) = strndup_i((s), (n), (ap)); \
+} while (/* CONSTCOND */ 0)
+#else
+/* be careful to evaluate arguments only once! */
+#define strdupx(d,s,ap) do { \
+ const char *strdup_src = (const void *)(s); \
+ char *strdup_dst = NULL; \
+ \
+ if (strdup_src != NULL) { \
+ size_t strdup_len = strlen(strdup_src) + 1; \
+ strdup_dst = alloc(strdup_len, (ap)); \
+ memcpy(strdup_dst, strdup_src, strdup_len); \
+ } \
+ (d) = strdup_dst; \
+} while (/* CONSTCOND */ 0)
+#define strndupx(d,s,n,ap) do { \
+ const char *strdup_src = (const void *)(s); \
+ char *strdup_dst = NULL; \
+ \
+ if (strdup_src != NULL) { \
+ size_t strndup_len = (n); \
+ strdup_dst = alloc(strndup_len + 1, (ap)); \
+ memcpy(strdup_dst, strdup_src, strndup_len); \
+ strdup_dst[strndup_len] = '\0'; \
+ } \
+ (d) = strdup_dst; \
+} while (/* CONSTCOND */ 0)
+#endif
+#define strdup2x(d,s1,s2) do { \
+ const char *strdup_src = (const void *)(s1); \
+ const char *strdup_app = (const void *)(s2); \
+ size_t strndup_len = strlen(strdup_src); \
+ size_t strndup_ln2 = strlen(strdup_app) + 1; \
+ char *strdup_dst = alloc(strndup_len + strndup_ln2, ATEMP); \
+ \
+ memcpy(strdup_dst, strdup_src, strndup_len); \
+ memcpy(strdup_dst + strndup_len, strdup_app, strndup_ln2); \
+ (d) = strdup_dst; \
+} while (/* CONSTCOND */ 0)
+#define strpathx(d,s1,s2,cond) do { \
+ const char *strdup_src = (const void *)(s1); \
+ const char *strdup_app = (const void *)(s2); \
+ size_t strndup_len = strlen(strdup_src) + 1; \
+ size_t strndup_ln2 = ((cond) || *strdup_app) ? \
+ strlen(strdup_app) + 1 : 0; \
+ char *strdup_dst = alloc(strndup_len + strndup_ln2, ATEMP); \
+ \
+ memcpy(strdup_dst, strdup_src, strndup_len); \
+ if (strndup_ln2) { \
+ strdup_dst[strndup_len - 1] = '/'; \
+ memcpy(strdup_dst + strndup_len, strdup_app, \
+ strndup_ln2); \
+ } \
+ (d) = strdup_dst; \
+} while (/* CONSTCOND */ 0)
+
+#ifdef MKSH_SMALL
+#ifndef MKSH_NOPWNAM
+#define MKSH_NOPWNAM /* defined */
+#endif
+#ifndef MKSH_S_NOVI
+#define MKSH_S_NOVI 1
+#endif
+#endif
+
+#ifndef MKSH_S_NOVI
+#define MKSH_S_NOVI 0
+#endif
+
+#if defined(MKSH_NOPROSPECTOFWORK) && !defined(MKSH_UNEMPLOYED)
+#define MKSH_UNEMPLOYED 1
+#endif
+
+#define NUFILE 32 /* Number of user-accessible files */
+#define FDBASE 10 /* First file usable by Shell */
+
+/*
+ * 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
+
+#ifdef MKSH__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_common {
+ struct lalloc_common *next;
+};
+
+#ifdef MKSH_ALLOC_CATCH_UNDERRUNS
+struct lalloc_item {
+ struct lalloc_common *next;
+ size_t len;
+ char dummy[8192 - sizeof(struct lalloc_common *) - sizeof(size_t)];
+};
+#endif
+
+/* 2. sizes */
+#ifdef MKSH_ALLOC_CATCH_UNDERRUNS
+#define ALLOC_ITEM struct lalloc_item
+#define ALLOC_OVERHEAD 0
+#else
+#define ALLOC_ITEM struct lalloc_common
+#define ALLOC_OVERHEAD (sizeof(ALLOC_ITEM))
+#endif
+
+/* 3. group structure */
+typedef struct lalloc_common Area;
+
+
+EXTERN Area aperm; /* permanent object space */
+#define APERM &aperm
+#define ATEMP &e->area
+
+/*
+ * flags (the order of these enums MUST match the order in misc.c(options[]))
+ */
+enum sh_flag {
+#define SHFLAGS_ENUMS
+#include "sh_flags.gen"
+ FNFLAGS /* (place holder: how many flags are there) */
+};
+
+#define Flag(f) (shell_flags[(int)(f)])
+#define UTFMODE Flag(FUNNYCODE)
+
+/*
+ * parsing & execution environment
+ *
+ * note that kshlongjmp MUST NOT be passed 0 as second argument!
+ */
+#ifdef MKSH_NO_SIGSETJMP
+#define kshjmp_buf jmp_buf
+#define kshsetjmp(jbuf) _setjmp(jbuf)
+#define kshlongjmp _longjmp
+#else
+#define kshjmp_buf sigjmp_buf
+#define kshsetjmp(jbuf) sigsetjmp((jbuf), 0)
+#define kshlongjmp siglongjmp
+#endif
+
+struct sretrace_info;
+struct yyrecursive_state;
+
+EXTERN struct sretrace_info *retrace_info;
+EXTERN unsigned int subshell_nesting_type;
+
+extern struct env {
+ 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 */
+ short *savefd; /* original redirected fds */
+ struct temp *temps; /* temp files */
+ /* saved parser recursion state */
+ struct yyrecursive_state *yyrecursive_statep;
+ kshjmp_buf jbuf; /* long jump back to env creator */
+ uint8_t type; /* environment type - see below */
+ uint8_t flags; /* EF_* */
+} *e;
+
+/* struct env.type values */
+#define E_NONE 0 /* dummy environment */
+#define E_PARSE 1 /* parsing command # */
+#define E_FUNC 2 /* executing function # */
+#define E_INCL 3 /* including a file via . # */
+#define E_EXEC 4 /* executing command tree */
+#define E_LOOP 5 /* executing for/while # */
+#define E_ERRH 6 /* general error handler # */
+#define E_GONE 7 /* hidden in child */
+#define E_EVAL 8 /* running eval # */
+/* # indicates env has valid jbuf (see unwind()) */
+
+/* struct env.flag values */
+#define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */
+#define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */
+#define EF_IN_EVAL BIT(3) /* inside an eval */
+
+/* Do breaks/continues stop at env type e? */
+#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE || \
+ (t) == E_FUNC || (t) == E_INCL)
+/* Do returns stop at env type e? */
+#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL)
+
+/* values for kshlongjmp(e->jbuf, i) */
+/* note that i MUST NOT be zero */
+#define LRETURN 1 /* return statement */
+#define LEXIT 2 /* exit statement */
+#define LERROR 3 /* errorf() called */
+#define LERREXT 4 /* set -e caused */
+#define LINTR 5 /* ^C noticed */
+#define LBREAK 6 /* break statement */
+#define LCONTIN 7 /* continue statement */
+#define LSHELL 8 /* return to interactive shell() */
+#define LAEXPR 9 /* error in arithmetic expression */
+#define LLEAVE 10 /* untrappable exit/error */
+
+/* sort of shell global state */
+EXTERN pid_t procpid; /* PID of executing process */
+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 uint8_t baseline_flags[FNFLAGS
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+ + 1
+#endif
+ ];
+EXTERN bool as_builtin; /* direct builtin call */
+EXTERN const char *kshname; /* $0 */
+EXTERN struct {
+ uid_t kshuid_v; /* real UID of shell at startup */
+ uid_t ksheuid_v; /* effective UID of shell */
+ gid_t kshgid_v; /* real GID of shell at startup */
+ gid_t kshegid_v; /* effective GID of shell */
+ pid_t kshpgrp_v; /* process group of shell */
+ pid_t kshppid_v; /* PID of parent of shell */
+ 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 */
+#define OF_CMDLINE 0x01 /* command line */
+#define OF_SET 0x02 /* set builtin */
+#define OF_SPECIAL 0x04 /* a special variable changing */
+#define OF_INTERNAL 0x08 /* set internally by shell */
+#define OF_FIRSTTIME 0x10 /* as early as possible, once */
+#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL)
+
+/* null value for variable; comparison pointer for unset */
+EXTERN char null[] E_INIT("");
+
+/* string pooling: do we rely on the compiler? */
+#ifndef HAVE_STRING_POOLING
+/* no, we use our own, saves quite some space */
+#elif HAVE_STRING_POOLING == 2
+/* “on demand” */
+#ifdef __GNUC__
+/* only for GCC 4 or later, older ones can get by without */
+#if __GNUC__ < 4
+#undef HAVE_STRING_POOLING
+#endif
+#else
+/* not GCC, default to on */
+#endif
+#elif HAVE_STRING_POOLING == 0
+/* default to on, unless explicitly set to 0 */
+#undef HAVE_STRING_POOLING
+#endif
+
+#ifndef HAVE_STRING_POOLING /* helpers for pooled strings */
+EXTERN const char T4spaces[] E_INIT(" ");
+#define T1space (Treal_sp2 + 5)
+#define Tcolsp (Tf_sD_ + 2)
+#define TC_IFSWS (TinitIFS + 4)
+EXTERN const char TinitIFS[] E_INIT("IFS= \t\n");
+EXTERN const char TFCEDIT_dollaru[] E_INIT("${FCEDIT:-/bin/ed} $_");
+#define Tspdollaru (TFCEDIT_dollaru + 18)
+EXTERN const char Tsgdot[] E_INIT("*=.");
+EXTERN const char Taugo[] E_INIT("augo");
+EXTERN const char Tbracket[] E_INIT("[");
+#define Tdot (Tsgdot + 2)
+#define Talias (Tunalias + 2)
+EXTERN const char Tbadnum[] E_INIT("bad number");
+#define Tbadsubst (Tfg_badsubst + 10)
+EXTERN const char Tbg[] E_INIT("bg");
+EXTERN const char Tbad_bsize[] E_INIT("bad shf/buf/bsize");
+#define Tbsize (Tbad_bsize + 12)
+EXTERN const char Tbad_sig_ss[] E_INIT("%s: bad signal '%s'");
+#define Tbad_sig_s (Tbad_sig_ss + 4)
+EXTERN const char Tsgbreak[] E_INIT("*=break");
+#define Tbreak (Tsgbreak + 2)
+EXTERN const char T__builtin[] E_INIT("-\\builtin");
+#define T_builtin (T__builtin + 1)
+#define Tbuiltin (T__builtin + 2)
+EXTERN const char Toomem[] E_INIT("can't allocate %zu data bytes");
+EXTERN const char Tcant_cd[] E_INIT("restricted shell - can't cd");
+EXTERN const char Tcant_find[] E_INIT("can't find");
+EXTERN const char Tcant_open[] E_INIT("can't open");
+#define Tbytes (Toomem + 24)
+EXTERN const char Tbcat[] E_INIT("!cat");
+#define Tcat (Tbcat + 1)
+#define Tcd (Tcant_cd + 25)
+#define T_command (T_funny_command + 9)
+#define Tcommand (T_funny_command + 10)
+EXTERN const char Tsgcontinue[] E_INIT("*=continue");
+#define Tcontinue (Tsgcontinue + 2)
+EXTERN const char Tcreate[] E_INIT("create");
+EXTERN const char TELIF_unexpected[] E_INIT("TELIF unexpected");
+EXTERN const char TEXECSHELL[] E_INIT("EXECSHELL");
+EXTERN const char TENV[] E_INIT("ENV");
+EXTERN const char Tdsgexport[] E_INIT("^*=export");
+#define Texport (Tdsgexport + 3)
+#ifdef __OS2__
+EXTERN const char Textproc[] E_INIT("extproc");
+#endif
+EXTERN const char Tfalse[] E_INIT("false");
+EXTERN const char Tfg[] E_INIT("fg");
+EXTERN const char Tfg_badsubst[] E_INIT("fileglob: bad substitution");
+#define Tfile (Tfile_fd + 20)
+EXTERN const char Tfile_fd[] E_INIT("function definition file");
+EXTERN const char TFPATH[] E_INIT("FPATH");
+EXTERN const char T_function[] E_INIT(" function");
+#define Tfunction (T_function + 1)
+EXTERN const char T_funny_command[] E_INIT("funny $()-command");
+EXTERN const char Tgetopts[] E_INIT("getopts");
+#define Thistory (Tnot_in_history + 7)
+EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented");
+EXTERN const char Tinvname[] E_INIT("%s: invalid %s name");
+EXTERN const char Tjobs[] E_INIT("jobs");
+EXTERN const char Tjob_not_started[] E_INIT("job not started");
+EXTERN const char Tmksh[] E_INIT("mksh");
+#define Tname (Tinvname + 15)
+EXTERN const char Tno_args[] E_INIT("missing argument");
+EXTERN const char Tno_OLDPWD[] E_INIT("no OLDPWD");
+EXTERN const char Tnot_ident[] E_INIT("is not an identifier");
+EXTERN const char Tnot_in_history[] E_INIT("not in history");
+EXTERN const char Tnot_found_s[] E_INIT("%s not found");
+#define Tnot_found (Tnot_found_s + 3)
+#define Tnot_started (Tjob_not_started + 4)
+#define TOLDPWD (Tno_OLDPWD + 3)
+#define Topen (Tcant_open + 6)
+EXTERN const char To_o_reset[] E_INIT(" -o .reset");
+#define To_reset (To_o_reset + 4)
+#define TPATH (TFPATH + 1)
+#define Tpo (Tset_po + 4)
+#define Tpv (TpVv + 1)
+EXTERN const char TpVv[] E_INIT("Vpv");
+#define TPWD (Tno_OLDPWD + 6)
+#define Tread (Tshf_read + 4)
+EXTERN const char Tdsgreadonly[] E_INIT("^*=readonly");
+#define Treadonly (Tdsgreadonly + 3)
+EXTERN const char Tredirection_dup[] E_INIT("can't finish (dup) redirection");
+#define Tredirection (Tredirection_dup + 19)
+#define Treal_sp1 (Treal_sp2 + 1)
+EXTERN const char Treal_sp2[] E_INIT(" real ");
+EXTERN const char TREPLY[] E_INIT("REPLY");
+EXTERN const char Treq_arg[] E_INIT("requires an argument");
+EXTERN const char Tselect[] E_INIT("select");
+#define Tset (Tf_parm + 18)
+EXTERN const char Tset_po[] E_INIT("set +o");
+EXTERN const char Tsghset[] E_INIT("*=#set");
+#define Tsh (Tmksh + 2)
+#define TSHELL (TEXECSHELL + 4)
+#define Tshell (Ttoo_many_files + 23)
+EXTERN const char Tshf_read[] E_INIT("shf_read");
+EXTERN const char Tshf_write[] E_INIT("shf_write");
+EXTERN const char Tgsource[] E_INIT("=source");
+#define Tsource (Tgsource + 1)
+EXTERN const char Tj_suspend[] E_INIT("j_suspend");
+#define Tsuspend (Tj_suspend + 2)
+EXTERN const char Tsynerr[] E_INIT("syntax error");
+EXTERN const char Ttime[] E_INIT("time");
+EXTERN const char Ttoo_many_args[] E_INIT("too many arguments");
+EXTERN const char Ttoo_many_files[] E_INIT("too many open files in shell");
+EXTERN const char Ttrue[] E_INIT("true");
+EXTERN const char Ttty_fd_dupof[] E_INIT("dup of tty fd");
+#define Ttty_fd (Ttty_fd_dupof + 7)
+EXTERN const char Tdgtypeset[] E_INIT("^=typeset");
+#define Ttypeset (Tdgtypeset + 2)
+#define Tugo (Taugo + 1)
+EXTERN const char Tunalias[] E_INIT("unalias");
+#define Tunexpected (TELIF_unexpected + 6)
+EXTERN const char Tunexpected_type[] E_INIT("%s: unexpected %s type %d");
+EXTERN const char Tunknown_option[] E_INIT("unknown option");
+EXTERN const char Tunwind[] E_INIT("unwind");
+#define Tuser_sp1 (Tuser_sp2 + 1)
+EXTERN const char Tuser_sp2[] E_INIT(" user ");
+#define Twrite (Tshf_write + 4)
+EXTERN const char Tf__S[] E_INIT(" %S");
+#define Tf__d (Tunexpected_type + 22)
+#define Tf_ss (Tf__ss + 1)
+EXTERN const char Tf__ss[] E_INIT(" %s%s");
+#define Tf__sN (Tf_s_s_sN + 5)
+#define Tf_T (Tf_s_T + 3)
+EXTERN const char Tf_dN[] E_INIT("%d\n");
+EXTERN const char Tf_s_[] E_INIT("%s ");
+EXTERN const char Tf_s_T[] E_INIT("%s %T");
+EXTERN const char Tf_s_s_sN[] E_INIT("%s %s %s\n");
+#define Tf_s_s (Tf_sD_s_s + 4)
+#define Tf__s_s (Tf_sD_s_s + 3)
+#define Tf_s_sD_s (Tf_cant_ss_s + 6)
+EXTERN const char Tf_optfoo[] E_INIT("%s%s-%c: %s");
+EXTERN const char Tf_sD_[] E_INIT("%s: ");
+EXTERN const char Tf_szs[] E_INIT("%s: %zd %s");
+EXTERN const char Tf_parm[] E_INIT("%s: parameter not set");
+EXTERN const char Tf_coproc[] E_INIT("-p: %s");
+EXTERN const char Tf_cant_s[] E_INIT("%s: can't %s");
+EXTERN const char Tf_cant_ss_s[] E_INIT("can't %s %s: %s");
+EXTERN const char Tf_heredoc[] E_INIT("here document '%s' unclosed");
+#if HAVE_MKNOD
+EXTERN const char Tf_nonnum[] E_INIT("non-numeric %s %s '%s'");
+#endif
+EXTERN const char Tf_S_[] E_INIT("%S ");
+#define Tf_S (Tf__S + 1)
+#define Tf_lu (Tf_toolarge + 17)
+EXTERN const char Tf_toolarge[] E_INIT("%s %s too large: %lu");
+EXTERN const char Tf_ldfailed[] E_INIT("%s %s(%d, %ld) failed: %s");
+EXTERN const char Tf_sD_s_sD_s[] E_INIT("%s: %s %s: %s");
+EXTERN const char Tf_toomany[] E_INIT("too many %ss");
+EXTERN const char Tf_sd[] E_INIT("%s %d");
+#define Tf_s (Tf_temp + 28)
+EXTERN const char Tft_end[] E_INIT("%;");
+EXTERN const char Tft_R[] E_INIT("%R");
+#define Tf_d (Tunexpected_type + 23)
+EXTERN const char Tf_sD_s_qs[] E_INIT("%s: %s '%s'");
+EXTERN const char Tf_ro[] E_INIT("read-only: %s");
+EXTERN const char Tf_flags[] E_INIT("%s: flags 0x%X");
+EXTERN const char Tf_temp[] E_INIT("can't %s temporary file %s: %s");
+EXTERN const char Tf_ssfaileds[] E_INIT("%s: %s failed: %s");
+EXTERN const char Tf_sD_sD_s[] E_INIT("%s: %s: %s");
+EXTERN const char Tf__c_[] E_INIT("-%c ");
+EXTERN const char Tf_sD_s_s[] E_INIT("%s: %s %s");
+#define Tf_sN (Tf_s_s_sN + 6)
+#define Tf_sD_s (Tf_temp + 24)
+EXTERN const char T_devtty[] E_INIT("/dev/tty");
+#else /* helpers for string pooling */
+#define T4spaces " "
+#define T1space " "
+#define Tcolsp ": "
+#define TC_IFSWS " \t\n"
+#define TinitIFS "IFS= \t\n"
+#define TFCEDIT_dollaru "${FCEDIT:-/bin/ed} $_"
+#define Tspdollaru " $_"
+#define Tsgdot "*=."
+#define Taugo "augo"
+#define Tbracket "["
+#define Tdot "."
+#define Talias "alias"
+#define Tbadnum "bad number"
+#define Tbadsubst "bad substitution"
+#define Tbg "bg"
+#define Tbad_bsize "bad shf/buf/bsize"
+#define Tbsize "bsize"
+#define Tbad_sig_ss "%s: bad signal '%s'"
+#define Tbad_sig_s "bad signal '%s'"
+#define Tsgbreak "*=break"
+#define Tbreak "break"
+#define T__builtin "-\\builtin"
+#define T_builtin "\\builtin"
+#define Tbuiltin "builtin"
+#define Toomem "can't allocate %zu data bytes"
+#define Tcant_cd "restricted shell - can't cd"
+#define Tcant_find "can't find"
+#define Tcant_open "can't open"
+#define Tbytes "bytes"
+#define Tbcat "!cat"
+#define Tcat "cat"
+#define Tcd "cd"
+#define T_command "-command"
+#define Tcommand "command"
+#define Tsgcontinue "*=continue"
+#define Tcontinue "continue"
+#define Tcreate "create"
+#define TELIF_unexpected "TELIF unexpected"
+#define TEXECSHELL "EXECSHELL"
+#define TENV "ENV"
+#define Tdsgexport "^*=export"
+#define Texport "export"
+#ifdef __OS2__
+#define Textproc "extproc"
+#endif
+#define Tfalse "false"
+#define Tfg "fg"
+#define Tfg_badsubst "fileglob: bad substitution"
+#define Tfile "file"
+#define Tfile_fd "function definition file"
+#define TFPATH "FPATH"
+#define T_function " function"
+#define Tfunction "function"
+#define T_funny_command "funny $()-command"
+#define Tgetopts "getopts"
+#define Thistory "history"
+#define Tintovfl "integer overflow %zu %c %zu prevented"
+#define Tinvname "%s: invalid %s name"
+#define Tjobs "jobs"
+#define Tjob_not_started "job not started"
+#define Tmksh "mksh"
+#define Tname "name"
+#define Tno_args "missing argument"
+#define Tno_OLDPWD "no OLDPWD"
+#define Tnot_ident "is not an identifier"
+#define Tnot_in_history "not in history"
+#define Tnot_found_s "%s not found"
+#define Tnot_found "not found"
+#define Tnot_started "not started"
+#define TOLDPWD "OLDPWD"
+#define Topen "open"
+#define To_o_reset " -o .reset"
+#define To_reset ".reset"
+#define TPATH "PATH"
+#define Tpo "+o"
+#define Tpv "pv"
+#define TpVv "Vpv"
+#define TPWD "PWD"
+#define Tread "read"
+#define Tdsgreadonly "^*=readonly"
+#define Treadonly "readonly"
+#define Tredirection_dup "can't finish (dup) redirection"
+#define Tredirection "redirection"
+#define Treal_sp1 "real "
+#define Treal_sp2 " real "
+#define TREPLY "REPLY"
+#define Treq_arg "requires an argument"
+#define Tselect "select"
+#define Tset "set"
+#define Tset_po "set +o"
+#define Tsghset "*=#set"
+#define Tsh "sh"
+#define TSHELL "SHELL"
+#define Tshell "shell"
+#define Tshf_read "shf_read"
+#define Tshf_write "shf_write"
+#define Tgsource "=source"
+#define Tsource "source"
+#define Tj_suspend "j_suspend"
+#define Tsuspend "suspend"
+#define Tsynerr "syntax error"
+#define Ttime "time"
+#define Ttoo_many_args "too many arguments"
+#define Ttoo_many_files "too many open files in shell"
+#define Ttrue "true"
+#define Ttty_fd_dupof "dup of tty fd"
+#define Ttty_fd "tty fd"
+#define Tdgtypeset "^=typeset"
+#define Ttypeset "typeset"
+#define Tugo "ugo"
+#define Tunalias "unalias"
+#define Tunexpected "unexpected"
+#define Tunexpected_type "%s: unexpected %s type %d"
+#define Tunknown_option "unknown option"
+#define Tunwind "unwind"
+#define Tuser_sp1 "user "
+#define Tuser_sp2 " user "
+#define Twrite "write"
+#define Tf__S " %S"
+#define Tf__d " %d"
+#define Tf_ss "%s%s"
+#define Tf__ss " %s%s"
+#define Tf__sN " %s\n"
+#define Tf_T "%T"
+#define Tf_dN "%d\n"
+#define Tf_s_ "%s "
+#define Tf_s_T "%s %T"
+#define Tf_s_s_sN "%s %s %s\n"
+#define Tf_s_s "%s %s"
+#define Tf__s_s " %s %s"
+#define Tf_s_sD_s "%s %s: %s"
+#define Tf_optfoo "%s%s-%c: %s"
+#define Tf_sD_ "%s: "
+#define Tf_szs "%s: %zd %s"
+#define Tf_parm "%s: parameter not set"
+#define Tf_coproc "-p: %s"
+#define Tf_cant_s "%s: can't %s"
+#define Tf_cant_ss_s "can't %s %s: %s"
+#define Tf_heredoc "here document '%s' unclosed"
+#if HAVE_MKNOD
+#define Tf_nonnum "non-numeric %s %s '%s'"
+#endif
+#define Tf_S_ "%S "
+#define Tf_S "%S"
+#define Tf_lu "%lu"
+#define Tf_toolarge "%s %s too large: %lu"
+#define Tf_ldfailed "%s %s(%d, %ld) failed: %s"
+#define Tf_sD_s_sD_s "%s: %s %s: %s"
+#define Tf_toomany "too many %ss"
+#define Tf_sd "%s %d"
+#define Tf_s "%s"
+#define Tft_end "%;"
+#define Tft_R "%R"
+#define Tf_d "%d"
+#define Tf_sD_s_qs "%s: %s '%s'"
+#define Tf_ro "read-only: %s"
+#define Tf_flags "%s: flags 0x%X"
+#define Tf_temp "can't %s temporary file %s: %s"
+#define Tf_ssfaileds "%s: %s failed: %s"
+#define Tf_sD_sD_s "%s: %s: %s"
+#define Tf__c_ "-%c "
+#define Tf_sD_s_s "%s: %s %s"
+#define Tf_sN "%s\n"
+#define Tf_sD_s "%s: %s"
+#define T_devtty "/dev/tty"
+#endif /* end of string pooling */
+
+typedef uint8_t Temp_type;
+/* expanded heredoc */
+#define TT_HEREDOC_EXP 0
+/* temporary file used for history editing (fc -e) */
+#define TT_HIST_EDIT 1
+/* temporary file used during in-situ command substitution */
+#define TT_FUNSUB 2
+
+/* temp/heredoc files. The file is removed when the struct is freed. */
+struct temp {
+ struct temp *next;
+ struct shf *shf;
+ /* pid of process parsed here-doc */
+ pid_t pid;
+ Temp_type type;
+ /* actually longer: name (variable length) */
+ char tffn[3];
+};
+
+/*
+ * stdio and our IO routines
+ */
+
+#define shl_xtrace (&shf_iob[0]) /* for set -x */
+#define shl_stdout (&shf_iob[1])
+#define shl_out (&shf_iob[2])
+#ifdef DF
+#define shl_dbg (&shf_iob[3]) /* for DF() */
+#endif
+EXTERN bool shl_stdout_ok;
+
+/*
+ * trap handlers
+ */
+typedef struct trap {
+ const char *name; /* short name */
+ const char *mess; /* descriptive name */
+ char *trap; /* trap command */
+ sig_t cursig; /* current handler (valid if TF_ORIG_* set) */
+ sig_t shtrap; /* shell signal handler */
+ int signal; /* signal number */
+ int flags; /* TF_* */
+ volatile sig_atomic_t set; /* trap pending */
+} Trap;
+
+/* values for Trap.flags */
+#define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */
+#define TF_USER_SET BIT(1) /* user has (tried to) set trap */
+#define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */
+#define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */
+#define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */
+#define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */
+#define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */
+#define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */
+#define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */
+#define TF_FATAL BIT(9) /* causes termination if not trapped */
+
+/* values for setsig()/setexecsig() flags argument */
+#define SS_RESTORE_MASK 0x3 /* how to restore a signal before an exec() */
+#define SS_RESTORE_CURR 0 /* leave current handler in place */
+#define SS_RESTORE_ORIG 1 /* restore original handler */
+#define SS_RESTORE_DFL 2 /* restore to SIG_DFL */
+#define SS_RESTORE_IGN 3 /* restore to SIG_IGN */
+#define SS_FORCE BIT(3) /* set signal even if original signal ignored */
+#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 ksh_SIGEXIT 0 /* for trap EXIT */
+#define ksh_SIGERR ksh_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 Trap sigtraps[ksh_NSIG + 1];
+
+/* got_winch = 1 when we need to re-adjust the window size */
+#ifdef SIGWINCH
+EXTERN volatile sig_atomic_t got_winch E_INIT(1);
+#else
+#define got_winch true
+#endif
+
+/*
+ * TMOUT support
+ */
+/* values for ksh_tmout_state */
+enum tmout_enum {
+ TMOUT_EXECUTING = 0, /* executing commands */
+ TMOUT_READING, /* waiting for input */
+ TMOUT_LEAVING /* have timed out */
+};
+EXTERN unsigned int ksh_tmout;
+EXTERN enum tmout_enum ksh_tmout_state;
+
+/* For "You have stopped jobs" message */
+EXTERN bool really_exit;
+
+/*
+ * fast character classes
+ */
+
+/* internal types, do not reference */
+
+/* initially empty — filled at runtime from $IFS */
+#define CiIFS BIT(0)
+#define CiCNTRL BIT(1) /* \x01‥\x08\x0E‥\x1F\x7F */
+#define CiUPPER BIT(2) /* A‥Z */
+#define CiLOWER BIT(3) /* a‥z */
+#define CiHEXLT BIT(4) /* A‥Fa‥f */
+#define CiOCTAL BIT(5) /* 0‥7 */
+#define CiQCL BIT(6) /* &();| */
+#define CiALIAS BIT(7) /* !,.@ */
+#define CiQCX BIT(8) /* *[\\ */
+#define CiVAR1 BIT(9) /* !*@ */
+#define CiQCM BIT(10) /* /^~ */
+#define CiDIGIT BIT(11) /* 89 */
+#define CiQC BIT(12) /* "' */
+#define CiSPX BIT(13) /* \x0B\x0C */
+#define CiCURLY BIT(14) /* {} */
+#define CiANGLE BIT(15) /* <> */
+#define CiNUL BIT(16) /* \x00 */
+#define CiTAB BIT(17) /* \x09 */
+#define CiNL BIT(18) /* \x0A */
+#define CiCR BIT(19) /* \x0D */
+#define CiSP BIT(20) /* \x20 */
+#define CiHASH BIT(21) /* # */
+#define CiSS BIT(22) /* $ */
+#define CiPERCT BIT(23) /* % */
+#define CiPLUS BIT(24) /* + */
+#define CiMINUS BIT(25) /* - */
+#define CiCOLON BIT(26) /* : */
+#define CiEQUAL BIT(27) /* = */
+#define CiQUEST BIT(28) /* ? */
+#define CiBRACK BIT(29) /* [] */
+#define CiUNDER BIT(30) /* _ */
+#define CiGRAVE BIT(31) /* ` */
+/* out of space, but one for *@ would make sense, possibly others */
+
+/* compile-time initialised, ASCII only */
+extern const uint32_t tpl_ctypes[128];
+/* run-time, contains C_IFS as well, full 2⸠octet range */
+EXTERN uint32_t ksh_ctypes[256];
+/* first octet of $IFS, for concatenating "$*" */
+EXTERN char ifs0;
+
+/* external types */
+
+/* !%+,-.0‥9:@A‥Z[]_a‥z valid characters in alias names */
+#define C_ALIAS (CiALIAS | CiBRACK | CiCOLON | CiDIGIT | CiLOWER | CiMINUS | CiOCTAL | CiPERCT | CiPLUS | CiUNDER | CiUPPER)
+/* 0‥9A‥Za‥z alphanumerical */
+#define C_ALNUM (CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
+/* 0‥9A‥Z_a‥z alphanumerical plus underscore (“word character”) */
+#define C_ALNUX (CiDIGIT | CiLOWER | CiOCTAL | CiUNDER | CiUPPER)
+/* A‥Za‥z alphabetical (upper plus lower) */
+#define C_ALPHA (CiLOWER | CiUPPER)
+/* A‥Z_a‥z alphabetical plus underscore (identifier lead) */
+#define C_ALPHX (CiLOWER | CiUNDER | CiUPPER)
+/* \x01‥\x7F 7-bit ASCII except NUL */
+#define C_ASCII (CiALIAS | CiANGLE | CiBRACK | CiCNTRL | CiCOLON | CiCR | CiCURLY | CiDIGIT | CiEQUAL | CiGRAVE | CiHASH | CiLOWER | CiMINUS | CiNL | CiOCTAL | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSP | CiSPX | CiSS | CiTAB | CiUNDER | CiUPPER)
+/* \x09\x20 tab and space */
+#define C_BLANK (CiSP | CiTAB)
+/* \x09\x20"' separator for completion */
+#define C_CFS (CiQC | CiSP | CiTAB)
+/* \x00‥\x1F\x7F POSIX control characters */
+#define C_CNTRL (CiCNTRL | CiCR | CiNL | CiNUL | CiSPX | CiTAB)
+/* 0‥9 decimal digits */
+#define C_DIGIT (CiDIGIT | CiOCTAL)
+/* &();`| editor x_locate_word() command */
+#define C_EDCMD (CiGRAVE | CiQCL)
+/* \x09\x0A\x20"&'():;<=>`| editor non-word characters */
+#define C_EDNWC (CiANGLE | CiCOLON | CiEQUAL | CiGRAVE | CiNL | CiQC | CiQCL | CiSP | CiTAB)
+/* "#$&'()*:;<=>?[\\`{|} editor quotes for tab completion */
+#define C_EDQ (CiANGLE | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiQC | CiQCL | CiQCX | CiQUEST | CiSS)
+/* !‥~ POSIX graphical (alphanumerical plus punctuation) */
+#define C_GRAPH (C_PUNCT | CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
+/* A‥Fa‥f hex letter */
+#define C_HEXLT CiHEXLT
+/* \x00 + $IFS IFS whitespace, IFS non-whitespace, NUL */
+#define C_IFS (CiIFS | CiNUL)
+/* \x09\x0A\x20 IFS whitespace */
+#define C_IFSWS (CiNL | CiSP | CiTAB)
+/* \x09\x0A\x20&();<>| (for the lexer) */
+#define C_LEX1 (CiANGLE | CiNL | CiQCL | CiSP | CiTAB)
+/* a‥z lowercase letters */
+#define C_LOWER CiLOWER
+/* not alnux or dollar separator for motion */
+#define C_MFS (CiALIAS | CiANGLE | CiBRACK | CiCNTRL | CiCOLON | CiCR | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiMINUS | CiNL | CiNUL | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSP | CiSPX | CiTAB)
+/* 0‥7 octal digit */
+#define C_OCTAL CiOCTAL
+/* !*+?@ pattern magical operator, except space */
+#define C_PATMO (CiPLUS | CiQUEST | CiVAR1)
+/* \x20‥~ POSIX printable characters (graph plus space) */
+#define C_PRINT (C_GRAPH | CiSP)
+/* !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ POSIX punctuation */
+#define C_PUNCT (CiALIAS | CiANGLE | CiBRACK | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiMINUS | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSS | CiUNDER)
+/* \x09\x0A"#$&'()*;<=>?[\\]`| characters requiring quoting, minus space */
+#define C_QUOTE (CiANGLE | CiBRACK | CiEQUAL | CiGRAVE | CiHASH | CiNL | CiQC | CiQCL | CiQCX | CiQUEST | CiSS | CiTAB)
+/* 0‥9A‥Fa‥f hexadecimal digit */
+#define C_SEDEC (CiDIGIT | CiHEXLT | CiOCTAL)
+/* \x09‥\x0D\x20 POSIX space class */
+#define C_SPACE (CiCR | CiNL | CiSP | CiSPX | CiTAB)
+/* +-=? substitution operations with word */
+#define C_SUB1 (CiEQUAL | CiMINUS | CiPLUS | CiQUEST)
+/* #% substitution operations with pattern */
+#define C_SUB2 (CiHASH | CiPERCT)
+/* A‥Z uppercase letters */
+#define C_UPPER CiUPPER
+/* !#$*-?@ substitution parameters, other than positional */
+#define C_VAR1 (CiHASH | CiMINUS | CiQUEST | CiSS | CiVAR1)
+
+/* individual chars you might like */
+#define C_ANGLE CiANGLE /* <> angle brackets */
+#define C_COLON CiCOLON /* : colon */
+#define C_CR CiCR /* \x0D ASCII carriage return */
+#define C_DOLAR CiSS /* $ dollar sign */
+#define C_EQUAL CiEQUAL /* = equals sign */
+#define C_GRAVE CiGRAVE /* ` accent gravis */
+#define C_HASH CiHASH /* # hash sign */
+#define C_LF CiNL /* \x0A ASCII line feed */
+#define C_MINUS CiMINUS /* - hyphen-minus */
+#ifdef MKSH_WITH_TEXTMODE
+#define C_NL (CiNL | CiCR) /* CR or LF under OS/2 TEXTMODE */
+#else
+#define C_NL CiNL /* LF only like under Unix */
+#endif
+#define C_NUL CiNUL /* \x00 ASCII NUL */
+#define C_PLUS CiPLUS /* + plus sign */
+#define C_QC CiQC /* "' quote characters */
+#define C_QUEST CiQUEST /* ? question mark */
+#define C_SPC CiSP /* \x20 ASCII space */
+#define C_TAB CiTAB /* \x09 ASCII horizontal tabulator */
+#define C_UNDER CiUNDER /* _ underscore */
+
+/* identity transform of octet */
+#if defined(DEBUG) && defined(__GNUC__) && !defined(__ICC) && \
+ !defined(__INTEL_COMPILER) && !defined(__SUNPRO_C)
+extern unsigned int eek_ord;
+#define ORD(c) ((size_t)(c) > 0xFF ? eek_ord : \
+ ((unsigned int)(unsigned char)(c)))
+#define ord(c) __builtin_choose_expr( \
+ __builtin_types_compatible_p(__typeof__(c), char) || \
+ __builtin_types_compatible_p(__typeof__(c), unsigned char), \
+ ((unsigned int)(unsigned char)(c)), ({ \
+ size_t ord_c = (c); \
+ \
+ if (ord_c > (size_t)0xFFU) \
+ internal_errorf("%s:%d:ord(%zX)", \
+ __FILE__, __LINE__, ord_c); \
+ ((unsigned int)(unsigned char)(ord_c)); \
+}))
+#else
+#define ord(c) ((unsigned int)(unsigned char)(c))
+#define ORD(c) ord(c) /* may evaluate arguments twice */
+#endif
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+EXTERN unsigned short ebcdic_map[256];
+EXTERN unsigned char ebcdic_rtt_toascii[256];
+EXTERN unsigned char ebcdic_rtt_fromascii[256];
+extern void ebcdic_init(void);
+/* one-way to-ascii-or-high conversion, for POSIX locale ordering */
+#define asciibetical(c) ((unsigned int)ebcdic_map[(unsigned char)(c)])
+/* two-way round-trip conversion, for general use */
+#define rtt2asc(c) ebcdic_rtt_toascii[(unsigned char)(c)]
+#define asc2rtt(c) ebcdic_rtt_fromascii[(unsigned char)(c)]
+/* case-independent char comparison */
+#define ksh_eq(c,u,l) (ord(c) == ord(u) || ord(c) == ord(l))
+#else
+#define asciibetical(c) ord(c)
+#define rtt2asc(c) ((unsigned char)(c))
+#define asc2rtt(c) ((unsigned char)(c))
+#define ksh_eq(c,u,l) ((ord(c) | 0x20) == ord(l))
+#endif
+/* control character foo */
+#ifdef MKSH_EBCDIC
+#define ksh_isctrl(c) (ord(c) < 0x40 || ord(c) == 0xFF)
+#else
+#define ksh_isctrl(c) ((ord(c) & 0x7F) < 0x20 || ord(c) == 0x7F)
+#endif
+/* new fast character classes */
+#define ctype(c,t) tobool(ksh_ctypes[ord(c)] & (t))
+#define cinttype(c,t) ((c) >= 0 && (c) <= 0xFF ? \
+ tobool(ksh_ctypes[(unsigned char)(c)] & (t)) : false)
+/* helper functions */
+#define ksh_isdash(s) tobool(ord((s)[0]) == '-' && ord((s)[1]) == '\0')
+/* invariant distance even in EBCDIC */
+#define ksh_tolower(c) (ctype(c, C_UPPER) ? (c) - 'A' + 'a' : (c))
+#define ksh_toupper(c) (ctype(c, C_LOWER) ? (c) - 'a' + 'A' : (c))
+/* strictly speaking rtt2asc() here, but this works even in EBCDIC */
+#define ksh_numdig(c) (ord(c) - ORD('0'))
+#define ksh_numuc(c) (rtt2asc(c) - rtt2asc('A'))
+#define ksh_numlc(c) (rtt2asc(c) - rtt2asc('a'))
+#define ksh_toctrl(c) asc2rtt(ord(c) == ORD('?') ? 0x7F : rtt2asc(c) & 0x9F)
+#define ksh_unctrl(c) asc2rtt(rtt2asc(c) ^ 0x40U)
+
+#ifdef MKSH_SMALL
+#define SMALLP(x) /* nothing */
+#else
+#define SMALLP(x) , x
+#endif
+
+/* Argument parsing for built-in commands and getopts command */
+
+/* Values for Getopt.flags */
+#define GF_ERROR BIT(0) /* call errorf() if there is an error */
+#define GF_PLUSOPT BIT(1) /* allow +c as an option */
+#define GF_NONAME BIT(2) /* don't print argv[0] in errors */
+
+/* Values for Getopt.info */
+#define GI_MINUS BIT(0) /* an option started with -... */
+#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 */
+} Getopt;
+
+EXTERN Getopt builtin_opt; /* for shell builtin commands */
+EXTERN Getopt user_opt; /* parsing state for getopts builtin command */
+
+/* This for co-processes */
+
+/* something that won't (realisticly) wrap */
+typedef int Coproc_id;
+
+struct coproc {
+ void *job; /* 0 or job of co-process using input pipe */
+ int read; /* pipe from co-process's stdout */
+ int readw; /* other side of read (saved temporarily) */
+ int write; /* pipe to co-process's stdin */
+ int njobs; /* number of live jobs using output pipe */
+ Coproc_id id; /* id of current output pipe */
+};
+EXTERN struct coproc coproc;
+
+#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;
+/* is called builtin a POSIX special builtin? (error functions only) */
+EXTERN bool builtin_spec;
+
+/* current working directory */
+EXTERN char *current_wd;
+
+/* input line size */
+#ifdef MKSH_SMALL
+#define LINE (4096 - ALLOC_OVERHEAD)
+#else
+#define LINE (16384 - ALLOC_OVERHEAD)
+#endif
+/* columns and lines of the tty */
+EXTERN mksh_ari_t x_cols E_INIT(80);
+EXTERN mksh_ari_t x_lins E_INIT(24);
+
+
+/* Determine the location of the system (common) profile */
+
+#ifndef MKSH_DEFAULT_PROFILEDIR
+#define MKSH_DEFAULT_PROFILEDIR MKSH_UNIXROOT "/etc"
+#endif
+
+#define MKSH_SYSTEM_PROFILE MKSH_DEFAULT_PROFILEDIR "/profile"
+#define MKSH_SUID_PROFILE MKSH_DEFAULT_PROFILEDIR "/suid_profile"
+
+
+/* Used by v_evaluate() and setstr() to control action when error occurs */
+#define KSH_UNWIND_ERROR 0 /* unwind the stack (kshlongjmp) */
+#define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */
+
+/*
+ * Shell file I/O routines
+ */
+
+#define SHF_BSIZE 512
+
+#define shf_fileno(shf) ((shf)->fd)
+#define shf_setfileno(shf,nfd) ((shf)->fd = (nfd))
+#define shf_getc_i(shf) ((shf)->rnleft > 0 ? \
+ (shf)->rnleft--, (int)ord(*(shf)->rp++) : \
+ shf_getchar(shf))
+#define shf_putc_i(c,shf) ((shf)->wnleft == 0 ? \
+ shf_putchar((uint8_t)(c), (shf)) : \
+ ((shf)->wnleft--, *(shf)->wp++ = (c)))
+#define shf_eof(shf) ((shf)->flags & SHF_EOF)
+#define shf_error(shf) ((shf)->flags & SHF_ERROR)
+#define shf_errno(shf) ((shf)->errnosv)
+#define shf_clearerr(shf) ((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
+
+/* Flags passed to shf_*open() */
+#define SHF_RD 0x0001
+#define SHF_WR 0x0002
+#define SHF_RDWR (SHF_RD | SHF_WR)
+#define SHF_ACCMODE 0x0003 /* mask */
+#define SHF_GETFL 0x0004 /* use fcntl() to figure RD/WR flags */
+#define SHF_UNBUF 0x0008 /* unbuffered I/O */
+#define SHF_CLEXEC 0x0010 /* set close on exec flag */
+#define SHF_MAPHI 0x0020 /* make fd > FDBASE (and close orig)
+ * (shf_open() only) */
+#define SHF_DYNAMIC 0x0040 /* string: increase buffer as needed */
+#define SHF_INTERRUPT 0x0080 /* EINTR in read/write causes error */
+/* Flags used internally */
+#define SHF_STRING 0x0100 /* a string, not a file */
+#define SHF_ALLOCS 0x0200 /* shf and shf->buf were alloc()ed */
+#define SHF_ALLOCB 0x0400 /* shf->buf was alloc()ed */
+#define SHF_ERROR 0x0800 /* read()/write() error */
+#define SHF_EOF 0x1000 /* read eof (sticky) */
+#define SHF_READING 0x2000 /* currently reading: rnleft,rp valid */
+#define SHF_WRITING 0x4000 /* currently writing: wnleft,wp valid */
+
+
+struct shf {
+ Area *areap; /* area shf/buf were allocated in */
+ 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 fd; /* file descriptor */
+ int errnosv; /* saved value of errno after error */
+};
+
+extern struct shf shf_iob[];
+
+struct table {
+ Area *areap; /* area to allocate entries */
+ struct tbl **tbls; /* hashed table items */
+ size_t nfree; /* free table entries */
+ uint8_t tshift; /* table size (2^tshift) */
+};
+
+/* 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 **); /* 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 errnov; /* CEXEC/CTALIAS */
+ } u2;
+ union {
+ uint32_t hval; /* hash(name) */
+ uint32_t index; /* index for an array */
+ } ua;
+ /*
+ * 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];
+};
+
+EXTERN struct tbl *vtemp;
+/* set by isglobal(), global() and local() */
+EXTERN bool last_lookup_was_array;
+
+/* common flag bits */
+#define ALLOC BIT(0) /* val.s has been allocated */
+#define DEFINED BIT(1) /* is defined in block */
+#define ISSET BIT(2) /* has value, vp->val.[si] */
+#define EXPORT BIT(3) /* exported variable/function */
+#define TRACE BIT(4) /* var: user flagged, func: execution tracing */
+/* (start non-common flags at 8) */
+/* flag bits used for variables */
+#define SPECIAL BIT(8) /* PATH, IFS, SECONDS, etc */
+#define INTEGER BIT(9) /* val.i contains integer value */
+#define RDONLY BIT(10) /* read-only variable */
+#define LOCAL BIT(11) /* for local typeset() */
+#define ARRAY BIT(13) /* array */
+#define LJUST BIT(14) /* left justify */
+#define RJUST BIT(15) /* right justify */
+#define ZEROFIL BIT(16) /* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */
+#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 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 */
+#define EXPRLVALUE BIT(24) /* useable as lvalue (temp flag) */
+#define AINDEX BIT(25) /* array index >0 = ua.index filled in */
+#define ASSOC BIT(26) /* ARRAY ? associative : reference */
+/* flag bits used for taliases/builtins/aliases/keywords/functions */
+#define KEEPASN BIT(8) /* keep command assignments (eg, var=x cmd) */
+#define FINUSE BIT(9) /* function being executed */
+#define FDELETE BIT(10) /* function deleted while it was executing */
+#define FKSH BIT(11) /* function defined with function x (vs x()) */
+#define SPEC_BI BIT(12) /* a POSIX special builtin */
+#define LOWER_BI BIT(13) /* (with LOW_BI) override even w/o flags */
+#define LOW_BI BIT(14) /* external utility overrides built-in one */
+#define DECL_UTIL BIT(15) /* is declaration utility */
+#define DECL_FWDR BIT(16) /* is declaration utility forwarder */
+#define NEXTLOC_BI BIT(17) /* needs BF_RESETSPEC on e->loc */
+
+/*
+ * Attributes that can be set by the user (used to decide if an unset
+ * param should be repoted by set/typeset). Does not include ARRAY or
+ * LOCAL.
+ */
+#define USERATTRIB (EXPORT | INTEGER | RDONLY | LJUST | RJUST | ZEROFIL | \
+ LCASEV | UCASEV_AL | INT_U | INT_L)
+
+#define arrayindex(vp) ((unsigned long)((vp)->flag & AINDEX ? \
+ (vp)->ua.index : 0))
+
+enum namerefflag {
+ SRF_NOP,
+ SRF_ENABLE,
+ SRF_DISABLE
+};
+
+/* command types */
+#define CNONE 0 /* undefined */
+#define CSHELL 1 /* built-in */
+#define CFUNC 2 /* function */
+#define CEXEC 4 /* executable command */
+#define CALIAS 5 /* alias */
+#define CKEYWD 6 /* keyword */
+#define CTALIAS 7 /* tracked alias */
+
+/* Flags for findcom()/comexec() */
+#define FC_SPECBI BIT(0) /* special builtin */
+#define FC_FUNC BIT(1) /* function */
+#define FC_NORMBI BIT(2) /* not special builtin */
+#define FC_BI (FC_SPECBI | FC_NORMBI)
+#define FC_PATH BIT(3) /* do path search */
+#define FC_DEFPATH BIT(4) /* use default path in path search */
+#define FC_WHENCE BIT(5) /* for use by command and whence */
+
+#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).ai_argc - (a).skip)
+
+/* Argument info. Used for $#, $* for shell, functions, includes, etc. */
+struct arg_info {
+ const char **argv;
+ int flags; /* AF_* */
+ int ai_argc;
+ int skip; /* first arg is argv[0], second is argv[1 + skip] */
+};
+
+/*
+ * activation record for function blocks
+ */
+struct block {
+ Area area; /* area to allocate things */
+ const char **argv;
+ char *error; /* error handler */
+ char *exit; /* exit handler */
+ struct block *next; /* enclosing block */
+ struct table vars; /* local variables */
+ struct table funs; /* local functions */
+ Getopt getopts_state;
+ int argc;
+ int flags; /* see BF_* */
+};
+
+/* Values for struct block.flags */
+#define BF_DOGETOPTS BIT(0) /* save/restore getopts state */
+#define BF_STOPENV BIT(1) /* do not export further */
+/* BF_RESETSPEC and NEXTLOC_BI must be numerically identical! */
+#define BF_RESETSPEC BIT(17) /* use ->next for set and shift */
+
+/*
+ * Used by ktwalk() and ktnext() routines.
+ */
+struct tstate {
+ struct tbl **next;
+ ssize_t left;
+};
+
+EXTERN struct table taliases; /* tracked aliases */
+EXTERN struct table builtins; /* built-in commands */
+EXTERN struct table aliases; /* aliases */
+EXTERN struct table keywords; /* keywords */
+#ifndef MKSH_NOPWNAM
+EXTERN struct table homedirs; /* homedir() cache */
+#endif
+
+struct builtin {
+ const char *name;
+ int (*func)(const char **);
+};
+
+extern const struct builtin mkshbuiltins[];
+
+/* values for set_prompt() */
+#define PS1 0 /* command */
+#define PS2 1 /* command continuation */
+
+EXTERN char *path; /* copy of either PATH or def_path */
+EXTERN const char *def_path; /* path to use if PATH not set */
+EXTERN char *tmpdir; /* TMPDIR value */
+EXTERN const char *prompt;
+EXTERN uint8_t cur_prompt; /* PS1 or PS2 */
+EXTERN int current_lineno; /* LINENO value */
+
+/*
+ * Description of a command or an operation on commands.
+ */
+struct op {
+ const char **args; /* arguments to a command */
+ char **vars; /* variable assignments */
+ struct ioword **ioact; /* IO actions (eg, < > >>) */
+ struct op *left, *right; /* descendents */
+ char *str; /* word for case; identifier for for,
+ * select, and functions;
+ * path to execute for TEXEC;
+ * time hook for TCOM.
+ */
+ int lineno; /* TCOM/TFUNC: LINENO for this */
+ short type; /* operation type, see below */
+ /* 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;
+};
+
+/* Tree.type values */
+#define TEOF 0
+#define TCOM 1 /* command */
+#define TPAREN 2 /* (c-list) */
+#define TPIPE 3 /* a | b */
+#define TLIST 4 /* a ; b */
+#define TOR 5 /* || */
+#define TAND 6 /* && */
+#define TBANG 7 /* ! */
+#define TDBRACKET 8 /* [[ .. ]] */
+#define TFOR 9
+#define TSELECT 10
+#define TCASE 11
+#define TIF 12
+#define TWHILE 13
+#define TUNTIL 14
+#define TELIF 15
+#define TPAT 16 /* pattern in case */
+#define TBRACE 17 /* {c-list} */
+#define TASYNC 18 /* c & */
+#define TFUNCT 19 /* function name { command; } */
+#define TTIME 20 /* time pipeline */
+#define TEXEC 21 /* fork/exec eval'd TCOM */
+#define TCOPROC 22 /* coprocess |& */
+
+/*
+ * prefix codes for words in command tree
+ */
+#define EOS 0 /* end of string */
+#define CHAR 1 /* unquoted character */
+#define QCHAR 2 /* quoted character */
+#define COMSUB 3 /* $() substitution (0 terminated) */
+#define EXPRSUB 4 /* $(()) substitution (0 terminated) */
+#define OQUOTE 5 /* opening " or ' */
+#define CQUOTE 6 /* closing " or ' */
+#define OSUBST 7 /* opening ${ subst (followed by { or X) */
+#define CSUBST 8 /* closing } of above (followed by } or X) */
+#define OPAT 9 /* open pattern: *(, @(, etc. */
+#define SPAT 10 /* separate pattern: | */
+#define CPAT 11 /* close pattern: ) */
+#define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */
+#define FUNSUB 14 /* ${ foo;} substitution (NUL terminated) */
+#define VALSUB 15 /* ${|foo;} substitution (NUL terminated) */
+#define COMASUB 16 /* `…` substitution (COMSUB but expand aliases) */
+#define FUNASUB 17 /* function substitution but expand aliases */
+
+/*
+ * IO redirection
+ */
+struct ioword {
+ char *ioname; /* filename (unused if heredoc) */
+ char *delim; /* delimiter for <<, <<- */
+ char *heredoc; /* content of heredoc */
+ unsigned short ioflag; /* action (below) */
+ short unit; /* unit (fd) affected */
+};
+
+/* 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 IODUPSELF BIT(8) /* x>&x (as opposed to x>&y) */
+#define IONAMEXP BIT(9) /* name has been expanded */
+#define IOBASH BIT(10) /* &> etc. */
+#define IOHERESTR BIT(11) /* <<< (here string) */
+#define IONDELIM BIT(12) /* null delimiter (<<) */
+
+/* execute/exchild flags */
+#define XEXEC BIT(0) /* execute without forking */
+#define XFORK BIT(1) /* fork before executing */
+#define XBGND BIT(2) /* command & */
+#define XPIPEI BIT(3) /* input is pipe */
+#define XPIPEO BIT(4) /* output is pipe */
+#define XXCOM BIT(5) /* `...` command */
+#define XPCLOSE BIT(6) /* exchild: close close_fd in parent */
+#define XCCLOSE BIT(7) /* exchild: close close_fd in child */
+#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
+ * in a short)
+ */
+#define DOBLANK BIT(0) /* perform blank interpretation */
+#define DOGLOB BIT(1) /* expand [?* */
+#define DOPAT BIT(2) /* quote *?[ */
+#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) /* dito: in word part of ${..[%#=?]..} */
+#define DOVACHECK BIT(9) /* var assign check (for typeset, set, etc) */
+#define DOMARKDIRS BIT(10) /* force markdirs behaviour */
+#define DOTCOMEXEC BIT(11) /* not an eval flag, used by sh -c hack */
+#define DOSCALAR BIT(12) /* change field handling to non-list context */
+#define DOHEREDOC BIT(13) /* change scalar handling to heredoc body */
+#define DOHERESTR BIT(14) /* append a newline char */
+#define DODBMAGIC BIT(15) /* add magic to expansions for [[ x = $y ]] */
+
+#define X_EXTRA 20 /* this many extra bytes in X string */
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define X_WASTE 15 /* allowed extra bytes to avoid shrinking, */
+#else
+#define X_WASTE 255 /* … must be 2âż-1 */
+#endif
+
+typedef struct XString {
+ /* beginning of string */
+ char *beg;
+ /* length of allocated area, minus safety margin */
+ size_t len;
+ /* end of string */
+ char *end;
+ /* memory area used */
+ Area *areap;
+} XString;
+
+/* initialise expandable string */
+#define XinitN(xs,length,area) do { \
+ (xs).len = (length); \
+ (xs).areap = (area); \
+ (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \
+ (xs).end = (xs).beg + (xs).len; \
+} while (/* CONSTCOND */ 0)
+#define Xinit(xs,xp,length,area) do { \
+ XinitN((xs), (length), (area)); \
+ (xp) = (xs).beg; \
+} while (/* CONSTCOND */ 0)
+
+/* stuff char into string */
+#define Xput(xs,xp,c) (*xp++ = (c))
+
+/* check if there are at least n bytes left */
+#define XcheckN(xs,xp,n) do { \
+ ssize_t more = ((xp) + (n)) - (xs).end; \
+ if (more > 0) \
+ (xp) = Xcheck_grow(&(xs), (xp), (size_t)more); \
+} while (/* CONSTCOND */ 0)
+
+/* check for overflow, expand string */
+#define Xcheck(xs,xp) XcheckN((xs), (xp), 1)
+
+/* free string */
+#define Xfree(xs,xp) afree((xs).beg, (xs).areap)
+
+/* close, return string */
+#define Xclose(xs,xp) aresize((xs).beg, (xp) - (xs).beg, (xs).areap)
+
+/* beginning of string */
+#define Xstring(xs,xp) ((xs).beg)
+
+#define Xnleft(xs,xp) ((xs).end - (xp)) /* may be less than 0 */
+#define Xlength(xs,xp) ((xp) - (xs).beg)
+#define Xsize(xs,xp) ((xs).end - (xs).beg)
+#define Xsavepos(xs,xp) ((xp) - (xs).beg)
+#define Xrestpos(xs,xp,n) ((xs).beg + (n))
+
+char *Xcheck_grow(XString *, const char *, size_t);
+
+/*
+ * expandable vector of generic pointers
+ */
+
+typedef struct {
+ /* beginning of allocated area */
+ void **beg;
+ /* currently used number of entries */
+ size_t len;
+ /* allocated number of entries */
+ size_t siz;
+} XPtrV;
+
+#define XPinit(x,n) do { \
+ (x).siz = (n); \
+ (x).len = 0; \
+ (x).beg = alloc2((x).siz, sizeof(void *), ATEMP); \
+} while (/* CONSTCOND */ 0) \
+
+#define XPput(x,p) do { \
+ if ((x).len == (x).siz) { \
+ (x).beg = aresize2((x).beg, (x).siz, \
+ 2 * sizeof(void *), ATEMP); \
+ (x).siz <<= 1; \
+ } \
+ (x).beg[(x).len++] = (p); \
+} while (/* CONSTCOND */ 0)
+
+#define XPptrv(x) ((x).beg)
+#define XPsize(x) ((x).len)
+#define XPclose(x) aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP)
+#define XPfree(x) afree((x).beg, ATEMP)
+
+/* for print_columns */
+
+struct columnise_opts {
+ struct shf *shf;
+ char linesep;
+ bool do_last;
+ bool prefcol;
+};
+
+/*
+ * Lexer internals
+ */
+
+typedef struct source Source;
+struct source {
+ /* input buffer */
+ XString xs;
+ /* memory area, also checked in reclaim() */
+ Area *areap;
+ /* stacked source */
+ Source *next;
+ /* input pointer */
+ const char *str;
+ /* start of current buffer */
+ const char *start;
+ /* input file name */
+ const char *file;
+ /* extra data */
+ union {
+ /* string[] */
+ const char **strv;
+ /* shell file */
+ struct shf *shf;
+ /* alias (SF_HASALIAS) */
+ struct tbl *tblp;
+ /* (also for SREREAD) */
+ char *freeme;
+ } u;
+ /* flags */
+ int flags;
+ /* input type */
+ int type;
+ /* line number */
+ int line;
+ /* line the error occurred on (0 if not set) */
+ int errline;
+ /* buffer for ungetsc() (SREREAD) and alias (SALIAS) */
+ char ugbuf[2];
+};
+
+/* Source.type values */
+#define SEOF 0 /* input EOF */
+#define SFILE 1 /* file input */
+#define SSTDIN 2 /* read stdin */
+#define SSTRING 3 /* string */
+#define SWSTR 4 /* string without \n */
+#define SWORDS 5 /* string[] */
+#define SWORDSEP 6 /* string[] separator */
+#define SALIAS 7 /* alias expansion */
+#define SREREAD 8 /* read ahead to be re-scanned */
+#define SSTRINGCMDLINE 9 /* string from "mksh -c ..." */
+
+/* Source.flags values */
+#define SF_ECHO BIT(0) /* echo input to shlout */
+#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_HASALIAS BIT(4) /* u.tblp valid (SALIAS, SEOF) */
+#define SF_MAYEXEC BIT(5) /* special sh -c optimisation hack */
+
+typedef union {
+ int i;
+ char *cp;
+ char **wp;
+ struct op *o;
+ struct ioword *iop;
+} YYSTYPE;
+
+/* If something is added here, add it to tokentab[] in syn.c as well */
+#define LWORD 256
+#define LOGAND 257 /* && */
+#define LOGOR 258 /* || */
+#define BREAK 259 /* ;; */
+#define IF 260
+#define THEN 261
+#define ELSE 262
+#define ELIF 263
+#define FI 264
+#define CASE 265
+#define ESAC 266
+#define FOR 267
+#define SELECT 268
+#define WHILE 269
+#define UNTIL 270
+#define DO 271
+#define DONE 272
+#define IN 273
+#define FUNCTION 274
+#define TIME 275
+#define REDIR 276
+#define MDPAREN 277 /* (( )) */
+#define BANG 278 /* ! */
+#define DBRACKET 279 /* [[ .. ]] */
+#define COPROC 280 /* |& */
+#define BRKEV 281 /* ;| */
+#define BRKFT 282 /* ;& */
+#define YYERRCODE 300
+
+/* flags to yylex */
+#define CONTIN BIT(0) /* skip new lines to complete command */
+#define ONEWORD BIT(1) /* single word for substitute() */
+#define ALIAS BIT(2) /* recognise alias */
+#define KEYWORD BIT(3) /* recognise keywords */
+#define LETEXPR BIT(4) /* get expression inside (( )) */
+#define CMDASN BIT(5) /* parse x[1 & 2] as one word, for typeset */
+#define HEREDOC BIT(6) /* parsing a here document body */
+#define ESACONLY BIT(7) /* only accept esac keyword */
+#define CMDWORD BIT(8) /* parsing simple command (alias related) */
+#define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */
+#define LQCHAR BIT(10) /* source string contains QCHAR */
+
+#define HERES 10 /* max number of << in line */
+
+#ifdef MKSH_EBCDIC
+#define CTRL_AT (0x00U)
+#define CTRL_A (0x01U)
+#define CTRL_B (0x02U)
+#define CTRL_C (0x03U)
+#define CTRL_D (0x37U)
+#define CTRL_E (0x2DU)
+#define CTRL_F (0x2EU)
+#define CTRL_G (0x2FU)
+#define CTRL_H (0x16U)
+#define CTRL_I (0x05U)
+#define CTRL_J (0x15U)
+#define CTRL_K (0x0BU)
+#define CTRL_L (0x0CU)
+#define CTRL_M (0x0DU)
+#define CTRL_N (0x0EU)
+#define CTRL_O (0x0FU)
+#define CTRL_P (0x10U)
+#define CTRL_Q (0x11U)
+#define CTRL_R (0x12U)
+#define CTRL_S (0x13U)
+#define CTRL_T (0x3CU)
+#define CTRL_U (0x3DU)
+#define CTRL_V (0x32U)
+#define CTRL_W (0x26U)
+#define CTRL_X (0x18U)
+#define CTRL_Y (0x19U)
+#define CTRL_Z (0x3FU)
+#define CTRL_BO (0x27U)
+#define CTRL_BK (0x1CU)
+#define CTRL_BC (0x1DU)
+#define CTRL_CA (0x1EU)
+#define CTRL_US (0x1FU)
+#define CTRL_QM (0x07U)
+#else
+#define CTRL_AT (0x00U)
+#define CTRL_A (0x01U)
+#define CTRL_B (0x02U)
+#define CTRL_C (0x03U)
+#define CTRL_D (0x04U)
+#define CTRL_E (0x05U)
+#define CTRL_F (0x06U)
+#define CTRL_G (0x07U)
+#define CTRL_H (0x08U)
+#define CTRL_I (0x09U)
+#define CTRL_J (0x0AU)
+#define CTRL_K (0x0BU)
+#define CTRL_L (0x0CU)
+#define CTRL_M (0x0DU)
+#define CTRL_N (0x0EU)
+#define CTRL_O (0x0FU)
+#define CTRL_P (0x10U)
+#define CTRL_Q (0x11U)
+#define CTRL_R (0x12U)
+#define CTRL_S (0x13U)
+#define CTRL_T (0x14U)
+#define CTRL_U (0x15U)
+#define CTRL_V (0x16U)
+#define CTRL_W (0x17U)
+#define CTRL_X (0x18U)
+#define CTRL_Y (0x19U)
+#define CTRL_Z (0x1AU)
+#define CTRL_BO (0x1BU)
+#define CTRL_BK (0x1CU)
+#define CTRL_BC (0x1DU)
+#define CTRL_CA (0x1EU)
+#define CTRL_US (0x1FU)
+#define CTRL_QM (0x7FU)
+#endif
+
+#define IDENT 64
+
+EXTERN Source *source; /* yyparse/yylex source */
+EXTERN YYSTYPE yylval; /* result from yylex */
+EXTERN struct ioword *heres[HERES], **herep;
+EXTERN char ident[IDENT + 1];
+
+EXTERN char **history; /* saved commands */
+EXTERN char **histptr; /* last history item */
+EXTERN mksh_ari_t histsize; /* history size */
+
+/* flags to histsave */
+#define HIST_FLUSH 0
+#define HIST_QUEUE 1
+#define HIST_APPEND 2
+#define HIST_STORE 3
+#define HIST_NOTE 4
+
+/* user and system time of last j_waitjed job */
+EXTERN struct timeval j_usrtime, j_systime;
+
+#define notok2mul(max,val,c) (((val) != 0) && ((c) != 0) && \
+ (((max) / (c)) < (val)))
+#define notok2add(max,val,c) ((val) > ((max) - (c)))
+#define notoktomul(val,cnst) notok2mul(SIZE_MAX, (val), (cnst))
+#define notoktoadd(val,cnst) notok2add(SIZE_MAX, (val), (cnst))
+#define checkoktoadd(val,cnst) do { \
+ if (notoktoadd((val), (cnst))) \
+ internal_errorf(Tintovfl, (size_t)(val), \
+ '+', (size_t)(cnst)); \
+} 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 alloc2(m,n,ap) aresize2(NULL, (m), (n), (ap))
+void *aresize(void *, size_t, Area *);
+void *aresize2(void *, size_t, size_t, Area *);
+void afree(void *, Area *); /* can take NULL */
+#define aresizeif(z,p,n,ap) (((p) == NULL) || ((z) < (n)) || \
+ (((z) & ~X_WASTE) > ((n) & ~X_WASTE)) ? \
+ aresize((p), (n), (ap)) : (p))
+/* edit.c */
+#ifndef MKSH_NO_CMDLINE_EDITING
+int x_bind(const char * SMALLP(bool));
+int x_bind_check(void);
+int x_bind_list(void);
+int x_bind_showall(void);
+void x_init(void);
+#ifdef DEBUG_LEAKS
+void x_done(void);
+#endif
+int x_read(char *);
+#endif
+void x_mkraw(int, mksh_ttyst *, bool);
+void x_initterm(const char *);
+/* eval.c */
+char *substitute(const char *, int);
+char **eval(const char **, int);
+char *evalstr(const char *cp, int);
+char *evalonestr(const char *cp, int);
+char *debunk(char *, const char *, size_t);
+void expand(const char *, XPtrV *, int);
+int glob_str(char *, XPtrV *, bool);
+char *do_tilde(char *);
+/* exec.c */
+int execute(struct op * volatile, volatile int, volatile int * volatile);
+int c_builtin(const char **);
+struct tbl *get_builtin(const char *);
+struct tbl *findfunc(const char *, uint32_t, bool);
+int define(const char *, struct op *);
+const char *builtin(const char *, int (*)(const char **));
+struct tbl *findcom(const char *, int);
+void flushcom(bool);
+int search_access(const char *, int);
+const char *search_path(const char *, const char *, int, int *);
+void pr_menu(const char * const *);
+void pr_list(struct columnise_opts *, char * const *);
+int herein(struct ioword *, char **);
+/* expr.c */
+int evaluate(const char *, mksh_ari_t *, int, bool);
+int v_evaluate(struct tbl *, const char *, volatile int, bool);
+/* UTF-8 stuff */
+size_t utf_mbtowc(unsigned int *, const char *);
+size_t utf_wctomb(char *, unsigned int);
+int utf_widthadj(const char *, const char **);
+size_t utf_mbswidth(const char *) MKSH_A_PURE;
+const char *utf_skipcols(const char *, int, int *);
+size_t utf_ptradj(const char *) MKSH_A_PURE;
+#ifdef MIRBSD_BOOTFLOPPY
+#define utf_wcwidth(i) wcwidth((wchar_t)(i))
+#else
+int utf_wcwidth(unsigned int) MKSH_A_PURE;
+#endif
+int ksh_access(const char *, int);
+struct tbl *tempvar(const char *);
+/* funcs.c */
+int c_hash(const char **);
+int c_pwd(const char **);
+int c_print(const char **);
+#ifdef MKSH_PRINTF_BUILTIN
+int c_printf(const char **);
+#endif
+int c_whence(const char **);
+int c_command(const char **);
+int c_typeset(const char **);
+bool valid_alias_name(const char *);
+int c_alias(const char **);
+int c_unalias(const char **);
+int c_let(const char **);
+int c_jobs(const char **);
+#ifndef MKSH_UNEMPLOYED
+int c_fgbg(const char **);
+#endif
+int c_kill(const char **);
+void getopts_reset(int);
+int c_getopts(const char **);
+#ifndef MKSH_NO_CMDLINE_EDITING
+int c_bind(const char **);
+#endif
+int c_shift(const char **);
+int c_umask(const char **);
+int c_dot(const char **);
+int c_wait(const char **);
+int c_read(const char **);
+int c_eval(const char **);
+int c_trap(const char **);
+int c_brkcont(const char **);
+int c_exitreturn(const char **);
+int c_set(const char **);
+int c_unset(const char **);
+int c_ulimit(const char **);
+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_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 *);
+#if HAVE_PERSISTENT_HISTORY
+void hist_finish(void);
+#endif
+void histsave(int *, const char *, int, bool);
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+bool histsync(void);
+#endif
+int c_fc(const char **);
+void sethistsize(mksh_ari_t);
+#if HAVE_PERSISTENT_HISTORY
+void sethistfile(const char *);
+#endif
+#if !defined(MKSH_NO_CMDLINE_EDITING) && !MKSH_S_NOVI
+char **histpos(void) MKSH_A_PURE;
+int histnum(int);
+#endif
+int findhist(int, int, const char *, bool) MKSH_A_PURE;
+char **hist_get_newest(bool);
+void inittraps(void);
+void alarm_init(void);
+Trap *gettrap(const char *, bool, bool);
+void trapsig(int);
+void intrcheck(void);
+int fatal_trap_check(void);
+int trap_pending(void);
+void runtraps(int intr);
+void runtrap(Trap *, bool);
+void cleartraps(void);
+void restoresigs(void);
+void settrap(Trap *, const char *);
+bool block_pipe(void);
+void restore_pipe(void);
+int setsig(Trap *, sig_t, int);
+void setexecsig(Trap *, int);
+#if HAVE_FLOCK || HAVE_LOCK_FCNTL
+void mksh_lockfd(int);
+void mksh_unlkfd(int);
+#endif
+/* jobs.c */
+void j_init(void);
+void j_exit(void);
+#ifndef MKSH_UNEMPLOYED
+void j_change(void);
+#endif
+int exchild(struct op *, int, volatile int *, int);
+void startlast(void);
+int waitlast(void);
+int waitfor(const char *, int *);
+int j_kill(const char *, int);
+#ifndef MKSH_UNEMPLOYED
+int j_resume(const char *, int);
+#endif
+#if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
+void j_suspend(void);
+#endif
+int j_jobs(const char *, int, int);
+void j_notify(void);
+pid_t j_async(void);
+int j_stopped_running(void);
+/* lex.c */
+int yylex(int);
+void yyskiputf8bom(void);
+void yyerror(const char *, ...)
+ MKSH_A_NORETURN
+ MKSH_A_FORMAT(__printf__, 1, 2);
+Source *pushs(int, Area *);
+void set_prompt(int, Source *);
+int pprompt(const char *, int);
+/* main.c */
+int include(const char *, int, const char **, bool);
+int command(const char *, int);
+int shell(Source * volatile, volatile int);
+/* argument MUST NOT be 0 */
+void unwind(int) MKSH_A_NORETURN;
+void newenv(int);
+void quitenv(struct shf *);
+void cleanup_parents_env(void);
+void cleanup_proc_env(void);
+void errorf(const char *, ...)
+ MKSH_A_NORETURN
+ 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);
+void bi_errorf(const char *, ...)
+ MKSH_A_FORMAT(__printf__, 1, 2);
+void maybe_errorf(int *, int, const char *, ...)
+ MKSH_A_FORMAT(__printf__, 3, 4);
+#define errorfz() errorf(NULL)
+#define errorfxz(rc) errorfx((rc), NULL)
+#define bi_errorfz() bi_errorf(NULL)
+void internal_errorf(const char *, ...)
+ MKSH_A_NORETURN
+ MKSH_A_FORMAT(__printf__, 1, 2);
+void internal_warningf(const char *, ...)
+ MKSH_A_FORMAT(__printf__, 1, 2);
+void error_prefix(bool);
+void shellf(const char *, ...)
+ MKSH_A_FORMAT(__printf__, 1, 2);
+void shprintf(const char *, ...)
+ MKSH_A_FORMAT(__printf__, 1, 2);
+int can_seek(int);
+void initio(void);
+void recheck_ctype(void);
+int ksh_dup2(int, int, bool);
+short savefd(int);
+void restfd(int, int);
+void openpipe(int *);
+void closepipe(int *);
+int check_fd(const char *, int, const char **);
+void coproc_init(void);
+void coproc_read_close(int);
+void coproc_readw_close(int);
+void coproc_write_close(int);
+int coproc_getfd(int, const char **);
+void coproc_cleanup(int);
+struct temp *maketemp(Area *, Temp_type, struct temp **);
+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 *);
+struct tbl *ktnext(struct tstate *);
+struct tbl **ktsort(struct table *);
+#ifdef DF
+void DF(const char *, ...)
+ MKSH_A_FORMAT(__printf__, 1, 2);
+#endif
+/* misc.c */
+size_t option(const char *) MKSH_A_PURE;
+char *getoptions(void);
+void change_flag(enum sh_flag, int, bool);
+void change_xtrace(unsigned char, bool);
+int parse_args(const char **, int, bool *);
+int getn(const char *, int *);
+int gmatchx(const char *, const char *, bool);
+bool has_globbing(const char *) MKSH_A_PURE;
+int ascstrcmp(const void *, const void *) MKSH_A_PURE;
+int ascpstrcmp(const void *, const void *) MKSH_A_PURE;
+void ksh_getopt_reset(Getopt *, int);
+int ksh_getopt(const char **, Getopt *, const char *);
+void print_value_quoted(struct shf *, const char *);
+char *quote_value(const char *);
+void print_columns(struct columnise_opts *, unsigned int,
+ void (*)(char *, size_t, unsigned int, const void *),
+ const void *, size_t, size_t);
+void strip_nuls(char *, size_t)
+ MKSH_A_BOUNDED(__string__, 1, 2);
+ssize_t blocking_read(int, char *, size_t)
+ MKSH_A_BOUNDED(__buffer__, 2, 3);
+int reset_nonblock(int);
+char *ksh_get_wd(void);
+char *do_realpath(const char *);
+void simplify_path(char *);
+void set_current_wd(const char *);
+int c_cd(const char **);
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+char *strdup_i(const char *, Area *);
+char *strndup_i(const char *, size_t, Area *);
+#endif
+int unbksl(bool, int (*)(void), void (*)(int));
+#ifdef __OS2__
+/* os2.c */
+void os2_init(int *, const char ***);
+void setextlibpath(const char *, const char *);
+int access_ex(int (*)(const char *, int), const char *, int);
+int stat_ex(int (*)(const char *, struct stat *), const char *, struct stat *);
+const char *real_exec_name(const char *);
+#endif
+/* shf.c */
+struct shf *shf_open(const char *, int, int, int);
+struct shf *shf_fdopen(int, int, struct shf *);
+struct shf *shf_reopen(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 *);
+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_SHF_NO_INLINE
+int shf_getc(struct shf *);
+int shf_putc(int, struct shf *);
+#else
+#define shf_getc shf_getc_i
+#define shf_putc shf_putc_i
+#endif
+int shf_putchar(int, struct shf *);
+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);
+ssize_t shf_vfprintf(struct shf *, const char *, va_list)
+ MKSH_A_FORMAT(__printf__, 2, 0);
+void set_ifs(const char *);
+/* syn.c */
+void initkeywords(void);
+struct op *compile(Source *, bool, bool);
+bool parse_usec(const char *, struct timeval *);
+char *yyrecursive(int);
+void yyrecursive_pop(bool);
+/* tree.c */
+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);
+#define WDS_TPUTS BIT(0) /* tputS (dumpwdvar) mode */
+char *wdstrip(const char *, int);
+void tfree(struct op *, Area *);
+void dumpchar(struct shf *, unsigned char);
+void dumptree(struct shf *, struct op *);
+void dumpwdvar(struct shf *, const char *);
+void dumpioact(struct shf *shf, struct op *t);
+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);
+void initvar(void);
+struct block *varsearch(struct block *, struct tbl **, const char *, uint32_t);
+struct tbl *global(const char *);
+struct tbl *isglobal(const char *, bool);
+struct tbl *local(const char *, bool);
+char *str_val(struct tbl *);
+int setstr(struct tbl *, const char *, int);
+struct tbl *setint_v(struct tbl *, struct tbl *, bool);
+void setint(struct tbl *, mksh_ari_t);
+void setint_n(struct tbl *, mksh_ari_t, int);
+struct tbl *typeset(const char *, uint32_t, uint32_t, int, int);
+void unset(struct tbl *, int);
+const char *skip_varname(const char *, bool) MKSH_A_PURE;
+const char *skip_wdvarname(const char *, bool) MKSH_A_PURE;
+int is_wdvarname(const char *, bool) MKSH_A_PURE;
+int is_wdvarassign(const char *) MKSH_A_PURE;
+struct tbl *arraysearch(struct tbl *, uint32_t);
+char **makenv(void);
+void change_winsz(void);
+size_t array_ref_len(const char *) MKSH_A_PURE;
+char *arrayname(const char *);
+mksh_uari_t set_array(const char *, bool, const char **);
+uint32_t hash(const void *) MKSH_A_PURE;
+uint32_t chvt_rndsetup(const void *, size_t) MKSH_A_PURE;
+mksh_ari_t rndget(void);
+void rndset(unsigned long);
+void rndpush(const void *);
+void record_match(const char *);
+
+enum Test_op {
+ /* non-operator */
+ TO_NONOP = 0,
+ /* unary operators */
+ TO_STNZE, TO_STZER, TO_ISSET, TO_OPTION,
+ TO_FILAXST,
+ TO_FILEXST,
+ TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
+ TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID,
+ TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX,
+ /* binary operators */
+ TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT,
+ TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT,
+ /* not an operator */
+ TO_NONNULL /* !TO_NONOP */
+};
+typedef enum Test_op Test_op;
+
+/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */
+enum Test_meta {
+ TM_OR, /* -o or || */
+ TM_AND, /* -a or && */
+ TM_NOT, /* ! */
+ TM_OPAREN, /* ( */
+ TM_CPAREN, /* ) */
+ TM_UNOP, /* unary operator */
+ TM_BINOP, /* binary operator */
+ TM_END /* end of input */
+};
+typedef enum Test_meta Test_meta;
+
+struct t_op {
+ const char op_text[4];
+ Test_op op_num;
+};
+
+/* for string reuse */
+extern const struct t_op u_ops[];
+extern const struct t_op b_ops[];
+/* ensure order with funcs.c */
+#define Tda (u_ops[0].op_text)
+#define Tdn (u_ops[12].op_text)
+#define Tdo (u_ops[14].op_text)
+#define Tdr (u_ops[16].op_text)
+#define Tdu (u_ops[20].op_text) /* "-u" */
+#define Tdx (u_ops[23].op_text)
+
+#define Tu (Tdu + 1) /* "u" */
+
+#define TEF_ERROR BIT(0) /* set if we've hit an error */
+#define TEF_DBRACKET BIT(1) /* set if [[ .. ]] test */
+
+typedef struct test_env {
+ union {
+ const char **wp; /* used by ptest_* */
+ XPtrV *av; /* used by dbtestp_* */
+ } pos;
+ 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_* */
+} Test_env;
+
+extern const char * const dbtest_tokens[];
+
+Test_op test_isop(Test_meta, const char *) MKSH_A_PURE;
+int test_eval(Test_env *, Test_op, const char *, const char *, bool);
+int test_parse(Test_env *);
+
+/* tty_fd is not opened O_BINARY, it's thus never read/written */
+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 mksh_ttyst tty_state; /* saved tty state */
+EXTERN bool tty_hasstate; /* true if tty_state is valid */
+
+extern int tty_init_fd(void); /* initialise tty_fd, tty_devtty */
+
+#ifdef __OS2__
+#define binopen2(path,flags) __extension__({ \
+ int binopen2_fd = open((path), (flags) | O_BINARY); \
+ if (binopen2_fd >= 0) \
+ setmode(binopen2_fd, O_BINARY); \
+ (binopen2_fd); \
+})
+#define binopen3(path,flags,mode) __extension__({ \
+ int binopen3_fd = open((path), (flags) | O_BINARY, (mode)); \
+ if (binopen3_fd >= 0) \
+ setmode(binopen3_fd, O_BINARY); \
+ (binopen3_fd); \
+})
+#else
+#define binopen2(path,flags) open((path), (flags) | O_BINARY)
+#define binopen3(path,flags,mode) open((path), (flags) | O_BINARY, (mode))
+#endif
+
+#ifdef MKSH_DOSPATH
+#define mksh_drvltr(s) __extension__({ \
+ const char *mksh_drvltr_s = (s); \
+ (ctype(mksh_drvltr_s[0], C_ALPHA) && mksh_drvltr_s[1] == ':'); \
+})
+#define mksh_abspath(s) __extension__({ \
+ const char *mksh_abspath_s = (s); \
+ (mksh_cdirsep(mksh_abspath_s[0]) || \
+ (mksh_drvltr(mksh_abspath_s) && \
+ mksh_cdirsep(mksh_abspath_s[2]))); \
+})
+#define mksh_cdirsep(c) __extension__({ \
+ char mksh_cdirsep_c = (c); \
+ (mksh_cdirsep_c == '/' || mksh_cdirsep_c == '\\'); \
+})
+#define mksh_sdirsep(s) strpbrk((s), "/\\")
+#define mksh_vdirsep(s) __extension__({ \
+ const char *mksh_vdirsep_s = (s); \
+ (((mksh_drvltr(mksh_vdirsep_s) && \
+ !mksh_cdirsep(mksh_vdirsep_s[2])) ? (!0) : \
+ (mksh_sdirsep(mksh_vdirsep_s) != NULL)) && \
+ (strcmp(mksh_vdirsep_s, T_builtin) != 0)); \
+})
+int getdrvwd(char **, unsigned int);
+#else
+#define mksh_abspath(s) (ord((s)[0]) == ORD('/'))
+#define mksh_cdirsep(c) (ord(c) == ORD('/'))
+#define mksh_sdirsep(s) strchr((s), '/')
+#define mksh_vdirsep(s) vstrchr((s), '/')
+#endif
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef E_INIT
+
+#endif /* !MKSH_INCLUDES_ONLY */
diff --git a/shells/mksh/files/sh_flags.opt b/shells/mksh/files/sh_flags.opt
new file mode 100644
index 00000000000..d84d1f17c60
--- /dev/null
+++ b/shells/mksh/files/sh_flags.opt
@@ -0,0 +1,194 @@
+/*-
+ * Copyright (c) 2013, 2014, 2015, 2017
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+@SHFLAGS_DEFNS
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.9 2020/05/16 22:38:25 tg Exp $");
+@SHFLAGS_ENUMS
+#define FN(sname,cname,flags,ochar) cname,
+#define F0(sname,cname,flags,ochar) cname = 0,
+@SHFLAGS_ITEMS
+#define FN(sname,cname,flags,ochar) ((const char *)(&shoptione_ ## cname)) + 2,
+@@
+
+/* special cases */
+
+<o:|!SHFLAGS_NOT_CMD
+<T:|!SHFLAGS_NOT_CMD
+<A:|!SHFLAGS_NOT_SET
+<o;|!SHFLAGS_NOT_SET
+<s|!SHFLAGS_NOT_SET
+
+/*
+ * options are sorted by their longnames
+ */
+
+/* -a all new parameters are created with the export attribute */
+>a|
+F0("allexport", FEXPORT, OF_ANY
+
+/* ./. bgnice */
+>| HAVE_NICE
+FN("bgnice", FBGNICE, OF_ANY
+
+/* ./. enable {} globbing (non-standard) */
+>|
+FN("braceexpand", FBRACEEXPAND, OF_ANY
+
+/* ./. Emacs command line editing mode */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("emacs", FEMACS, OF_ANY
+
+/* -e quit on error */
+>e|
+FN("errexit", FERREXIT, OF_ANY
+
+/* ./. Emacs command line editing mode, gmacs variant */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("gmacs", FGMACS, OF_ANY
+
+/* ./. reading EOF does not exit */
+>|
+FN("ignoreeof", FIGNOREEOF, OF_ANY
+
+/* ./. inherit -x flag */
+>|
+FN("inherit-xtrace", FXTRACEREC, OF_ANY
+
+/* -i interactive shell */
+>i|!SHFLAGS_NOT_CMD
+FN("interactive", FTALKING, OF_CMDLINE
+
+/* -k name=value are recognised anywhere */
+>k|
+FN("keyword", FKEYWORD, OF_ANY
+
+/* -l login shell */
+>l|!SHFLAGS_NOT_CMD
+FN("login", FLOGIN, OF_CMDLINE
+
+/* -X mark dirs with / in file name completion */
+>X|
+FN("markdirs", FMARKDIRS, OF_ANY
+
+/* -m job control monitoring */
+>m|!MKSH_UNEMPLOYED
+FN("monitor", FMONITOR, OF_ANY
+
+/* -C don't overwrite existing files */
+>C|
+FN("noclobber", FNOCLOBBER, OF_ANY
+
+/* -n don't execute any commands */
+>n|
+FN("noexec", FNOEXEC, OF_ANY
+
+/* -f don't do file globbing */
+>f|
+FN("noglob", FNOGLOB, OF_ANY
+
+/* ./. don't kill running jobs when login shell exits */
+>|
+FN("nohup", FNOHUP, OF_ANY
+
+/* ./. don't save functions in history (no effect) */
+>|
+FN("nolog", FNOLOG, OF_ANY
+
+/* -b asynchronous job completion notification */
+>b|!MKSH_UNEMPLOYED
+FN("notify", FNOTIFY, OF_ANY
+
+/* -u using an unset variable is an error */
+>u|
+FN("nounset", FNOUNSET, OF_ANY
+
+/* ./. don't do logical cds/pwds (non-standard) */
+>|
+FN("physical", FPHYSICAL, OF_ANY
+
+/* ./. errorlevel of a pipeline is the rightmost nonzero value */
+>|
+FN("pipefail", FPIPEFAIL, OF_ANY
+
+/* ./. adhere more closely to POSIX even when undesirable */
+>|
+FN("posix", FPOSIX, OF_ANY
+
+/* -p privileged shell (suid) */
+>p|
+FN("privileged", FPRIVILEGED, OF_ANY
+
+/* -r restricted shell */
+>r|!SHFLAGS_NOT_CMD
+FN("restricted", FRESTRICTED, OF_CMDLINE
+
+/* ./. kludge mode for better compat with traditional sh (OS-specific) */
+>|
+FN("sh", FSH, OF_ANY
+
+/* -s (invocation) parse stdin (pseudo non-standard) */
+>s|!SHFLAGS_NOT_CMD
+FN("stdin", FSTDIN, OF_CMDLINE
+
+/* -h create tracked aliases for all commands */
+>h|
+FN("trackall", FTRACKALL, OF_ANY
+
+/* -U enable UTF-8 processing (non-standard) */
+>U|
+FN("utf8-mode", FUNNYCODE, OF_ANY
+
+/* -v echo input */
+>v|
+FN("verbose", FVERBOSE, OF_ANY
+
+/* ./. Vi command line editing mode */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("vi", FVI, OF_ANY
+
+/* ./. enable ESC as file name completion character (non-standard) */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("vi-esccomplete", FVIESCCOMPLETE, OF_ANY
+
+/* ./. enable Tab as file name completion character (non-standard) */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("vi-tabcomplete", FVITABCOMPLETE, OF_ANY
+
+/* ./. always read in raw mode (no effect) */
+>|!MKSH_NO_CMDLINE_EDITING
+FN("viraw", FVIRAW, OF_ANY
+
+/* -x execution trace (display commands as they are run) */
+>x|
+FN("xtrace", FXTRACE, OF_ANY
+
+/* -c (invocation) execute specified command */
+>c|!SHFLAGS_NOT_CMD
+FN("", FCOMMAND, OF_CMDLINE
+
+/*
+ * anonymous flags: used internally by shell only (not visible to user)
+ */
+
+/* ./. (internal) initial shell was interactive */
+>|
+FN("", FTALKING_I, OF_INTERNAL
+
+|SHFLAGS_OPTCS
diff --git a/shells/mksh/files/shf.c b/shells/mksh/files/shf.c
new file mode 100644
index 00000000000..8e95fc66338
--- /dev/null
+++ b/shells/mksh/files/shf.c
@@ -0,0 +1,1322 @@
+/* $OpenBSD: shf.c,v 1.16 2013/04/19 17:36:09 millert Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
+ * 2012, 2013, 2015, 2016, 2017, 2018, 2019
+ * mirabilos <m@mirbsd.org>
+ * Copyright (c) 2015
+ * Daniel Richard G. <skunk@iSKUNK.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 %zX instead of %p and floating point isn't supported at all.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.101 2019/12/11 17:56:58 tg Exp $");
+
+/* flags to shf_emptybuf() */
+#define EB_READSW 0x01 /* about to switch to reading */
+#define EB_GROW 0x02 /* grow buffer if necessary (STRING+DYNAMIC) */
+
+/*
+ * Replacement stdio routines. Stdio is too flakey on too many machines
+ * to be useful when you have multiple processes using the same underlying
+ * file descriptors.
+ */
+
+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
+ * this package. Returns NULL if file could not be opened, or if a dup
+ * fails.
+ */
+struct shf *
+shf_open(const char *name, int oflags, int mode, int sflags)
+{
+ struct shf *shf;
+ ssize_t bsize =
+ /* at most 512 */
+ sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+ int fd, eno;
+
+ /* Done before open so if alloca fails, fd won't be lost. */
+ shf = alloc(sizeof(struct shf) + bsize, ATEMP);
+ shf->areap = ATEMP;
+ shf->buf = (unsigned char *)&shf[1];
+ shf->bsize = bsize;
+ shf->flags = SHF_ALLOCS;
+ /* Rest filled in by reopen. */
+
+ fd = binopen3(name, oflags, mode);
+ if (fd < 0) {
+ eno = errno;
+ afree(shf, shf->areap);
+ errno = eno;
+ return (NULL);
+ }
+ if ((sflags & SHF_MAPHI) && fd < FDBASE) {
+ int nfd;
+
+ nfd = fcntl(fd, F_DUPFD, FDBASE);
+ eno = errno;
+ close(fd);
+ if (nfd < 0) {
+ afree(shf, shf->areap);
+ errno = eno;
+ return (NULL);
+ }
+ fd = nfd;
+ }
+ sflags &= ~SHF_ACCMODE;
+ sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD :
+ ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR : SHF_RDWR);
+
+ return (shf_reopen(fd, sflags, shf));
+}
+
+/* helper function for shf_fdopen and shf_reopen */
+static void
+shf_open_hlp(int fd, int *sflagsp, const char *where)
+{
+ int sflags = *sflagsp;
+
+ /* 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;
+ }
+ }
+ *sflagsp = sflags;
+ }
+
+ if (!(sflags & (SHF_RD | SHF_WR)))
+ internal_errorf(Tf_sD_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);
+ sflags |= SHF_ALLOCB;
+ } else
+ shf->buf = NULL;
+ } else {
+ shf = alloc(sizeof(struct shf) + bsize, ATEMP);
+ shf->buf = (unsigned char *)&shf[1];
+ sflags |= SHF_ALLOCS;
+ }
+ shf->areap = ATEMP;
+ shf->fd = fd;
+ shf->rp = shf->wp = shf->buf;
+ shf->rnleft = 0;
+ shf->rbsize = bsize;
+ shf->wnleft = 0; /* force call to shf_emptybuf() */
+ shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+ shf->flags = sflags;
+ shf->errnosv = 0;
+ shf->bsize = bsize;
+ if (sflags & SHF_CLEXEC)
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return (shf);
+}
+
+/* Set up an existing shf (and buffer) to use the given fd */
+struct shf *
+shf_reopen(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_reopen");
+ if (!shf || !shf->buf || shf->bsize < bsize)
+ internal_errorf(Tf_sD_s, "shf_reopen", Tbad_bsize);
+
+ /* assumes shf->buf and shf->bsize already set up */
+ shf->fd = fd;
+ shf->rp = shf->wp = shf->buf;
+ shf->rnleft = 0;
+ shf->rbsize = bsize;
+ 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->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
+ * 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
+ * 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 NUL - see shf_sclose().
+ */
+struct 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(Tf_flags, "shf_sopen",
+ (unsigned int)sflags);
+
+ if (!shf) {
+ shf = alloc(sizeof(struct shf), ATEMP);
+ sflags |= SHF_ALLOCS;
+ }
+ shf->areap = ATEMP;
+ if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) {
+ if (bsize <= 0)
+ bsize = 64;
+ sflags |= SHF_ALLOCB;
+ buf = alloc(bsize, shf->areap);
+ }
+ shf->fd = -1;
+ shf->buf = shf->rp = shf->wp = (unsigned char *)buf;
+ shf->rnleft = bsize;
+ shf->rbsize = bsize;
+ shf->wnleft = bsize - 1; /* space for a '\0' */
+ shf->wbsize = bsize;
+ shf->flags = sflags | SHF_STRING;
+ shf->errnosv = 0;
+ shf->bsize = bsize;
+
+ return (shf);
+}
+
+/* Flush and close file descriptor, free the shf structure */
+int
+shf_close(struct shf *shf)
+{
+ int ret = 0;
+
+ if (shf->fd >= 0) {
+ ret = shf_flush(shf);
+ if (close(shf->fd) < 0)
+ ret = -1;
+ }
+ if (shf->flags & SHF_ALLOCS)
+ afree(shf, shf->areap);
+ else if (shf->flags & SHF_ALLOCB)
+ afree(shf->buf, shf->areap);
+
+ return (ret);
+}
+
+/* Flush and close file descriptor, don't free file structure */
+int
+shf_fdclose(struct shf *shf)
+{
+ int ret = 0;
+
+ if (shf->fd >= 0) {
+ ret = shf_flush(shf);
+ if (close(shf->fd) < 0)
+ ret = -1;
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ shf->wnleft = 0;
+ shf->fd = -1;
+ }
+
+ return (ret);
+}
+
+/*
+ * 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).
+ */
+char *
+shf_sclose(struct shf *shf)
+{
+ unsigned char *s = shf->buf;
+
+ /* NUL terminate */
+ if (shf->flags & SHF_WR) {
+ shf->wnleft++;
+ shf_putc('\0', shf);
+ }
+ if (shf->flags & SHF_ALLOCS)
+ afree(shf, shf->areap);
+ return ((char *)s);
+}
+
+/*
+ * Un-read what has been read but not examined, or write what has been
+ * buffered. Returns 0 for success, -1 for (write) error.
+ */
+int
+shf_flush(struct shf *shf)
+{
+ int rv = 0;
+
+ if (shf->flags & SHF_STRING)
+ rv = (shf->flags & SHF_WR) ? -1 : 0;
+ else if (shf->fd < 0)
+ internal_errorf(Tf_sD_s, "shf_flush", "no fd");
+ else if (shf->flags & SHF_ERROR) {
+ errno = shf->errnosv;
+ rv = -1;
+ } else if (shf->flags & SHF_READING) {
+ shf->flags &= ~(SHF_EOF | SHF_READING);
+ if (shf->rnleft > 0) {
+ if (lseek(shf->fd, (off_t)-shf->rnleft,
+ SEEK_CUR) == -1) {
+ shf->flags |= SHF_ERROR;
+ shf->errnosv = errno;
+ rv = -1;
+ }
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ }
+ } else if (shf->flags & SHF_WRITING)
+ rv = shf_emptybuf(shf, 0);
+
+ return (rv);
+}
+
+/*
+ * Write out any buffered data. If currently reading, flushes the read
+ * buffer. Returns 0 for success, -1 for (write) error.
+ */
+static int
+shf_emptybuf(struct shf *shf, int flags)
+{
+ int ret = 0;
+
+ if (!(shf->flags & SHF_STRING) && shf->fd < 0)
+ internal_errorf(Tf_sD_s, "shf_emptybuf", "no fd");
+
+ if (shf->flags & SHF_ERROR) {
+ errno = shf->errnosv;
+ return (-1);
+ }
+
+ if (shf->flags & SHF_READING) {
+ if (flags & EB_READSW)
+ /* doesn't happen */
+ return (0);
+ ret = shf_flush(shf);
+ shf->flags &= ~SHF_READING;
+ }
+ 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)
+ */
+ if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) ||
+ !(shf->flags & SHF_ALLOCB))
+ return (-1);
+ /* allocate more space for buffer */
+ 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 <<= 1;
+ shf->buf = nbuf;
+ } else {
+ if (shf->flags & SHF_WRITING) {
+ ssize_t n, ntowrite = shf->wp - shf->buf;
+ unsigned char *buf = shf->buf;
+
+ while (ntowrite > 0) {
+ n = write(shf->fd, buf, ntowrite);
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errnosv = errno;
+ shf->wnleft = 0;
+ if (buf != shf->buf) {
+ /*
+ * allow a second flush
+ * to work
+ */
+ memmove(shf->buf, buf,
+ ntowrite);
+ shf->wp = shf->buf + ntowrite;
+ }
+ return (-1);
+ }
+ buf += n;
+ ntowrite -= n;
+ }
+ if (flags & EB_READSW) {
+ shf->wp = shf->buf;
+ shf->wnleft = 0;
+ shf->flags &= ~SHF_WRITING;
+ return (0);
+ }
+ }
+ shf->wp = shf->buf;
+ shf->wnleft = shf->wbsize;
+ }
+ shf->flags |= SHF_WRITING;
+
+ return (ret);
+}
+
+/* Fill up a read buffer. Returns -1 for a read error, 0 otherwise. */
+static int
+shf_fillbuf(struct shf *shf)
+{
+ ssize_t n;
+
+ if (shf->flags & SHF_STRING)
+ return (0);
+
+ if (shf->fd < 0)
+ internal_errorf(Tf_sD_s, "shf_fillbuf", "no fd");
+
+ if (shf->flags & (SHF_EOF | SHF_ERROR)) {
+ if (shf->flags & SHF_ERROR)
+ errno = shf->errnosv;
+ return (-1);
+ }
+
+ if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == -1)
+ return (-1);
+
+ shf->flags |= SHF_READING;
+
+ shf->rp = shf->buf;
+ 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 (n < 0) {
+ shf->flags |= SHF_ERROR;
+ shf->errnosv = errno;
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ return (-1);
+ }
+ 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, -1 if a read
+ * error occurred.
+ */
+ssize_t
+shf_read(char *buf, ssize_t bsize, struct shf *shf)
+{
+ ssize_t ncopy, orig_bsize = bsize;
+
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(Tf_flags, Tshf_read,
+ (unsigned int)shf->flags);
+
+ if (bsize <= 0)
+ internal_errorf(Tf_szs, Tshf_read, bsize, Tbsize);
+
+ while (bsize > 0) {
+ if (shf->rnleft == 0 &&
+ (shf_fillbuf(shf) == -1 || shf->rnleft == 0))
+ break;
+ ncopy = shf->rnleft;
+ if (ncopy > bsize)
+ ncopy = bsize;
+ memcpy(buf, shf->rp, ncopy);
+ buf += ncopy;
+ bsize -= ncopy;
+ shf->rp += ncopy;
+ shf->rnleft -= ncopy;
+ }
+ /* Note: fread(3S) returns 0 for errors - this doesn't */
+ return (orig_bsize == bsize ? (shf_error(shf) ? -1 : 0) :
+ orig_bsize - bsize);
+}
+
+/*
+ * Read up to a newline or -1. 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, ssize_t bsize, struct shf *shf)
+{
+ unsigned char *end;
+ ssize_t ncopy;
+ char *orig_buf = buf;
+
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(Tf_flags, "shf_getse",
+ (unsigned int)shf->flags);
+
+ if (bsize <= 0)
+ return (NULL);
+
+ /* save room for NUL */
+ --bsize;
+ do {
+ if (shf->rnleft == 0) {
+ if (shf_fillbuf(shf) == -1)
+ return (NULL);
+ if (shf->rnleft == 0) {
+ *buf = '\0';
+ return (buf == orig_buf ? NULL : buf);
+ }
+ }
+ end = (unsigned char *)memchr((char *)shf->rp, '\n',
+ shf->rnleft);
+ ncopy = end ? end - shf->rp + 1 : shf->rnleft;
+ if (ncopy > bsize)
+ ncopy = bsize;
+ memcpy(buf, (char *) shf->rp, ncopy);
+ shf->rp += ncopy;
+ shf->rnleft -= ncopy;
+ buf += ncopy;
+ bsize -= ncopy;
+#ifdef MKSH_WITH_TEXTMODE
+ if (buf > orig_buf + 1 && ord(buf[-2]) == ORD('\r') &&
+ ord(buf[-1]) == ORD('\n')) {
+ buf--;
+ bsize++;
+ buf[-1] = '\n';
+ }
+#endif
+ } while (!end && bsize);
+#ifdef MKSH_WITH_TEXTMODE
+ if (!bsize && ord(buf[-1]) == ORD('\r')) {
+ int c = shf_getc(shf);
+ if (ord(c) == ORD('\n'))
+ buf[-1] = '\n';
+ else if (c != -1)
+ shf_ungetc(c, shf);
+ }
+#endif
+ *buf = '\0';
+ return (buf);
+}
+
+/* Returns the char read. Returns -1 for error and end of file. */
+int
+shf_getchar(struct shf *shf)
+{
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(Tf_flags, "shf_getchar",
+ (unsigned int)shf->flags);
+
+ if (shf->rnleft == 0 && (shf_fillbuf(shf) == -1 || shf->rnleft == 0))
+ return (-1);
+ --shf->rnleft;
+ return (ord(*shf->rp++));
+}
+
+/*
+ * Put a character back in the input stream. Returns the character if
+ * successful, -1 if there is no room.
+ */
+int
+shf_ungetc(int c, struct shf *shf)
+{
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(Tf_flags, "shf_ungetc",
+ (unsigned int)shf->flags);
+
+ if ((shf->flags & SHF_ERROR) || c == -1 ||
+ (shf->rp == shf->buf && shf->rnleft))
+ return (-1);
+
+ if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == -1)
+ return (-1);
+
+ 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.
+ */
+ if ((int)(shf->rp[-1]) != c)
+ return (-1);
+ shf->flags &= ~SHF_EOF;
+ shf->rp--;
+ shf->rnleft++;
+ return (c);
+ }
+ shf->flags &= ~SHF_EOF;
+ *--(shf->rp) = c;
+ shf->rnleft++;
+ return (c);
+}
+
+/*
+ * Write a character. Returns the character if successful, -1 if the
+ * char could not be written.
+ */
+int
+shf_putchar(int c, struct shf *shf)
+{
+ if (!(shf->flags & SHF_WR))
+ internal_errorf(Tf_flags, "shf_putchar",
+ (unsigned int)shf->flags);
+
+ if (c == -1)
+ return (-1);
+
+ if (shf->flags & SHF_UNBUF) {
+ unsigned char cc = (unsigned char)c;
+ ssize_t n;
+
+ if (shf->fd < 0)
+ internal_errorf(Tf_sD_s, "shf_putchar", "no fd");
+ if (shf->flags & SHF_ERROR) {
+ errno = shf->errnosv;
+ return (-1);
+ }
+ while ((n = write(shf->fd, &cc, 1)) != 1)
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errnosv = errno;
+ return (-1);
+ }
+ } else {
+ /* Flush deals with strings and sticky errors */
+ if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == -1)
+ return (-1);
+ shf->wnleft--;
+ *shf->wp++ = c;
+ }
+
+ return (c);
+}
+
+/*
+ * Write a string. Returns the length of the string if successful, -1
+ * if the string could not be written.
+ */
+ssize_t
+shf_puts(const char *s, struct shf *shf)
+{
+ if (!s)
+ return (-1);
+
+ return (shf_write(s, strlen(s), shf));
+}
+
+/* Write a buffer. Returns nbytes if successful, -1 if there is an error. */
+ssize_t
+shf_write(const char *buf, ssize_t nbytes, struct shf *shf)
+{
+ ssize_t n, ncopy, orig_nbytes = nbytes;
+
+ if (!(shf->flags & SHF_WR))
+ internal_errorf(Tf_flags, Tshf_write,
+ (unsigned int)shf->flags);
+
+ if (nbytes < 0)
+ internal_errorf(Tf_szs, Tshf_write, nbytes, Tbytes);
+
+ /* Don't buffer if buffer is empty and we're writting a large amount. */
+ if ((ncopy = shf->wnleft) &&
+ (shf->wp != shf->buf || nbytes < shf->wnleft)) {
+ if (ncopy > nbytes)
+ ncopy = nbytes;
+ memcpy(shf->wp, buf, ncopy);
+ nbytes -= ncopy;
+ buf += ncopy;
+ shf->wp += ncopy;
+ shf->wnleft -= ncopy;
+ }
+ if (nbytes > 0) {
+ if (shf->flags & SHF_STRING) {
+ /* resize buffer until there's enough space left */
+ while (nbytes > shf->wnleft)
+ if (shf_emptybuf(shf, EB_GROW) == -1)
+ return (-1);
+ /* then write everything into the buffer */
+ } else {
+ /* flush deals with sticky errors */
+ if (shf_emptybuf(shf, EB_GROW) == -1)
+ return (-1);
+ /* write chunks larger than window size directly */
+ if (nbytes > shf->wbsize) {
+ ncopy = nbytes;
+ if (shf->wbsize)
+ ncopy -= nbytes % shf->wbsize;
+ nbytes -= ncopy;
+ while (ncopy > 0) {
+ n = write(shf->fd, buf, ncopy);
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errnosv = errno;
+ shf->wnleft = 0;
+ /*
+ * Note: fwrite(3) returns 0
+ * for errors - this doesn't
+ */
+ return (-1);
+ }
+ buf += n;
+ ncopy -= n;
+ }
+ }
+ /* ... and buffer the rest */
+ }
+ if (nbytes > 0) {
+ /* write remaining bytes to buffer */
+ memcpy(shf->wp, buf, nbytes);
+ shf->wp += nbytes;
+ shf->wnleft -= nbytes;
+ }
+ }
+
+ return (orig_nbytes);
+}
+
+ssize_t
+shf_fprintf(struct shf *shf, const char *fmt, ...)
+{
+ va_list args;
+ ssize_t n;
+
+ va_start(args, fmt);
+ n = shf_vfprintf(shf, fmt, args);
+ va_end(args);
+
+ return (n);
+}
+
+ssize_t
+shf_snprintf(char *buf, ssize_t bsize, const char *fmt, ...)
+{
+ struct shf shf;
+ va_list args;
+ ssize_t n;
+
+ if (!buf || bsize <= 0)
+ 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);
+ /* NUL terminates */
+ shf_sclose(&shf);
+ return (n);
+}
+
+char *
+shf_smprintf(const char *fmt, ...)
+{
+ struct shf shf;
+ va_list args;
+
+ shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
+ va_start(args, fmt);
+ shf_vfprintf(&shf, fmt, args);
+ va_end(args);
+ /* NUL terminates */
+ return (shf_sclose(&shf));
+}
+
+#define FL_HASH 0x001 /* '#' seen */
+#define FL_PLUS 0x002 /* '+' seen */
+#define FL_RIGHT 0x004 /* '-' seen */
+#define FL_BLANK 0x008 /* ' ' seen */
+#define FL_SHORT 0x010 /* 'h' seen */
+#define FL_LONG 0x020 /* 'l' seen */
+#define FL_ZERO 0x040 /* '0' seen */
+#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 */
+
+ssize_t
+shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
+{
+ const char *s;
+ char c, *cp;
+ int tmp = 0, flags;
+ size_t field, precision, len;
+ unsigned long lnum;
+ /* %#o produces the longest output */
+ char numbuf[(8 * sizeof(long) + 2) / 3 + 1 + /* NUL */ 1];
+ /* this stuff for dealing with the buffer */
+ ssize_t nwritten = 0;
+
+#define VA(type) va_arg(args, type)
+
+ if (!fmt)
+ return (0);
+
+ while ((c = *fmt++)) {
+ if (c != '%') {
+ shf_putc(c, shf);
+ nwritten++;
+ 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 :-).
+ */
+ flags = 0;
+ field = precision = 0;
+ while ((c = *fmt++)) {
+ switch (c) {
+ case '#':
+ flags |= FL_HASH;
+ continue;
+
+ case '+':
+ flags |= FL_PLUS;
+ continue;
+
+ case '-':
+ flags |= FL_RIGHT;
+ continue;
+
+ case ' ':
+ flags |= FL_BLANK;
+ continue;
+
+ case '0':
+ if (!(flags & FL_DOT))
+ flags |= FL_ZERO;
+ continue;
+
+ case '.':
+ flags |= FL_DOT;
+ precision = 0;
+ continue;
+
+ case '*':
+ tmp = VA(int);
+ if (tmp < 0) {
+ if (flags & FL_DOT)
+ precision = 0;
+ else {
+ field = (unsigned int)-tmp;
+ flags |= FL_RIGHT;
+ }
+ } else if (flags & FL_DOT)
+ precision = (unsigned int)tmp;
+ else
+ field = (unsigned int)tmp;
+ 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 (ctype(c, C_DIGIT)) {
+ bool overflowed = false;
+
+ tmp = ksh_numdig(c);
+ while (ctype((c = *fmt++), C_DIGIT))
+ if (notok2mul(2147483647, tmp, 10))
+ overflowed = true;
+ else
+ tmp = tmp * 10 + ksh_numdig(c);
+ --fmt;
+ if (overflowed)
+ tmp = 0;
+ if (flags & FL_DOT)
+ precision = (unsigned int)tmp;
+ else
+ field = (unsigned int)tmp;
+ continue;
+ }
+ break;
+ }
+
+ if (!c)
+ /* nasty format */
+ break;
+
+ if (ctype(c, C_UPPER)) {
+ flags |= FL_UPPER;
+ c = ksh_tolower(c);
+ }
+
+ switch (c) {
+ 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);
+ *--cp = '\0';
+
+ switch (c) {
+ case 'd':
+ case 'i':
+ if (0 > (long)lnum) {
+ lnum = -(long)lnum;
+ tmp = 1;
+ } else
+ tmp = 0;
+ /* FALLTHROUGH */
+ case 'u':
+ do {
+ *--cp = digits_lc[lnum % 10];
+ lnum /= 10;
+ } while (lnum);
+
+ if (c != 'u') {
+ if (tmp)
+ *--cp = '-';
+ else if (flags & FL_PLUS)
+ *--cp = '+';
+ else if (flags & FL_BLANK)
+ *--cp = ' ';
+ }
+ break;
+
+ case 'o':
+ do {
+ *--cp = digits_lc[lnum & 0x7];
+ lnum >>= 3;
+ } while (lnum);
+
+ if ((flags & FL_HASH) && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case 'x': {
+ const char *digits = (flags & FL_UPPER) ?
+ digits_uc : digits_lc;
+ do {
+ *--cp = digits[lnum & 0xF];
+ lnum >>= 4;
+ } while (lnum);
+
+ if (flags & FL_HASH) {
+ *--cp = (flags & FL_UPPER) ? 'X' : 'x';
+ *--cp = '0';
+ }
+ }
+ }
+ len = numbuf + sizeof(numbuf) - 1 - (s = cp);
+ if (flags & FL_DOT) {
+ if (precision > len) {
+ field = precision;
+ flags |= FL_ZERO;
+ } else
+ /* no loss */
+ precision = len;
+ }
+ break;
+
+ case 's':
+ if ((s = VA(const char *)) == NULL)
+ s = "(null)";
+ else if (flags & FL_HASH) {
+ print_value_quoted(shf, s);
+ continue;
+ }
+ len = utf_mbswidth(s);
+ break;
+
+ case 'c':
+ flags &= ~FL_DOT;
+ c = (char)(VA(int));
+ /* FALLTHROUGH */
+
+ case '%':
+ default:
+ numbuf[0] = c;
+ numbuf[1] = 0;
+ s = numbuf;
+ len = 1;
+ break;
+ }
+
+ /*
+ * At this point s should point to a string that is to be
+ * formatted, and len should be the length of the string.
+ */
+ if (!(flags & FL_DOT) || len < precision)
+ precision = len;
+ if (field > precision) {
+ field -= precision;
+ if (!(flags & FL_RIGHT)) {
+ /* skip past sign or 0x when padding with 0 */
+ if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
+ if (ctype(*s, C_SPC | C_PLUS | C_MINUS)) {
+ shf_putc(*s, shf);
+ s++;
+ precision--;
+ nwritten++;
+ } else if (*s == '0') {
+ shf_putc(*s, shf);
+ s++;
+ nwritten++;
+ if (--precision &&
+ ksh_eq(*s, 'X', 'x')) {
+ shf_putc(*s, shf);
+ s++;
+ precision--;
+ nwritten++;
+ }
+ }
+ c = '0';
+ } else
+ c = flags & FL_ZERO ? '0' : ' ';
+ nwritten += field;
+ while (field--)
+ shf_putc(c, shf);
+ field = 0;
+ } else
+ c = ' ';
+ } else
+ field = 0;
+
+ nwritten += precision;
+ precision = utf_skipcols(s, precision, &tmp) - s;
+ while (precision--)
+ shf_putc(*s++, shf);
+
+ nwritten += field;
+ while (field--)
+ shf_putc(c, shf);
+ }
+
+ return (shf_error(shf) ? -1 : nwritten);
+}
+
+#ifdef MKSH_SHF_NO_INLINE
+int
+shf_getc(struct shf *shf)
+{
+ return (shf_getc_i(shf));
+}
+
+int
+shf_putc(int c, struct shf *shf)
+{
+ return (shf_putc_i(c, shf));
+}
+#endif
+
+#ifdef DEBUG
+const char *
+cstrerror(int errnum)
+{
+#undef strerror
+ return (strerror(errnum));
+#define strerror dontuse_strerror /* poisoned */
+}
+#elif !HAVE_STRERROR
+
+#if HAVE_SYS_ERRLIST
+#if !HAVE_SYS_ERRLIST_DECL
+extern const int sys_nerr;
+extern const char * const sys_errlist[];
+#endif
+#endif
+
+const char *
+cstrerror(int errnum)
+{
+ /* "Unknown error: " + sign + rough estimate + NUL */
+ static char errbuf[15 + 1 + (8 * sizeof(int) + 2) / 3 + 1];
+
+#if HAVE_SYS_ERRLIST
+ if (errnum > 0 && errnum < sys_nerr && sys_errlist[errnum])
+ return (sys_errlist[errnum]);
+#endif
+
+ switch (errnum) {
+ case 0:
+ return ("Undefined error: 0");
+ case EPERM:
+ return ("Operation not permitted");
+ case ENOENT:
+ return ("No such file or directory");
+#ifdef ESRCH
+ case ESRCH:
+ return ("No such process");
+#endif
+#ifdef E2BIG
+ case E2BIG:
+ return ("Argument list too long");
+#endif
+ case ENOEXEC:
+ return ("Exec format error");
+ case EBADF:
+ return ("Bad file descriptor");
+#ifdef ENOMEM
+ case ENOMEM:
+ return ("Cannot allocate memory");
+#endif
+ case EACCES:
+ return ("Permission denied");
+ case EEXIST:
+ return ("File exists");
+ case ENOTDIR:
+ return ("Not a directory");
+#ifdef EINVAL
+ case EINVAL:
+ return ("Invalid argument");
+#endif
+#ifdef ELOOP
+ case ELOOP:
+ return ("Too many levels of symbolic links");
+#endif
+ default:
+ shf_snprintf(errbuf, sizeof(errbuf),
+ "Unknown error: %d", errnum);
+ return (errbuf);
+ }
+}
+#endif
+
+/* fast character classes */
+const uint32_t tpl_ctypes[128] = {
+ /* 0x00 */
+ CiNUL, CiCNTRL, CiCNTRL, CiCNTRL,
+ CiCNTRL, CiCNTRL, CiCNTRL, CiCNTRL,
+ CiCNTRL, CiTAB, CiNL, CiSPX,
+ CiSPX, CiCR, CiCNTRL, CiCNTRL,
+ /* 0x10 */
+ CiCNTRL, CiCNTRL, CiCNTRL, CiCNTRL,
+ CiCNTRL, CiCNTRL, CiCNTRL, CiCNTRL,
+ CiCNTRL, CiCNTRL, CiCNTRL, CiCNTRL,
+ CiCNTRL, CiCNTRL, CiCNTRL, CiCNTRL,
+ /* 0x20 */
+ CiSP, CiALIAS | CiVAR1, CiQC, CiHASH,
+ CiSS, CiPERCT, CiQCL, CiQC,
+ CiQCL, CiQCL, CiQCX | CiVAR1, CiPLUS,
+ CiALIAS, CiMINUS, CiALIAS, CiQCM,
+ /* 0x30 */
+ CiOCTAL, CiOCTAL, CiOCTAL, CiOCTAL,
+ CiOCTAL, CiOCTAL, CiOCTAL, CiOCTAL,
+ CiDIGIT, CiDIGIT, CiCOLON, CiQCL,
+ CiANGLE, CiEQUAL, CiANGLE, CiQUEST,
+ /* 0x40 */
+ CiALIAS | CiVAR1, CiUPPER | CiHEXLT,
+ CiUPPER | CiHEXLT, CiUPPER | CiHEXLT,
+ CiUPPER | CiHEXLT, CiUPPER | CiHEXLT,
+ CiUPPER | CiHEXLT, CiUPPER,
+ CiUPPER, CiUPPER, CiUPPER, CiUPPER,
+ CiUPPER, CiUPPER, CiUPPER, CiUPPER,
+ /* 0x50 */
+ CiUPPER, CiUPPER, CiUPPER, CiUPPER,
+ CiUPPER, CiUPPER, CiUPPER, CiUPPER,
+ CiUPPER, CiUPPER, CiUPPER, CiQCX | CiBRACK,
+ CiQCX, CiBRACK, CiQCM, CiUNDER,
+ /* 0x60 */
+ CiGRAVE, CiLOWER | CiHEXLT,
+ CiLOWER | CiHEXLT, CiLOWER | CiHEXLT,
+ CiLOWER | CiHEXLT, CiLOWER | CiHEXLT,
+ CiLOWER | CiHEXLT, CiLOWER,
+ CiLOWER, CiLOWER, CiLOWER, CiLOWER,
+ CiLOWER, CiLOWER, CiLOWER, CiLOWER,
+ /* 0x70 */
+ CiLOWER, CiLOWER, CiLOWER, CiLOWER,
+ CiLOWER, CiLOWER, CiLOWER, CiLOWER,
+ CiLOWER, CiLOWER, CiLOWER, CiCURLY,
+ CiQCL, CiCURLY, CiQCM, CiCNTRL
+};
+
+void
+set_ifs(const char *s)
+{
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+ int i = 256;
+
+ memset(ksh_ctypes, 0, sizeof(ksh_ctypes));
+ while (i--)
+ if (ebcdic_map[i] < 0x80U)
+ ksh_ctypes[i] = tpl_ctypes[ebcdic_map[i]];
+#else
+ memcpy(ksh_ctypes, tpl_ctypes, sizeof(tpl_ctypes));
+ memset((char *)ksh_ctypes + sizeof(tpl_ctypes), '\0',
+ sizeof(ksh_ctypes) - sizeof(tpl_ctypes));
+#endif
+ ifs0 = *s;
+ while (*s)
+ ksh_ctypes[ord(*s++)] |= CiIFS;
+}
+
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+#include <locale.h>
+
+/*
+ * Many headaches with EBCDIC:
+ * 1. There are numerous EBCDIC variants, and it is not feasible for us
+ * to support them all. But we can support the EBCDIC code pages that
+ * contain all (most?) of the characters in ASCII, and these
+ * usually tend to agree on the code points assigned to the ASCII
+ * subset. If you need a representative example, look at EBCDIC 1047,
+ * which is first among equals in the IBM MVS development
+ * environment: https://en.wikipedia.org/wiki/EBCDIC_1047
+ * Unfortunately, the square brackets are not consistently mapped,
+ * and for certain reasons, we need an unambiguous bijective
+ * mapping between EBCDIC and "extended ASCII".
+ * 2. Character ranges that are contiguous in ASCII, like the letters
+ * in [A-Z], are broken up into segments (i.e. [A-IJ-RS-Z]), so we
+ * can't implement e.g. islower() as { return c >= 'a' && c <= 'z'; }
+ * because it will also return true for a handful of extraneous
+ * characters (like the plus-minus sign at 0x8F in EBCDIC 1047, a
+ * little after 'i'). But at least '_' is not one of these.
+ * 3. The normal [0-9A-Za-z] characters are at codepoints beyond 0x80.
+ * Not only do they require all 8 bits instead of 7, if chars are
+ * signed, they will have negative integer values! Something like
+ * (c - 'A') could actually become (c + 63)! Use the ord() macro to
+ * ensure you're getting a value in [0, 255] (ORD for constants).
+ * 4. '\n' is actually NL (0x15, U+0085) instead of LF (0x25, U+000A).
+ * EBCDIC has a proper newline character instead of "emulating" one
+ * with line feeds, although this is mapped to LF for our purposes.
+ * 5. Note that it is possible to compile programs in ASCII mode on IBM
+ * mainframe systems, using the -qascii option to the XL C compiler.
+ * We can determine the build mode by looking at __CHARSET_LIB:
+ * 0 == EBCDIC, 1 == ASCII
+ */
+
+void
+ebcdic_init(void)
+{
+ int i = 256;
+ unsigned char t;
+ bool mapcache[256];
+
+ while (i--)
+ ebcdic_rtt_toascii[i] = i;
+ memset(ebcdic_rtt_fromascii, 0xFF, sizeof(ebcdic_rtt_fromascii));
+ setlocale(LC_ALL, "");
+#ifdef MKSH_EBCDIC
+ if (__etoa_l(ebcdic_rtt_toascii, 256) != 256) {
+ write(2, "mksh: could not map EBCDIC to ASCII\n", 36);
+ exit(255);
+ }
+#endif
+
+ memset(mapcache, 0, sizeof(mapcache));
+ i = 256;
+ while (i--) {
+ t = ebcdic_rtt_toascii[i];
+ /* ensure unique round-trip capable mapping */
+ if (mapcache[t]) {
+ write(2, "mksh: duplicate EBCDIC to ASCII mapping\n", 40);
+ exit(255);
+ }
+ /*
+ * since there are 256 input octets, this also ensures
+ * the other mapping direction is completely filled
+ */
+ mapcache[t] = true;
+ /* fill the complete round-trip map */
+ ebcdic_rtt_fromascii[t] = i;
+ /*
+ * Only use the converted value if it's in the range
+ * [0x00; 0x7F], which I checked; the "extended ASCII"
+ * characters can be any encoding, not just Latin1,
+ * and the C1 control characters other than NEL are
+ * hopeless, but we map EBCDIC NEL to ASCII LF so we
+ * cannot even use C1 NEL.
+ * If ever we map to UCS, bump the table width to
+ * an unsigned int, and or the raw unconverted EBCDIC
+ * values with 0x01000000 instead.
+ */
+ if (t < 0x80U)
+ ebcdic_map[i] = (unsigned short)ord(t);
+ else
+ ebcdic_map[i] = (unsigned short)(0x100U | ord(i));
+ }
+ if (ebcdic_rtt_toascii[0] || ebcdic_rtt_fromascii[0] || ebcdic_map[0]) {
+ write(2, "mksh: NUL not at position 0\n", 28);
+ exit(255);
+ }
+}
+#endif
diff --git a/shells/mksh/files/strlcpy.c b/shells/mksh/files/strlcpy.c
new file mode 100644
index 00000000000..ba8581ef68a
--- /dev/null
+++ b/shells/mksh/files/strlcpy.c
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 2006, 2008, 2009, 2013
+ * mirabilos <m@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.10 2015/11/29 17:05:02 tg Exp $");
+
+/*
+ * 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.
+ */
+#undef strlcpy
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ const char *s = src;
+
+ if (siz == 0)
+ goto traverse_src;
+
+ /* copy as many chars as will fit */
+ while (--siz && (*dst++ = *s++))
+ ;
+
+ /* not enough room in dst */
+ if (siz == 0) {
+ /* safe to NUL-terminate dst since we copied <= siz-1 chars */
+ *dst = '\0';
+ traverse_src:
+ /* traverse rest of src */
+ while (*s++)
+ ;
+ }
+
+ /* count does not include NUL */
+ return ((size_t)(s - src - 1));
+}
diff --git a/shells/mksh/files/syn.c b/shells/mksh/files/syn.c
new file mode 100644
index 00000000000..3387cf53ba1
--- /dev/null
+++ b/shells/mksh/files/syn.c
@@ -0,0 +1,1187 @@
+/* $OpenBSD: syn.c,v 1.30 2015/09/01 13:12:31 tedu Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018, 2020
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.128 2020/03/31 00:30:05 tg Exp $");
+
+struct nesting_state {
+ int start_token; /* token than began nesting (eg, FOR) */
+ int start_line; /* line nesting began on */
+};
+
+struct yyrecursive_state {
+ struct ioword *old_heres[HERES];
+ struct yyrecursive_state *next;
+ struct ioword **old_herep;
+ int old_symbol;
+ unsigned int old_nesting_type;
+ bool old_reject;
+};
+
+static void yyparse(bool);
+static struct op *pipeline(int, int);
+static struct op *andor(int);
+static struct op *c_list(int, bool);
+static struct ioword *synio(int);
+static struct op *nested(int, int, int, int);
+static struct op *get_command(int, int);
+static struct op *dogroup(int);
+static struct op *thenpart(int);
+static struct op *elsepart(int);
+static struct op *caselist(int);
+static struct op *casepart(int, int);
+static struct op *function_body(char *, int, bool);
+static char **wordlist(int);
+static struct op *block(int, struct op *, struct op *);
+static struct op *newtp(int);
+static void syntaxerr(const char *) MKSH_A_NORETURN;
+static void nesting_push(struct nesting_state *, int);
+static void nesting_pop(struct nesting_state *);
+static int inalias(struct source *) MKSH_A_PURE;
+static Test_op dbtestp_isa(Test_env *, Test_meta);
+static const char *dbtestp_getopnd(Test_env *, Test_op, bool);
+static int dbtestp_eval(Test_env *, Test_op, const char *,
+ const char *, bool);
+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 bool reject; /* token(cf) gets symbol again */
+static int symbol; /* yylex value */
+
+#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 ((unsigned int)token(cf) != (unsigned int)(c)) \
+ syntaxerr(NULL); \
+} while (/* CONSTCOND */ 0)
+
+static const char Tcbrace[] = "}";
+static const char Tesac[] = "esac";
+
+static void
+yyparse(bool doalias)
+{
+ int c;
+
+ ACCEPT;
+
+ outtree = c_list(doalias ? ALIAS : 0, source->type == SSTRING);
+ c = tpeek(0);
+ if (c == 0 && !outtree)
+ outtree = newtp(TEOF);
+ else if (!cinttype(c, C_LF | C_NUL))
+ syntaxerr(NULL);
+}
+
+static struct op *
+pipeline(int cf, int sALIAS)
+{
+ struct op *t, *p, *tl = NULL;
+
+ t = get_command(cf, sALIAS);
+ if (t != NULL) {
+ while (token(0) == '|') {
+ if ((p = get_command(CONTIN, sALIAS)) == NULL)
+ syntaxerr(NULL);
+ if (tl == NULL)
+ t = tl = block(TPIPE, t, p);
+ else
+ tl = tl->right = block(TPIPE, tl->right, p);
+ }
+ REJECT;
+ }
+ return (t);
+}
+
+static struct op *
+andor(int sALIAS)
+{
+ struct op *t, *p;
+ int c;
+
+ t = pipeline(0, sALIAS);
+ if (t != NULL) {
+ while ((c = token(0)) == LOGAND || c == LOGOR) {
+ if ((p = pipeline(CONTIN, sALIAS)) == NULL)
+ syntaxerr(NULL);
+ t = block(c == LOGAND? TAND: TOR, t, p);
+ }
+ REJECT;
+ }
+ return (t);
+}
+
+static struct op *
+c_list(int sALIAS, bool multi)
+{
+ struct op *t = NULL, *p, *tl = NULL;
+ int c;
+ bool have_sep;
+
+ while (/* CONSTCOND */ 1) {
+ p = andor(sALIAS);
+ /*
+ * 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 = true;
+ if (c == '\n' && (multi || inalias(source))) {
+ if (!p)
+ /* ignore blank lines */
+ continue;
+ } else if (!p)
+ break;
+ else if (c == '&' || c == COPROC)
+ p = block(c == '&' ? TASYNC : TCOPROC, p, NULL);
+ else if (c != ';')
+ have_sep = false;
+ if (!t)
+ t = p;
+ else if (!tl)
+ t = tl = block(TLIST, t, p);
+ else
+ tl = tl->right = block(TLIST, tl->right, p);
+ if (!have_sep)
+ break;
+ }
+ REJECT;
+ return (t);
+}
+
+static const char IONDELIM_delim[] = { CHAR, '<', CHAR, '<', EOS };
+
+static struct ioword *
+synio(int cf)
+{
+ struct ioword *iop;
+ static struct ioword *nextiop;
+ bool ishere;
+
+ if (nextiop != NULL) {
+ iop = nextiop;
+ nextiop = NULL;
+ return (iop);
+ }
+
+ if (tpeek(cf) != REDIR)
+ return (NULL);
+ ACCEPT;
+ iop = yylval.iop;
+ ishere = (iop->ioflag & IOTYPE) == IOHERE;
+ if (iop->ioflag & IOHERESTR) {
+ musthave(LWORD, 0);
+ } else if (ishere && tpeek(HEREDELIM) == '\n') {
+ ACCEPT;
+ yylval.cp = wdcopy(IONDELIM_delim, ATEMP);
+ iop->ioflag |= IOEVAL | IONDELIM;
+ } else
+ musthave(LWORD, ishere ? HEREDELIM : 0);
+ if (ishere) {
+ iop->delim = yylval.cp;
+ if (*ident != 0 && !(iop->ioflag & IOHERESTR)) {
+ /* unquoted */
+ iop->ioflag |= IOEVAL;
+ }
+ if (herep > &heres[HERES - 1])
+ yyerror(Tf_toomany, "<<");
+ *herep++ = iop;
+ } else
+ iop->ioname = yylval.cp;
+
+ if (iop->ioflag & IOBASH) {
+ char *cp;
+
+ nextiop = alloc(sizeof(*iop), ATEMP);
+ nextiop->ioname = cp = alloc(3, ATEMP);
+ *cp++ = CHAR;
+ *cp++ = digits_lc[iop->unit % 10];
+ *cp = EOS;
+
+ iop->ioflag &= ~IOBASH;
+ nextiop->unit = 2;
+ nextiop->ioflag = IODUP;
+ nextiop->delim = NULL;
+ nextiop->heredoc = NULL;
+ }
+ return (iop);
+}
+
+static struct op *
+nested(int type, int smark, int emark, int sALIAS)
+{
+ struct op *t;
+ struct nesting_state old_nesting;
+
+ nesting_push(&old_nesting, smark);
+ t = c_list(sALIAS, true);
+ musthave(emark, KEYWORD|sALIAS);
+ nesting_pop(&old_nesting);
+ return (block(type, t, NULL));
+}
+
+static const char builtin_cmd[] = {
+ QCHAR, '\\', CHAR, 'b', CHAR, 'u', CHAR, 'i',
+ CHAR, 'l', CHAR, 't', CHAR, 'i', CHAR, 'n', EOS
+};
+static const char let_cmd[] = {
+ CHAR, 'l', CHAR, 'e', CHAR, 't', EOS
+};
+static const char setA_cmd0[] = {
+ CHAR, 's', CHAR, 'e', CHAR, 't', EOS
+};
+static const char setA_cmd1[] = {
+ CHAR, '-', CHAR, 'A', EOS
+};
+static const char setA_cmd2[] = {
+ CHAR, '-', CHAR, '-', EOS
+};
+
+static struct op *
+get_command(int cf, int sALIAS)
+{
+ struct op *t;
+ int c, iopn = 0, syniocf, lno;
+ struct ioword *iop, **iops;
+ XPtrV args, vars;
+ struct nesting_state old_nesting;
+ bool check_decl_utility;
+
+ /* NUFILE is small enough to leave this addition unchecked */
+ iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP);
+ XPinit(args, 16);
+ XPinit(vars, 16);
+
+ syniocf = KEYWORD|sALIAS;
+ switch (c = token(cf|KEYWORD|sALIAS|CMDASN)) {
+ default:
+ REJECT;
+ afree(iops, ATEMP);
+ XPfree(args);
+ XPfree(vars);
+ /* empty line */
+ return (NULL);
+
+ case LWORD:
+ case REDIR:
+ REJECT;
+ syniocf &= ~(KEYWORD|sALIAS);
+ t = newtp(TCOM);
+ t->lineno = source->line;
+ goto get_command_start;
+
+ get_command_loop:
+ if (XPsize(args) == 0) {
+ get_command_start:
+ check_decl_utility = true;
+ cf = sALIAS | CMDASN;
+ } else if (t->u.evalflags)
+ cf = CMDWORD | CMDASN;
+ else
+ cf = CMDWORD;
+
+ switch (tpeek(cf)) {
+ case REDIR:
+ while ((iop = synio(cf)) != NULL) {
+ if (iopn >= NUFILE)
+ yyerror(Tf_toomany, Tredirection);
+ iops[iopn++] = iop;
+ }
+ goto get_command_loop;
+
+ case LWORD:
+ ACCEPT;
+ if (check_decl_utility) {
+ struct tbl *tt = get_builtin(ident);
+ uint32_t flag;
+
+ flag = tt ? tt->flag : 0;
+ if (flag & DECL_UTIL)
+ t->u.evalflags = DOVACHECK;
+ if (!(flag & DECL_FWDR))
+ check_decl_utility = false;
+ }
+ if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+ is_wdvarassign(yylval.cp))
+ XPput(vars, yylval.cp);
+ else
+ XPput(args, yylval.cp);
+ goto get_command_loop;
+
+ case ORD('(' /*)*/):
+ if (XPsize(args) == 0 && XPsize(vars) == 1 &&
+ is_wdvarassign(yylval.cp)) {
+ char *tcp;
+
+ /* wdarrassign: foo=(bar) */
+ ACCEPT;
+
+ /* manipulate the vars string */
+ tcp = XPptrv(vars)[(vars.len = 0)];
+ /* 'varname=' -> 'varname' */
+ tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
+
+ /* construct new args strings */
+ XPput(args, wdcopy(builtin_cmd, ATEMP));
+ XPput(args, wdcopy(setA_cmd0, ATEMP));
+ XPput(args, wdcopy(setA_cmd1, ATEMP));
+ XPput(args, tcp);
+ XPput(args, wdcopy(setA_cmd2, ATEMP));
+
+ /* slurp in words till closing paren */
+ while (token(CONTIN) == LWORD)
+ XPput(args, yylval.cp);
+ if (symbol != /*(*/ ')')
+ syntaxerr(NULL);
+ break;
+ }
+
+ afree(t, ATEMP);
+
+ /*
+ * Check for "> foo (echo hi)" which AT&T ksh allows
+ * (not POSIX, but not disallowed)
+ */
+ if (XPsize(args) == 0 && XPsize(vars) == 0) {
+ ACCEPT;
+ goto Subshell;
+ }
+
+ /* must be a function */
+ if (iopn != 0 || XPsize(args) != 1 || XPsize(vars) != 0)
+ syntaxerr(NULL);
+ ACCEPT;
+ musthave(/*(*/ ')', 0);
+ t = function_body(XPptrv(args)[0],
+ sALIAS, false);
+ break;
+ }
+ break;
+
+ case ORD('(' /*)*/): {
+ unsigned int subshell_nesting_type_saved;
+ Subshell:
+ subshell_nesting_type_saved = subshell_nesting_type;
+ subshell_nesting_type = ORD(')');
+ t = nested(TPAREN, ORD('('), ORD(')'), sALIAS);
+ subshell_nesting_type = subshell_nesting_type_saved;
+ break;
+ }
+
+ case ORD('{' /*}*/):
+ t = nested(TBRACE, ORD('{'), ORD('}'), sALIAS);
+ break;
+
+ case MDPAREN:
+ /* leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+ lno = source->line;
+ ACCEPT;
+ switch (token(LETEXPR)) {
+ case LWORD:
+ break;
+ case ORD('(' /*)*/):
+ c = ORD('(');
+ goto Subshell;
+ default:
+ syntaxerr(NULL);
+ }
+ t = newtp(TCOM);
+ t->lineno = lno;
+ XPput(args, wdcopy(builtin_cmd, ATEMP));
+ XPput(args, wdcopy(let_cmd, ATEMP));
+ XPput(args, yylval.cp);
+ break;
+
+ case DBRACKET: /* [[ .. ]] */
+ /* leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+ t = newtp(TDBRACKET);
+ ACCEPT;
+ {
+ Test_env te;
+
+ te.flags = TEF_DBRACKET;
+ te.pos.av = &args;
+ te.isa = dbtestp_isa;
+ te.getopnd = dbtestp_getopnd;
+ te.eval = dbtestp_eval;
+ te.error = dbtestp_error;
+
+ test_parse(&te);
+ }
+ break;
+
+ case FOR:
+ case SELECT:
+ t = newtp((c == FOR) ? TFOR : TSELECT);
+ musthave(LWORD, CMDASN);
+ if (!is_wdvarname(yylval.cp, true))
+ yyerror("%s: bad identifier",
+ c == FOR ? "for" : Tselect);
+ strdupx(t->str, ident, ATEMP);
+ nesting_push(&old_nesting, c);
+ t->vars = wordlist(sALIAS);
+ t->left = dogroup(sALIAS);
+ nesting_pop(&old_nesting);
+ break;
+
+ case WHILE:
+ case UNTIL:
+ nesting_push(&old_nesting, c);
+ t = newtp((c == WHILE) ? TWHILE : TUNTIL);
+ t->left = c_list(sALIAS, true);
+ t->right = dogroup(sALIAS);
+ nesting_pop(&old_nesting);
+ break;
+
+ case CASE:
+ t = newtp(TCASE);
+ musthave(LWORD, 0);
+ t->str = yylval.cp;
+ nesting_push(&old_nesting, c);
+ t->left = caselist(sALIAS);
+ nesting_pop(&old_nesting);
+ break;
+
+ case IF:
+ nesting_push(&old_nesting, c);
+ t = newtp(TIF);
+ t->left = c_list(sALIAS, true);
+ t->right = thenpart(sALIAS);
+ musthave(FI, KEYWORD|sALIAS);
+ nesting_pop(&old_nesting);
+ break;
+
+ case BANG:
+ syniocf &= ~(KEYWORD|sALIAS);
+ t = pipeline(0, sALIAS);
+ if (t == NULL)
+ syntaxerr(NULL);
+ t = block(TBANG, NULL, t);
+ break;
+
+ case TIME:
+ syniocf &= ~(KEYWORD|sALIAS);
+ t = pipeline(0, sALIAS);
+ if (t && t->type == TCOM) {
+ t->str = alloc(2, ATEMP);
+ /* TF_* flags */
+ t->str[0] = '\0';
+ t->str[1] = '\0';
+ }
+ t = block(TTIME, t, NULL);
+ break;
+
+ case FUNCTION:
+ musthave(LWORD, 0);
+ t = function_body(yylval.cp, sALIAS, true);
+ break;
+ }
+
+ while ((iop = synio(syniocf)) != NULL) {
+ if (iopn >= NUFILE)
+ yyerror(Tf_toomany, Tredirection);
+ iops[iopn++] = iop;
+ }
+
+ if (iopn == 0) {
+ afree(iops, ATEMP);
+ t->ioact = NULL;
+ } else {
+ iops[iopn++] = NULL;
+ iops = aresize2(iops, iopn, sizeof(struct ioword *), ATEMP);
+ t->ioact = iops;
+ }
+
+ if (t->type == TCOM || t->type == TDBRACKET) {
+ XPput(args, NULL);
+ t->args = (const char **)XPclose(args);
+ XPput(vars, NULL);
+ t->vars = (char **)XPclose(vars);
+ } else {
+ XPfree(args);
+ XPfree(vars);
+ }
+
+ if (c == MDPAREN) {
+ t = block(TBRACE, t, NULL);
+ t->ioact = t->left->ioact;
+ t->left->ioact = NULL;
+ }
+
+ return (t);
+}
+
+static struct op *
+dogroup(int sALIAS)
+{
+ int c;
+ struct op *list;
+
+ c = token(CONTIN|KEYWORD|sALIAS);
+ /*
+ * 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...
+ */
+ if (c == DO)
+ c = DONE;
+ else if ((unsigned int)c == ORD('{'))
+ c = ORD('}');
+ else
+ syntaxerr(NULL);
+ list = c_list(sALIAS, true);
+ musthave(c, KEYWORD|sALIAS);
+ return (list);
+}
+
+static struct op *
+thenpart(int sALIAS)
+{
+ struct op *t;
+
+ musthave(THEN, KEYWORD|sALIAS);
+ t = newtp(0);
+ t->left = c_list(sALIAS, true);
+ if (t->left == NULL)
+ syntaxerr(NULL);
+ t->right = elsepart(sALIAS);
+ return (t);
+}
+
+static struct op *
+elsepart(int sALIAS)
+{
+ struct op *t;
+
+ switch (token(KEYWORD|sALIAS|CMDASN)) {
+ case ELSE:
+ if ((t = c_list(sALIAS, true)) == NULL)
+ syntaxerr(NULL);
+ return (t);
+
+ case ELIF:
+ t = newtp(TELIF);
+ t->left = c_list(sALIAS, true);
+ t->right = thenpart(sALIAS);
+ return (t);
+
+ default:
+ REJECT;
+ }
+ return (NULL);
+}
+
+static struct op *
+caselist(int sALIAS)
+{
+ struct op *t, *tl;
+ int c;
+
+ c = token(CONTIN|KEYWORD|sALIAS);
+ /* A {...} can be used instead of in...esac for case statements */
+ if (c == IN)
+ c = ESAC;
+ else if ((unsigned int)c == ORD('{'))
+ c = ORD('}');
+ else
+ syntaxerr(NULL);
+ t = tl = NULL;
+ /* no ALIAS here */
+ while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) {
+ struct op *tc = casepart(c, sALIAS);
+ if (tl == NULL)
+ t = tl = tc, tl->right = NULL;
+ else
+ tl->right = tc, tl = tc;
+ }
+ musthave(c, KEYWORD|sALIAS);
+ return (t);
+}
+
+static struct op *
+casepart(int endtok, int sALIAS)
+{
+ struct op *t;
+ XPtrV ptns;
+
+ XPinit(ptns, 16);
+ t = newtp(TPAT);
+ /* no ALIAS here */
+ if ((unsigned int)token(CONTIN | KEYWORD) != ORD('('))
+ REJECT;
+ do {
+ switch (token(0)) {
+ case LWORD:
+ break;
+ case ORD('}'):
+ case ESAC:
+ if (symbol != endtok) {
+ strdupx(yylval.cp, (unsigned int)symbol ==
+ ORD('}') ? Tcbrace : Tesac, ATEMP);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ syntaxerr(NULL);
+ }
+ XPput(ptns, yylval.cp);
+ } while (token(0) == '|');
+ REJECT;
+ XPput(ptns, NULL);
+ t->vars = (char **)XPclose(ptns);
+ musthave(ORD(')'), 0);
+
+ t->left = c_list(sALIAS, true);
+
+ /* initialise to default for ;; or omitted */
+ t->u.charflag = ORD(';');
+ /* SUSv4 requires the ;; except in the last casepart */
+ if ((tpeek(CONTIN|KEYWORD|sALIAS)) != endtok)
+ switch (symbol) {
+ default:
+ syntaxerr(NULL);
+ case BRKEV:
+ t->u.charflag = ORD('|');
+ if (0)
+ /* FALLTHROUGH */
+ case BRKFT:
+ t->u.charflag = ORD('&');
+ /* FALLTHROUGH */
+ case BREAK:
+ /* initialised above, but we need to eat the token */
+ ACCEPT;
+ }
+ return (t);
+}
+
+static struct op *
+function_body(char *name, int sALIAS,
+ /* function foo { ... } vs foo() { .. } */
+ bool ksh_func)
+{
+ char *sname, *p;
+ struct op *t;
+
+ 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|C_SPC covers all but adds # * ? [ ]
+ */
+ for (p = sname; *p; p++)
+ if (ctype(*p, C_QUOTE | C_SPC))
+ yyerror(Tinvname, sname, Tfunction);
+
+ /*
+ * 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 ((unsigned int)tpeek(CONTIN|KEYWORD|sALIAS) == ORD('(' /*)*/)) {
+ /* function foo () { //}*/
+ ACCEPT;
+ musthave(ORD(/*(*/ ')'), 0);
+ /* degrade to POSIX function */
+ ksh_func = false;
+ }
+ musthave(ORD('{' /*}*/), CONTIN|KEYWORD|sALIAS);
+ REJECT;
+ }
+
+ t = newtp(TFUNCT);
+ t->str = sname;
+ t->u.ksh_func = tobool(ksh_func);
+ t->lineno = source->line;
+
+ if ((t->left = get_command(CONTIN, sALIAS)) == NULL) {
+ char *tv;
+ /*
+ * 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] = QCHAR;
+ tv[1] = ':';
+ tv[2] = EOS;
+ t->left->args[1] = NULL;
+ t->left->vars = alloc(sizeof(char *), ATEMP);
+ t->left->vars[0] = NULL;
+ t->left->lineno = 1;
+ }
+
+ return (t);
+}
+
+static char **
+wordlist(int sALIAS)
+{
+ int c;
+ XPtrV args;
+
+ XPinit(args, 16);
+ /* POSIX does not do alias expansion here... */
+ if ((c = token(CONTIN|KEYWORD|sALIAS)) != IN) {
+ if (c != ';')
+ /* non-POSIX, but AT&T ksh accepts a ; here */
+ REJECT;
+ return (NULL);
+ }
+ while ((c = token(0)) == LWORD)
+ XPput(args, yylval.cp);
+ if (c != '\n' && c != ';')
+ syntaxerr(NULL);
+ XPput(args, NULL);
+ return ((char **)XPclose(args));
+}
+
+/*
+ * supporting functions
+ */
+
+static struct op *
+block(int type, struct op *t1, struct op *t2)
+{
+ struct op *t;
+
+ t = newtp(type);
+ t->left = t1;
+ t->right = t2;
+ return (t);
+}
+
+static const struct tokeninfo {
+ const char *name;
+ short val;
+ short reserved;
+} tokentab[] = {
+ /* Reserved words */
+ { "if", IF, true },
+ { "then", THEN, true },
+ { "else", ELSE, true },
+ { "elif", ELIF, true },
+ { "fi", FI, true },
+ { "case", CASE, true },
+ { Tesac, ESAC, true },
+ { "for", FOR, true },
+ { Tselect, SELECT, true },
+ { "while", WHILE, true },
+ { "until", UNTIL, true },
+ { "do", DO, true },
+ { "done", DONE, true },
+ { "in", IN, true },
+ { Tfunction, FUNCTION, true },
+ { Ttime, TIME, true },
+ { "{", ORD('{'), true },
+ { Tcbrace, ORD('}'), true },
+ { "!", BANG, true },
+ { "[[", DBRACKET, true },
+ /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
+ { "&&", LOGAND, false },
+ { "||", LOGOR, false },
+ { ";;", BREAK, false },
+ { ";|", BRKEV, false },
+ { ";&", BRKFT, false },
+ { "((", MDPAREN, false },
+ { "|&", COPROC, false },
+ /* and some special cases... */
+ { "newline", ORD('\n'), false },
+ { NULL, 0, false }
+};
+
+void
+initkeywords(void)
+{
+ struct tokeninfo const *tt;
+ struct tbl *p;
+
+ ktinit(APERM, &keywords,
+ /* currently 28 keywords: 75% of 64 = 2^6 */
+ 6);
+ for (tt = tokentab; tt->name; tt++) {
+ if (tt->reserved) {
+ p = ktenter(&keywords, tt->name, hash(tt->name));
+ p->flag |= DEFINED|ISSET;
+ p->type = CKEYWD;
+ p->val.i = tt->val;
+ }
+ }
+}
+
+static void
+syntaxerr(const char *what)
+{
+ /* 23<<- is the longest redirection, I think */
+ char redir[8];
+ const char *s;
+ struct tokeninfo const *tt;
+ int c;
+
+ if (!what)
+ what = Tunexpected;
+ REJECT;
+ c = token(0);
+ Again:
+ switch (c) {
+ case 0:
+ if (nesting.start_token) {
+ c = nesting.start_token;
+ source->errline = nesting.start_line;
+ what = "unmatched";
+ goto Again;
+ }
+ /* don't quote the EOF */
+ yyerror("%s: unexpected EOF", Tsynerr);
+ /* NOTREACHED */
+
+ case LWORD:
+ s = snptreef(NULL, 32, Tf_S, yylval.cp);
+ break;
+
+ case REDIR:
+ s = snptreef(redir, sizeof(redir), Tft_R, yylval.iop);
+ break;
+
+ default:
+ for (tt = tokentab; tt->name; tt++)
+ if (tt->val == c)
+ break;
+ if (tt->name)
+ s = tt->name;
+ else {
+ if (c > 0 && c < 256) {
+ redir[0] = c;
+ redir[1] = '\0';
+ } else
+ shf_snprintf(redir, sizeof(redir),
+ "?%d", c);
+ s = redir;
+ }
+ }
+ yyerror(Tf_sD_s_qs, Tsynerr, what, s);
+}
+
+static void
+nesting_push(struct nesting_state *save, int tok)
+{
+ *save = nesting;
+ nesting.start_token = tok;
+ nesting.start_line = source->line;
+}
+
+static void
+nesting_pop(struct nesting_state *saved)
+{
+ nesting = *saved;
+}
+
+static struct op *
+newtp(int type)
+{
+ struct op *t;
+
+ t = alloc(sizeof(struct op), ATEMP);
+ t->type = type;
+ t->u.evalflags = 0;
+ t->args = NULL;
+ t->vars = NULL;
+ t->ioact = NULL;
+ t->left = t->right = NULL;
+ t->str = NULL;
+ return (t);
+}
+
+struct op *
+compile(Source *s, bool skiputf8bom, bool doalias)
+{
+ nesting.start_token = 0;
+ nesting.start_line = 0;
+ herep = heres;
+ source = s;
+ if (skiputf8bom)
+ yyskiputf8bom();
+ yyparse(doalias);
+ return (outtree);
+}
+
+/* Check if we are in the middle of reading an alias */
+static int
+inalias(struct source *s)
+{
+ while (s && s->type == SALIAS) {
+ if (!(s->flags & SF_ALIASEND))
+ return (1);
+ s = s->next;
+ }
+ return (0);
+}
+
+
+/*
+ * 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.
+ */
+static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS };
+static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS };
+static const char dbtest_not[] = { CHAR, '!', EOS };
+static const char dbtest_oparen[] = { CHAR, '(', EOS };
+static const char dbtest_cparen[] = { CHAR, ')', EOS };
+const char * const dbtest_tokens[] = {
+ dbtest_or, dbtest_and, dbtest_not,
+ dbtest_oparen, dbtest_cparen
+};
+static const char db_close[] = { CHAR, ']', CHAR, ']', EOS };
+static const char db_lthan[] = { CHAR, '<', EOS };
+static const char db_gthan[] = { CHAR, '>', EOS };
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+dbtestp_isa(Test_env *te, Test_meta meta)
+{
+ int c = tpeek(CMDASN | (meta == TM_BINOP ? 0 : CONTIN));
+ bool uqword;
+ char *save = NULL;
+ Test_op ret = TO_NONOP;
+
+ /* unquoted word? */
+ uqword = c == LWORD && *ident;
+
+ if (meta == TM_OR)
+ ret = c == LOGOR ? TO_NONNULL : TO_NONOP;
+ else if (meta == TM_AND)
+ ret = c == LOGAND ? TO_NONNULL : TO_NONOP;
+ else if (meta == TM_NOT)
+ ret = (uqword && !strcmp(yylval.cp,
+ dbtest_tokens[(int)TM_NOT])) ? TO_NONNULL : TO_NONOP;
+ else if (meta == TM_OPAREN)
+ ret = (unsigned int)c == ORD('(') /*)*/ ? TO_NONNULL : TO_NONOP;
+ else if (meta == TM_CPAREN)
+ ret = (unsigned int)c == /*(*/ ORD(')') ? TO_NONNULL : TO_NONOP;
+ else if (meta == TM_UNOP || meta == TM_BINOP) {
+ if (meta == TM_BINOP && c == REDIR &&
+ (yylval.iop->ioflag == IOREAD ||
+ yylval.iop->ioflag == IOWRITE)) {
+ ret = TO_NONNULL;
+ save = wdcopy(yylval.iop->ioflag == IOREAD ?
+ db_lthan : db_gthan, ATEMP);
+ } else if (uqword && (ret = test_isop(meta, ident)))
+ save = yylval.cp;
+ } else
+ /* meta == TM_END */
+ ret = (uqword && !strcmp(yylval.cp,
+ db_close)) ? TO_NONNULL : TO_NONOP;
+ if (ret != TO_NONOP) {
+ ACCEPT;
+ if ((unsigned int)meta < NELEM(dbtest_tokens))
+ save = wdcopy(dbtest_tokens[(int)meta], ATEMP);
+ if (save)
+ XPput(*te->pos.av, save);
+ }
+ return (ret);
+}
+
+static const char *
+dbtestp_getopnd(Test_env *te, Test_op op MKSH_A_UNUSED,
+ bool do_eval MKSH_A_UNUSED)
+{
+ int c = tpeek(CMDASN);
+
+ if (c != LWORD)
+ return (NULL);
+
+ ACCEPT;
+ XPput(*te->pos.av, yylval.cp);
+
+ return (null);
+}
+
+static int
+dbtestp_eval(Test_env *te MKSH_A_UNUSED, Test_op op MKSH_A_UNUSED,
+ const char *opnd1 MKSH_A_UNUSED, const char *opnd2 MKSH_A_UNUSED,
+ bool do_eval MKSH_A_UNUSED)
+{
+ return (1);
+}
+
+static void
+dbtestp_error(Test_env *te, int offset, const char *msg)
+{
+ te->flags |= TEF_ERROR;
+
+ if (offset < 0) {
+ REJECT;
+ /* Kludgy to say the least... */
+ symbol = LWORD;
+ yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) +
+ offset);
+ }
+ 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 (ctype(*s, C_DIGIT)) {
+ tt.tv_sec = tv->tv_sec * 10 + ksh_numdig(*s++);
+ /*XXX this overflow check maybe UB */
+ 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 (ctype(*s, C_DIGIT)) {
+ tv->tv_usec += i * ksh_numdig(*s++);
+ if (i == 1)
+ break;
+ i /= 10;
+ }
+ /* check for junk after fractional part */
+ while (ctype(*s, C_DIGIT))
+ ++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(int subtype)
+{
+ struct op *t;
+ char *cp;
+ struct yyrecursive_state *ys;
+ unsigned int stok, etok;
+
+ if (subtype != COMSUB) {
+ stok = ORD('{');
+ etok = ORD('}');
+ } else {
+ stok = ORD('(');
+ etok = ORD(')');
+ }
+
+ ys = alloc(sizeof(struct yyrecursive_state), ATEMP);
+
+ /* tell the lexer to accept a closing parenthesis as EOD */
+ ys->old_nesting_type = subshell_nesting_type;
+ subshell_nesting_type = etok;
+
+ /* push reject state, parse recursively, pop reject state */
+ ys->old_reject = reject;
+ ys->old_symbol = symbol;
+ ACCEPT;
+ memcpy(ys->old_heres, heres, sizeof(heres));
+ ys->old_herep = herep;
+ herep = heres;
+ ys->next = e->yyrecursive_statep;
+ e->yyrecursive_statep = ys;
+ /* we use TPAREN as a helper container here */
+ t = nested(TPAREN, stok, etok, ALIAS);
+ yyrecursive_pop(false);
+
+ /* t->left because nested(TPAREN, ...) hides our goodies there */
+ cp = snptreef(NULL, 0, Tf_T, t->left);
+ tfree(t, ATEMP);
+
+ return (cp);
+}
+
+void
+yyrecursive_pop(bool popall)
+{
+ struct yyrecursive_state *ys;
+
+ popnext:
+ if (!(ys = e->yyrecursive_statep))
+ return;
+ e->yyrecursive_statep = ys->next;
+
+ memcpy(heres, ys->old_heres, sizeof(heres));
+ herep = ys->old_herep;
+ reject = ys->old_reject;
+ symbol = ys->old_symbol;
+
+ subshell_nesting_type = ys->old_nesting_type;
+
+ afree(ys, ATEMP);
+ if (popall)
+ goto popnext;
+}
diff --git a/shells/mksh/files/tree.c b/shells/mksh/files/tree.c
new file mode 100644
index 00000000000..335e3fba37f
--- /dev/null
+++ b/shells/mksh/files/tree.c
@@ -0,0 +1,1176 @@
+/* $OpenBSD: tree.c,v 1.21 2015/09/01 13:12:31 tedu Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2015, 2016, 2017
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.97 2018/10/20 18:46:00 tg Exp $");
+
+#define INDENT 8
+
+static void ptree(struct op *, int, struct shf *);
+static void pioact(struct shf *, struct ioword *);
+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;
+
+static const char Telif_pT[] = "elif %T";
+
+/*
+ * print a command tree
+ */
+static void
+ptree(struct op *t, int indent, struct shf *shf)
+{
+ const char **w;
+ struct ioword **ioact;
+ struct op *t1;
+ int i;
+ const char *ccp;
+
+ Chain:
+ if (t == NULL)
+ return;
+ switch (t->type) {
+ case TCOM:
+ prevent_semicolon = false;
+ /* special-case 'var=<<EOF' (cf. exec.c:execute) */
+ if (t->args &&
+ /* we have zero arguments, i.e. no program 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]->ioflag & 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) ||
+ /* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR &&
+ ccp[3] == '=' && ccp[4] == EOS))) {
+ fptreef(shf, indent, Tf_S, t->vars[0]);
+ break;
+ }
+
+ if (t->vars) {
+ w = (const char **)t->vars;
+ while (*w)
+ fptreef(shf, indent, Tf_S_, *w++);
+ } else
+ shf_puts("#no-vars# ", shf);
+ if (t->args) {
+ w = t->args;
+ if (*w && **w == CHAR) {
+ char *cp = wdstrip(*w++, WDS_TPUTS);
+
+ if (valid_alias_name(cp))
+ shf_putc('\\', shf);
+ shf_puts(cp, shf);
+ shf_putc(' ', shf);
+ afree(cp, ATEMP);
+ }
+ while (*w)
+ fptreef(shf, indent, Tf_S_, *w++);
+ } else
+ shf_puts("#no-args# ", shf);
+ break;
+ case TEXEC:
+ t = t->left;
+ goto Chain;
+ case TPAREN:
+ fptreef(shf, indent + 2, "( %T) ", t->left);
+ break;
+ case TPIPE:
+ fptreef(shf, indent, "%T| ", t->left);
+ t = t->right;
+ goto Chain;
+ case TLIST:
+ fptreef(shf, indent, "%T%;", t->left);
+ t = t->right;
+ goto Chain;
+ case TOR:
+ case TAND:
+ fptreef(shf, indent, "%T%s %T",
+ t->left, (t->type == TOR) ? "||" : "&&", t->right);
+ break;
+ case TBANG:
+ shf_puts("! ", shf);
+ prevent_semicolon = false;
+ t = t->right;
+ goto Chain;
+ case TDBRACKET:
+ w = t->args;
+ shf_puts("[[", shf);
+ while (*w)
+ fptreef(shf, indent, Tf__S, *w++);
+ shf_puts(" ]] ", shf);
+ break;
+ case TSELECT:
+ case TFOR:
+ fptreef(shf, indent, "%s %s ",
+ (t->type == TFOR) ? "for" : Tselect, t->str);
+ if (t->vars != NULL) {
+ shf_puts("in ", shf);
+ w = (const char **)t->vars;
+ while (*w)
+ fptreef(shf, indent, Tf_S_, *w++);
+ fptreef(shf, indent, Tft_end);
+ }
+ fptreef(shf, indent + INDENT, "do%N%T", t->left);
+ fptreef(shf, indent, "%;done ");
+ break;
+ case TCASE:
+ fptreef(shf, indent, "case %S in", t->str);
+ for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+ fptreef(shf, indent, "%N(");
+ w = (const char **)t1->vars;
+ while (*w) {
+ fptreef(shf, indent, "%S%c", *w,
+ (w[1] != NULL) ? '|' : ')');
+ ++w;
+ }
+ fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left,
+ t1->u.charflag);
+ }
+ fptreef(shf, indent, "%Nesac ");
+ break;
+ case TELIF:
+ internal_errorf(TELIF_unexpected);
+ /* FALLTHROUGH */
+ case TIF:
+ i = 2;
+ t1 = t;
+ goto process_TIF;
+ do {
+ t1 = t1->right;
+ i = 0;
+ fptreef(shf, indent, Tft_end);
+ process_TIF:
+ /* 5 == strlen("elif ") */
+ fptreef(shf, indent + 5 - i, Telif_pT + i, t1->left);
+ t1 = t1->right;
+ if (t1->left != NULL) {
+ fptreef(shf, indent, Tft_end);
+ fptreef(shf, indent + INDENT, "%s%N%T",
+ "then", t1->left);
+ }
+ } while (t1->right && t1->right->type == TELIF);
+ if (t1->right != NULL) {
+ fptreef(shf, indent, Tft_end);
+ fptreef(shf, indent + INDENT, "%s%N%T",
+ "else", t1->right);
+ }
+ fptreef(shf, indent, "%;fi ");
+ break;
+ case TWHILE:
+ case TUNTIL:
+ /* 6 == strlen("while "/"until ") */
+ fptreef(shf, indent + 6, Tf_s_T,
+ (t->type == TWHILE) ? "while" : "until",
+ t->left);
+ fptreef(shf, indent, Tft_end);
+ fptreef(shf, indent + INDENT, "do%N%T", t->right);
+ fptreef(shf, indent, "%;done ");
+ break;
+ case TBRACE:
+ 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:
+ fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left);
+ break;
+ case TTIME:
+ fptreef(shf, indent, Tf_s_T, Ttime, t->left);
+ break;
+ default:
+ shf_puts("<botch>", shf);
+ prevent_semicolon = false;
+ break;
+ }
+ if ((ioact = t->ioact) != NULL) {
+ bool need_nl = false;
+
+ while (*ioact != NULL)
+ pioact(shf, *ioact++);
+ /* Print here documents after everything else... */
+ ioact = t->ioact;
+ while (*ioact != NULL) {
+ struct ioword *iop = *ioact++;
+
+ /* heredoc is NULL when tracing (set -x) */
+ if ((iop->ioflag & (IOTYPE | IOHERESTR)) == IOHERE &&
+ iop->heredoc) {
+ shf_putc('\n', shf);
+ shf_puts(iop->heredoc, shf);
+ fptreef(shf, indent, Tf_s,
+ evalstr(iop->delim, 0));
+ need_nl = true;
+ }
+ }
+ /*
+ * 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) {
+ shf_putc('\n', shf);
+ prevent_semicolon = true;
+ }
+ }
+}
+
+static void
+pioact(struct shf *shf, struct ioword *iop)
+{
+ unsigned short flag = iop->ioflag;
+ unsigned short type = flag & IOTYPE;
+ short expected;
+
+ expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 :
+ (type == IOCAT || type == IOWRITE) ? 1 :
+ (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit :
+ iop->unit + 1;
+ if (iop->unit != expected)
+ shf_fprintf(shf, Tf_d, (int)iop->unit);
+
+ switch (type) {
+ case IOREAD:
+ shf_putc('<', shf);
+ break;
+ case IOHERE:
+ shf_puts("<<", shf);
+ if (flag & IOSKIP)
+ shf_putc('-', shf);
+ else if (flag & IOHERESTR)
+ shf_putc('<', shf);
+ break;
+ case IOCAT:
+ shf_puts(">>", shf);
+ break;
+ case IOWRITE:
+ shf_putc('>', shf);
+ if (flag & IOCLOB)
+ shf_putc('|', shf);
+ break;
+ case IORDWR:
+ shf_puts("<>", shf);
+ break;
+ case IODUP:
+ shf_puts(flag & IORDUP ? "<&" : ">&", shf);
+ break;
+ }
+ /* name/delim are NULL when printing syntax errors */
+ if (type == IOHERE) {
+ if (iop->delim && !(iop->ioflag & IONDELIM))
+ wdvarput(shf, iop->delim, 0, WDS_TPUTS);
+ } else if (iop->ioname) {
+ if (flag & IONAMEXP)
+ print_value_quoted(shf, iop->ioname);
+ else
+ wdvarput(shf, iop->ioname, 0, WDS_TPUTS);
+ }
+ shf_putc(' ', shf);
+ prevent_semicolon = false;
+}
+
+/* variant of fputs for ptreef and wdstrip */
+static const char *
+wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
+{
+ int c;
+ const char *cs;
+
+ /*-
+ * problems:
+ * `...` -> $(...)
+ * 'foo' -> "foo"
+ * x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
+ * x${foo:-'hi'} -> x${foo:-hi}
+ * could change encoding to:
+ * OQUOTE ["'] ... CQUOTE ["']
+ * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case)
+ */
+ while (/* CONSTCOND */ 1)
+ switch (*wp++) {
+ case EOS:
+ return (--wp);
+ case ADELIM:
+ if (ord(*wp) == ORD(/*{*/ '}')) {
+ ++wp;
+ goto wdvarput_csubst;
+ }
+ /* FALLTHROUGH */
+ case CHAR:
+ c = ord(*wp++);
+ shf_putc(c, shf);
+ break;
+ case QCHAR:
+ c = ord(*wp++);
+ if (opmode & WDS_TPUTS)
+ switch (c) {
+ case ORD('\n'):
+ if (quotelevel == 0) {
+ c = ORD('\'');
+ shf_putc(c, shf);
+ shf_putc(ORD('\n'), shf);
+ }
+ break;
+ default:
+ if (quotelevel == 0)
+ /* FALLTHROUGH */
+ case ORD('"'):
+ case ORD('`'):
+ case ORD('$'):
+ case ORD('\\'):
+ shf_putc(ORD('\\'), shf);
+ break;
+ }
+ shf_putc(c, shf);
+ break;
+ case COMASUB:
+ case COMSUB:
+ shf_puts("$(", shf);
+ cs = ")";
+ if (ord(*wp) == ORD('(' /*)*/))
+ shf_putc(' ', shf);
+ pSUB:
+ while ((c = *wp++) != 0)
+ shf_putc(c, shf);
+ shf_puts(cs, shf);
+ break;
+ case FUNASUB:
+ case FUNSUB:
+ c = ORD(' ');
+ if (0)
+ /* FALLTHROUGH */
+ case VALSUB:
+ c = ORD('|');
+ shf_putc('$', shf);
+ shf_putc('{', shf);
+ shf_putc(c, shf);
+ cs = ";}";
+ goto pSUB;
+ case EXPRSUB:
+ shf_puts("$((", shf);
+ cs = "))";
+ goto pSUB;
+ case OQUOTE:
+ if (opmode & WDS_TPUTS) {
+ quotelevel++;
+ shf_putc('"', shf);
+ }
+ break;
+ case CQUOTE:
+ if (opmode & WDS_TPUTS) {
+ if (quotelevel)
+ quotelevel--;
+ shf_putc('"', shf);
+ }
+ break;
+ case OSUBST:
+ shf_putc('$', shf);
+ if (ord(*wp++) == ORD('{'))
+ shf_putc('{', shf);
+ while ((c = *wp++) != 0)
+ shf_putc(c, shf);
+ wp = wdvarput(shf, wp, 0, opmode);
+ break;
+ case CSUBST:
+ if (ord(*wp++) == ORD('}')) {
+ wdvarput_csubst:
+ shf_putc('}', shf);
+ }
+ return (wp);
+ case OPAT:
+ shf_putchar(*wp++, shf);
+ shf_putc('(', shf);
+ break;
+ case SPAT:
+ c = ORD('|');
+ if (0)
+ /* FALLTHROUGH */
+ case CPAT:
+ c = ORD(/*(*/ ')');
+ shf_putc(c, shf);
+ break;
+ }
+}
+
+/*
+ * this is the _only_ way to reliably handle
+ * variable args with an ANSI compiler
+ */
+/* VARARGS */
+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);
+}
+
+/* VARARGS */
+char *
+snptreef(char *s, ssize_t n, const char *fmt, ...)
+{
+ va_list va;
+ struct shf shf;
+
+ shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
+
+ va_start(va, fmt);
+ vfptreef(&shf, 0, fmt, va);
+ va_end(va);
+
+ /* shf_sclose NUL terminates */
+ return (shf_sclose(&shf));
+}
+
+static void
+vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
+{
+ int c;
+
+ while ((c = ord(*fmt++))) {
+ if (c == '%') {
+ switch ((c = ord(*fmt++))) {
+ case ORD('c'):
+ /* character (octet, probably) */
+ shf_putchar(va_arg(va, int), shf);
+ break;
+ case ORD('s'):
+ /* string */
+ shf_puts(va_arg(va, char *), shf);
+ break;
+ case ORD('S'):
+ /* word */
+ wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
+ break;
+ case ORD('d'):
+ /* signed decimal */
+ shf_fprintf(shf, Tf_d, va_arg(va, int));
+ break;
+ case ORD('u'):
+ /* unsigned decimal */
+ shf_fprintf(shf, "%u", va_arg(va, unsigned int));
+ break;
+ case ORD('T'):
+ /* format tree */
+ ptree(va_arg(va, struct op *), indent, shf);
+ goto dont_trash_prevent_semicolon;
+ case ORD(';'):
+ /* newline or ; */
+ case ORD('N'):
+ /* newline or space */
+ if (shf->flags & SHF_STRING) {
+ if ((unsigned int)c == ORD(';') &&
+ !prevent_semicolon)
+ shf_putc(';', shf);
+ shf_putc(' ', shf);
+ } else {
+ int i;
+
+ shf_putc('\n', shf);
+ i = indent;
+ while (i >= 8) {
+ shf_putc('\t', shf);
+ i -= 8;
+ }
+ while (i--)
+ shf_putc(' ', shf);
+ }
+ break;
+ case ORD('R'):
+ /* I/O redirection */
+ pioact(shf, va_arg(va, struct ioword *));
+ break;
+ default:
+ shf_putc(c, shf);
+ break;
+ }
+ } else
+ shf_putc(c, shf);
+ prevent_semicolon = false;
+ dont_trash_prevent_semicolon:
+ ;
+ }
+}
+
+/*
+ * copy tree (for function definition)
+ */
+struct op *
+tcopy(struct op *t, Area *ap)
+{
+ struct op *r;
+ const char **tw;
+ char **rw;
+
+ if (t == NULL)
+ return (NULL);
+
+ r = alloc(sizeof(struct op), ap);
+
+ r->type = t->type;
+ r->u.evalflags = t->u.evalflags;
+
+ if (t->type == TCASE)
+ r->str = wdcopy(t->str, ap);
+ else
+ strdupx(r->str, t->str, ap);
+
+ if (t->vars == NULL)
+ r->vars = NULL;
+ else {
+ tw = (const char **)t->vars;
+ while (*tw)
+ ++tw;
+ rw = r->vars = alloc2(tw - (const char **)t->vars + 1,
+ sizeof(*tw), ap);
+ tw = (const char **)t->vars;
+ while (*tw)
+ *rw++ = wdcopy(*tw++, ap);
+ *rw = NULL;
+ }
+
+ if (t->args == NULL)
+ r->args = NULL;
+ else {
+ tw = t->args;
+ while (*tw)
+ ++tw;
+ r->args = (const char **)(rw = alloc2(tw - t->args + 1,
+ sizeof(*tw), ap));
+ tw = t->args;
+ while (*tw)
+ *rw++ = wdcopy(*tw++, ap);
+ *rw = NULL;
+ }
+
+ r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
+
+ r->left = tcopy(t->left, ap);
+ r->right = tcopy(t->right, ap);
+ r->lineno = t->lineno;
+
+ return (r);
+}
+
+char *
+wdcopy(const char *wp, Area *ap)
+{
+ size_t len;
+
+ len = wdscan(wp, EOS) - wp;
+ return (memcpy(alloc(len, ap), wp, len));
+}
+
+/* return the position of prefix c in wp plus 1 */
+const char *
+wdscan(const char *wp, int c)
+{
+ int nest = 0;
+
+ while (/* CONSTCOND */ 1)
+ switch (*wp++) {
+ case EOS:
+ return (wp);
+ case ADELIM:
+ if (c == ADELIM && nest == 0)
+ return (wp + 1);
+ if (ord(*wp) == ORD(/*{*/ '}'))
+ goto wdscan_csubst;
+ /* FALLTHROUGH */
+ case CHAR:
+ case QCHAR:
+ wp++;
+ break;
+ case COMASUB:
+ case COMSUB:
+ case FUNASUB:
+ case FUNSUB:
+ case VALSUB:
+ case EXPRSUB:
+ while (*wp++ != 0)
+ ;
+ break;
+ case OQUOTE:
+ case CQUOTE:
+ break;
+ case OSUBST:
+ nest++;
+ while (*wp++ != '\0')
+ ;
+ break;
+ case CSUBST:
+ wdscan_csubst:
+ wp++;
+ if (c == CSUBST && nest == 0)
+ return (wp);
+ nest--;
+ break;
+ case OPAT:
+ nest++;
+ wp++;
+ break;
+ case SPAT:
+ case CPAT:
+ if (c == wp[-1] && nest == 0)
+ return (wp);
+ if (wp[-1] == CPAT)
+ nest--;
+ break;
+ default:
+ internal_warningf(
+ "wdscan: unknown char 0x%X (carrying on)",
+ (unsigned char)wp[-1]);
+ }
+}
+
+/*
+ * 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, int opmode)
+{
+ struct shf shf;
+
+ shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
+ wdvarput(&shf, wp, 0, opmode);
+ /* shf_sclose NUL terminates */
+ return (shf_sclose(&shf));
+}
+
+static struct ioword **
+iocopy(struct ioword **iow, Area *ap)
+{
+ struct ioword **ior;
+ int i;
+
+ 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;
+
+ p = iow[i];
+ q = alloc(sizeof(struct ioword), ap);
+ ior[i] = q;
+ *q = *p;
+ if (p->ioname != NULL)
+ q->ioname = wdcopy(p->ioname, ap);
+ if (p->delim != NULL)
+ q->delim = wdcopy(p->delim, ap);
+ if (p->heredoc != NULL)
+ strdupx(q->heredoc, p->heredoc, ap);
+ }
+ ior[i] = NULL;
+
+ return (ior);
+}
+
+/*
+ * free tree (for function definition)
+ */
+void
+tfree(struct op *t, Area *ap)
+{
+ char **w;
+
+ if (t == NULL)
+ return;
+
+ afree(t->str, ap);
+
+ if (t->vars != NULL) {
+ for (w = t->vars; *w != NULL; w++)
+ afree(*w, ap);
+ afree(t->vars, ap);
+ }
+
+ if (t->args != NULL) {
+ /*XXX we assume the caller is right */
+ union mksh_ccphack cw;
+
+ cw.ro = t->args;
+ for (w = cw.rw; *w != NULL; w++)
+ afree(*w, ap);
+ afree(t->args, ap);
+ }
+
+ if (t->ioact != NULL)
+ iofree(t->ioact, ap);
+
+ tfree(t->left, ap);
+ tfree(t->right, ap);
+
+ afree(t, ap);
+}
+
+static void
+iofree(struct ioword **iow, Area *ap)
+{
+ struct ioword **iop;
+ struct ioword *p;
+
+ iop = iow;
+ while ((p = *iop++) != NULL) {
+ afree(p->ioname, ap);
+ afree(p->delim, ap);
+ afree(p->heredoc, ap);
+ afree(p, 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 if (ktsearch(&keywords, k, hash(k)))
+ 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)
+{
+ unsigned int c;
+ char *cp, *buf;
+ size_t n;
+
+ buf = alloc(sz + 16, ATEMP);
+ snptreef(buf, sz + 16, Tf_T, t);
+ cp = buf;
+ vist_loop:
+ if (UTFMODE && (n = utf_mbtowc(&c, cp)) != (size_t)-1) {
+ if (c == 0 || n >= sz)
+ /* NUL or not enough free space */
+ goto vist_out;
+ /* copy multibyte char */
+ sz -= n;
+ while (n--)
+ *dst++ = *cp++;
+ goto vist_loop;
+ }
+ if (--sz == 0 || (c = ord(*cp++)) == 0)
+ /* NUL or not enough free space */
+ goto vist_out;
+ if (ksh_isctrl(c)) {
+ /* C0 or C1 control character or DEL */
+ if (--sz == 0)
+ /* not enough free space for two chars */
+ goto vist_out;
+ *dst++ = '^';
+ c = ksh_unctrl(c);
+ } else if (UTFMODE && rtt2asc(c) > 0x7F) {
+ /* better not try to display broken multibyte chars */
+ /* also go easy on the UCS: no U+FFFD here */
+ c = ORD('?');
+ }
+ *dst++ = c;
+ goto vist_loop;
+
+ vist_out:
+ *dst = '\0';
+ afree(buf, ATEMP);
+}
+
+#ifdef DEBUG
+void
+dumpchar(struct shf *shf, unsigned char c)
+{
+ if (ksh_isctrl(c)) {
+ /* C0 or C1 control character or DEL */
+ shf_putc('^', shf);
+ c = ksh_unctrl(c);
+ }
+ shf_putc(c, shf);
+}
+
+/* see: wdvarput */
+static const char *
+dumpwdvar_i(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:
+ if (ord(*wp) == ORD(/*{*/ '}')) {
+ shf_puts(/*{*/ "]ADELIM(})", shf);
+ return (wp + 1);
+ }
+ shf_puts("ADELIM=", shf);
+ if (0)
+ /* FALLTHROUGH */
+ case CHAR:
+ shf_puts("CHAR=", shf);
+ dumpchar(shf, *wp++);
+ break;
+ case QCHAR:
+ shf_puts("QCHAR<", shf);
+ c = ord(*wp++);
+ if (quotelevel == 0 || c == ORD('"') ||
+ c == ORD('\\') || ctype(c, C_DOLAR | C_GRAVE))
+ shf_putc('\\', shf);
+ dumpchar(shf, c);
+ goto closeandout;
+ case COMASUB:
+ shf_puts("COMASUB<", shf);
+ goto dumpsub;
+ case COMSUB:
+ shf_puts("COMSUB<", shf);
+ dumpsub:
+ while ((c = *wp++) != 0)
+ dumpchar(shf, c);
+ closeandout:
+ shf_putc('>', shf);
+ break;
+ case FUNASUB:
+ shf_puts("FUNASUB<", shf);
+ goto dumpsub;
+ case FUNSUB:
+ shf_puts("FUNSUB<", shf);
+ goto dumpsub;
+ case VALSUB:
+ shf_puts("VALSUB<", shf);
+ goto dumpsub;
+ 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_i(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_i(shf, wp, 0);
+}
+
+void
+dumpioact(struct shf *shf, struct op *t)
+{
+ struct ioword **ioact, *iop;
+
+ if ((ioact = t->ioact) == NULL)
+ return;
+
+ shf_puts("{IOACT", shf);
+ while ((iop = *ioact++) != NULL) {
+ unsigned short type = iop->ioflag & IOTYPE;
+#define DT(x) case x: shf_puts(#x, shf); break;
+#define DB(x) if (iop->ioflag & x) shf_puts("|" #x, shf);
+
+ shf_putc(';', shf);
+ switch (type) {
+ DT(IOREAD)
+ DT(IOWRITE)
+ DT(IORDWR)
+ DT(IOHERE)
+ DT(IOCAT)
+ DT(IODUP)
+ default:
+ shf_fprintf(shf, "unk%d", type);
+ }
+ DB(IOEVAL)
+ DB(IOSKIP)
+ DB(IOCLOB)
+ DB(IORDUP)
+ DB(IONAMEXP)
+ DB(IOBASH)
+ DB(IOHERESTR)
+ DB(IONDELIM)
+ shf_fprintf(shf, ",unit=%d", (int)iop->unit);
+ if (iop->delim && !(iop->ioflag & IONDELIM)) {
+ shf_puts(",delim<", shf);
+ dumpwdvar(shf, iop->delim);
+ shf_putc('>', shf);
+ }
+ if (iop->ioname) {
+ if (iop->ioflag & IONAMEXP) {
+ shf_puts(",name=", shf);
+ print_value_quoted(shf, iop->ioname);
+ } else {
+ shf_puts(",name<", shf);
+ dumpwdvar(shf, iop->ioname);
+ shf_putc('>', shf);
+ }
+ }
+ if (iop->heredoc) {
+ shf_puts(",heredoc=", shf);
+ print_value_quoted(shf, iop->heredoc);
+ }
+#undef DT
+#undef DB
+ }
+ shf_putc('}', shf);
+}
+
+void
+dumptree(struct shf *shf, struct op *t)
+{
+ int i, j;
+ const char **w, *name;
+ struct op *t1;
+ static int nesting;
+
+ for (i = 0; i < nesting; ++i)
+ shf_putc('\t', shf);
+ ++nesting;
+ shf_puts("{tree:" /*}*/, shf);
+ if (t == NULL) {
+ name = "(null)";
+ goto out;
+ }
+ dumpioact(shf, t);
+ 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 (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 (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 (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 (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 (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);
+ dumpioact(shf, t);
+ 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 ? Ttrue : Tfalse);
+ goto dumpleftandout;
+ OPEN(TTIME)
+ goto dumpleftandout;
+ OPEN(TIF)
+ dumpif:
+ shf_putc('\n', shf);
+ dumptree(shf, t->left);
+ t = t->right;
+ dumpioact(shf, t);
+ 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;
+ dumpioact(shf, t);
+ goto dumpif;
+ }
+ if (t->right != NULL) {
+ shf_puts(" /TELSE:\n", shf);
+ dumptree(shf, t->right);
+ }
+ break;
+ OPEN(TEOF)
+ dumpunexpected:
+ shf_puts(Tunexpected, shf);
+ break;
+ OPEN(TELIF)
+ goto dumpunexpected;
+ OPEN(TPAT)
+ goto dumpunexpected;
+ default:
+ name = "TINVALID";
+ shf_fprintf(shf, "{T<%d>:" /*}*/, t->type);
+ goto dumpunexpected;
+
+#undef OPEN
+ }
+ out:
+ shf_fprintf(shf, /*{*/ " /%s}\n", name);
+ --nesting;
+}
+#endif
diff --git a/shells/mksh/files/var.c b/shells/mksh/files/var.c
new file mode 100644
index 00000000000..ade60c52314
--- /dev/null
+++ b/shells/mksh/files/var.c
@@ -0,0 +1,2244 @@
+/* $OpenBSD: var.c,v 1.44 2015/09/10 11:37:42 jca Exp $ */
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ * 2019
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+#include "mirhash.h"
+
+#if defined(__OpenBSD__)
+#include <sys/sysctl.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.236 2020/04/13 16:29:34 tg Exp $");
+
+/*-
+ * Variables
+ *
+ * WARNING: unreadable code, needs a rewrite
+ *
+ * if (flag&INTEGER), val.i contains integer value, and type contains base.
+ * otherwise, (val.s + type) contains string value.
+ * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
+ */
+
+static struct table specials;
+static uint32_t lcg_state = 5381, qh_state = 4711;
+/* may only be set by typeset() just before call to array_index_calc() */
+static enum namerefflag innermost_refflag = SRF_NOP;
+
+static void c_typeset_vardump(struct tbl *, uint32_t, int, int, bool, bool);
+static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
+ bool);
+static char *formatstr(struct tbl *, const char *);
+static void exportprep(struct tbl *, const char *, size_t);
+static int special(const char *);
+static void unspecial(const char *);
+static void getspec(struct tbl *);
+static void setspec(struct tbl *);
+static void unsetspec(struct tbl *, bool);
+static int getint(struct tbl *, mksh_ari_u *, bool);
+static const char *array_index_calc(const char *, bool *, uint32_t *);
+static struct tbl *vtypeset(int *, const char *, uint32_t, uint32_t, int, int);
+
+/*
+ * create a new block for function calls and simple commands
+ * assume caller has allocated and set up e->loc
+ */
+void
+newblock(void)
+{
+ struct block *l;
+ static const char *empty[] = { null };
+
+ l = alloc(sizeof(struct block), ATEMP);
+ l->flags = 0;
+ /* TODO: could use e->area (l->area => l->areap) */
+ ainit(&l->area);
+ if (!e->loc) {
+ l->argc = 0;
+ l->argv = empty;
+ } else {
+ l->argc = e->loc->argc;
+ l->argv = e->loc->argv;
+ }
+ l->exit = l->error = NULL;
+ ktinit(&l->area, &l->vars, 0);
+ ktinit(&l->area, &l->funs, 0);
+ l->next = e->loc;
+ e->loc = l;
+}
+
+/*
+ * pop a block handling special variables
+ */
+void
+popblock(void)
+{
+ ssize_t i;
+ struct block *l = e->loc;
+ struct tbl *vp, **vpp = l->vars.tbls, *vq;
+
+ /* 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);
+ else
+ unsetspec(vq, false);
+ }
+ if (l->flags & BF_DOGETOPTS)
+ user_opt = l->getopts_state;
+ afreeall(&l->area);
+ afree(l, ATEMP);
+}
+
+/* called by main() to initialise variable data structures */
+#define VARSPEC_DEFNS
+#include "var_spec.h"
+
+enum var_specs {
+#define VARSPEC_ENUMS
+#include "var_spec.h"
+ V_MAX
+};
+
+/* this is biased with -1 relative to VARSPEC_ENUMS */
+static const char * const initvar_names[] = {
+#define VARSPEC_ITEMS
+#include "var_spec.h"
+};
+
+void
+initvar(void)
+{
+ int i = 0;
+ struct tbl *tp;
+
+ ktinit(APERM, &specials,
+ /* currently 21 specials: 75% of 32 = 2^5 */
+ 5);
+ while (i < V_MAX - 1) {
+ tp = ktenter(&specials, initvar_names[i],
+ hash(initvar_names[i]));
+ tp->flag = DEFINED|ISSET;
+ tp->type = ++i;
+ }
+}
+
+/* common code for several functions below and c_typeset() */
+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. May only be called from global()/local()
+ * and must be their first callee.
+ */
+static const char *
+array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
+{
+ const char *p;
+ size_t len;
+ char *ap = NULL;
+
+ *arrayp = false;
+ redo_from_ref:
+ p = skip_varname(n, false);
+ if (innermost_refflag == SRF_NOP && (p != n) && ctype(n[0], C_ALPHX)) {
+ struct tbl *vp;
+ char *vn;
+
+ strndupx(vn, n, p - n, ATEMP);
+ /* check if this is a reference */
+ varsearch(e->loc, &vp, vn, hash(vn));
+ afree(vn, ATEMP);
+ if (vp && (vp->flag & (DEFINED | ASSOC | ARRAY)) ==
+ (DEFINED | ASSOC)) {
+ char *cp;
+
+ /* gotcha! */
+ strdup2x(cp, str_val(vp), p);
+ afree(ap, ATEMP);
+ n = ap = cp;
+ goto redo_from_ref;
+ }
+ }
+ innermost_refflag = SRF_NOP;
+
+ if (p != n && ord(*p) == ORD('[') && (len = array_ref_len(p))) {
+ char *sub, *tmp;
+ mksh_ari_t rval;
+ size_t tmplen = p - n;
+
+ /* calculate the value of the subscript */
+ *arrayp = true;
+ len -= 2;
+ tmp = alloc((len > tmplen ? len : tmplen) + 1, ATEMP);
+ memcpy(tmp, p + 1, len);
+ tmp[len] = '\0';
+ sub = substitute(tmp, 0);
+ evaluate(sub, &rval, KSH_UNWIND_ERROR, true);
+ *valp = (uint32_t)rval;
+ afree(sub, ATEMP);
+ memcpy(tmp, n, tmplen);
+ tmp[tmplen] = '\0';
+ n = tmp;
+ }
+ return (n);
+}
+
+#define vn vname.ro
+/*
+ * Search for variable, if not found create globally.
+ */
+struct tbl *
+global(const char *n)
+{
+ return (isglobal(n, true));
+}
+
+/* search for variable; if not found, return NULL or create globally */
+struct tbl *
+isglobal(const char *n, bool docreate)
+{
+ struct tbl *vp;
+ union mksh_cchack vname;
+ struct block *l = e->loc;
+ int c;
+ bool array;
+ uint32_t h, val;
+
+ /*
+ * check to see if this is an array;
+ * dereference namerefs; must come first
+ */
+ vn = array_index_calc(n, &array, &val);
+ h = hash(vn);
+ c = (unsigned char)vn[0];
+ if (!ctype(c, C_ALPHX)) {
+ if (array)
+ errorf(Tbadsubst);
+ vp = vtemp;
+ vp->flag = DEFINED;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ if (ctype(c, C_DIGIT)) {
+ if (getn(vn, &c)) {
+ /* main.c:main_init() says 12 */
+ shf_snprintf(vp->name, 12, Tf_d, c);
+ if (c <= l->argc) {
+ /* setstr can't fail here */
+ setstr(vp, l->argv[c],
+ KSH_RETURN_ERROR);
+ }
+ } else
+ vp->name[0] = '\0';
+ vp->flag |= RDONLY;
+ goto out;
+ }
+ vp->name[0] = c;
+ vp->name[1] = '\0';
+ vp->flag |= RDONLY;
+ if (!c || vn[1] != '\0')
+ goto out;
+ vp->flag |= ISSET|INTEGER;
+ switch (c) {
+ case '$':
+ vp->val.i = kshpid;
+ break;
+ case '!':
+ /* if no job, expand to nothing */
+ if ((vp->val.i = j_async()) == 0)
+ vp->flag &= ~(ISSET|INTEGER);
+ break;
+ case '?':
+ vp->val.i = exstat & 0xFF;
+ break;
+ case '#':
+ vp->val.i = l->argc;
+ break;
+ case '-':
+ vp->flag &= ~INTEGER;
+ vp->val.s = getoptions();
+ break;
+ default:
+ vp->flag &= ~(ISSET|INTEGER);
+ }
+ goto out;
+ }
+ l = varsearch(e->loc, &vp, vn, h);
+ if (vp == NULL && docreate)
+ vp = ktenter(&l->vars, vn, h);
+ else
+ docreate = false;
+ if (vp != NULL) {
+ if (array)
+ vp = arraysearch(vp, val);
+ if (docreate) {
+ vp->flag |= DEFINED;
+ if (special(vn))
+ vp->flag |= SPECIAL;
+ }
+ }
+ out:
+ last_lookup_was_array = array;
+ if (vn != n)
+ afree(vname.rw, ATEMP);
+ return (vp);
+}
+
+/*
+ * Search for local variable, if not found create locally.
+ */
+struct tbl *
+local(const char *n, bool copy)
+{
+ struct tbl *vp;
+ union mksh_cchack vname;
+ struct block *l = e->loc;
+ bool array;
+ uint32_t h, val;
+
+ /*
+ * check to see if this is an array;
+ * dereference namerefs; must come first
+ */
+ vn = array_index_calc(n, &array, &val);
+ h = hash(vn);
+ if (!ctype(*vn, C_ALPHX)) {
+ vp = vtemp;
+ vp->flag = DEFINED|RDONLY;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ goto out;
+ }
+ vp = ktenter(&l->vars, vn, h);
+ if (copy && !(vp->flag & DEFINED)) {
+ struct tbl *vq;
+
+ varsearch(l->next, &vq, vn, h);
+ if (vq != NULL) {
+ vp->flag |= vq->flag &
+ (EXPORT | INTEGER | RDONLY | LJUST | RJUST |
+ ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L);
+ if (vq->flag & INTEGER)
+ vp->type = vq->type;
+ vp->u2.field = vq->u2.field;
+ }
+ }
+ if (array)
+ vp = arraysearch(vp, val);
+ vp->flag |= DEFINED;
+ if (special(vn))
+ vp->flag |= SPECIAL;
+ out:
+ last_lookup_was_array = array;
+ if (vn != n)
+ afree(vname.rw, ATEMP);
+ return (vp);
+}
+#undef vn
+
+/* get variable string value */
+char *
+str_val(struct tbl *vp)
+{
+ char *s;
+
+ if ((vp->flag&SPECIAL))
+ getspec(vp);
+ if (!(vp->flag&ISSET))
+ /* special to dollar() */
+ s = null;
+ else if (!(vp->flag&INTEGER))
+ /* string source */
+ s = vp->val.s + vp->type;
+ else {
+ /* integer source */
+ mksh_uari_t n;
+ unsigned 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;
+
+ s = strbuf + sizeof(strbuf);
+ if (vp->flag & INT_U)
+ n = vp->val.u;
+ else
+ n = (vp->val.i < 0) ? -vp->val.u : vp->val.u;
+ base = (vp->type == 0) ? 10U : (unsigned int)vp->type;
+
+ if (base == 1 && n == 0)
+ base = 2;
+ if (base == 1) {
+ size_t sz = 1;
+
+ *(s = strbuf) = '1';
+ s[1] = '#';
+ if (!UTFMODE)
+ s[2] = (unsigned char)n;
+ else if ((n & 0xFF80) == 0xEF80)
+ /* OPTU-16 -> raw octet */
+ s[2] = asc2rtt(n & 0xFF);
+ else
+ sz = utf_wctomb(s + 2, n);
+ s[2 + sz] = '\0';
+ } else {
+ *--s = '\0';
+ do {
+ *--s = digits[n % base];
+ n /= base;
+ } while (n != 0);
+ if (base != 10) {
+ *--s = '#';
+ *--s = digits[base % 10];
+ if (base >= 10)
+ *--s = digits[base / 10];
+ }
+ if (!(vp->flag & INT_U) && vp->val.i < 0)
+ *--s = '-';
+ }
+ if (vp->flag & (RJUST|LJUST))
+ /* case already dealt with */
+ s = formatstr(vp, s);
+ else
+ strdupx(s, s, ATEMP);
+ }
+ return (s);
+}
+
+/* set variable to string value */
+int
+setstr(struct tbl *vq, const char *s, int error_ok)
+{
+ bool no_ro_check = tobool(error_ok & 0x4);
+
+ error_ok &= ~0x4;
+ if ((vq->flag & RDONLY) && !no_ro_check) {
+ warningf(true, Tf_ro, vq->name);
+ if (!error_ok)
+ errorfxz(2);
+ return (0);
+ }
+ if (!(vq->flag&INTEGER)) {
+ /* string dest */
+ char *salloc = NULL;
+ size_t cursz;
+ if ((vq->flag&ALLOC)) {
+ cursz = strlen(vq->val.s) + 1;
+#ifndef MKSH_SMALL
+ /* debugging */
+ if (s >= vq->val.s && s < (vq->val.s + cursz)) {
+ internal_errorf(
+ "setstr: %s=%s: assigning to self",
+ vq->name, s);
+ }
+#endif
+ } else
+ cursz = 0;
+ if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST)))
+ s = salloc = formatstr(vq, s);
+ if ((vq->flag&EXPORT))
+ exportprep(vq, s, cursz);
+ else {
+ size_t n = strlen(s) + 1;
+ vq->val.s = aresizeif(cursz, (vq->flag & ALLOC) ?
+ vq->val.s : NULL, n, vq->areap);
+ memcpy(vq->val.s, s, n);
+ vq->flag |= ALLOC;
+ vq->type = 0;
+ }
+ afree(salloc, ATEMP);
+ } else {
+ /* integer dest */
+ if (!v_evaluate(vq, s, error_ok, true))
+ return (0);
+ }
+ vq->flag |= ISSET;
+ if ((vq->flag&SPECIAL))
+ setspec(vq);
+ return (1);
+}
+
+/* set variable to integer */
+void
+setint(struct tbl *vq, mksh_ari_t n)
+{
+ if (!(vq->flag&INTEGER)) {
+ vtemp->flag = (ISSET|INTEGER);
+ vtemp->type = 0;
+ vtemp->areap = ATEMP;
+ vtemp->val.i = n;
+ /* setstr can't fail here */
+ setstr(vq, str_val(vtemp), KSH_RETURN_ERROR);
+ } else
+ vq->val.i = n;
+ vq->flag |= ISSET;
+ if ((vq->flag&SPECIAL))
+ setspec(vq);
+}
+
+static int
+getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
+{
+ mksh_uari_t c, num = 0, base = 10;
+ const char *s;
+ bool have_base = false, neg = false;
+
+ if (vp->flag & SPECIAL)
+ getspec(vp);
+ /* XXX is it possible for ISSET to be set and val.s to be NULL? */
+ if (!(vp->flag & ISSET) || (!(vp->flag & INTEGER) && vp->val.s == NULL))
+ return (-1);
+ if (vp->flag & INTEGER) {
+ nump->i = vp->val.i;
+ return (vp->type);
+ }
+ s = vp->val.s + vp->type;
+
+ do {
+ c = (unsigned char)*s++;
+ } while (ctype(c, C_SPACE));
+
+ switch (c) {
+ case '-':
+ neg = true;
+ /* FALLTHROUGH */
+ case '+':
+ c = (unsigned char)*s++;
+ break;
+ }
+
+ if (c == '0' && arith) {
+ if (ksh_eq(s[0], 'X', 'x')) {
+ /* interpret as hexadecimal */
+ base = 16;
+ ++s;
+ goto getint_c_style_base;
+ } else if (Flag(FPOSIX) && ctype(s[0], C_DIGIT) &&
+ !(vp->flag & ZEROFIL)) {
+ /* interpret as octal (deprecated) */
+ base = 8;
+ getint_c_style_base:
+ have_base = true;
+ c = (unsigned char)*s++;
+ }
+ }
+
+ do {
+ if (c == '#') {
+ /* ksh-style base determination */
+ if (have_base || num < 1)
+ return (-1);
+ if ((base = num) == 1) {
+ /* mksh-specific extension */
+ unsigned int wc;
+
+ if (!UTFMODE)
+ wc = *(const unsigned char *)s;
+ else if (utf_mbtowc(&wc, s) == (size_t)-1)
+ /* OPTU-8 -> OPTU-16 */
+ /*
+ * (with a twist: 1#\uEF80 converts
+ * the same as 1#\x80 does, thus is
+ * not round-tripping correctly XXX)
+ */
+ wc = 0xEF00 + rtt2asc(*s);
+ nump->u = (mksh_uari_t)wc;
+ return (1);
+ } else if (base > 36)
+ base = 10;
+ num = 0;
+ have_base = true;
+ continue;
+ }
+ if (ctype(c, C_DIGIT))
+ c = ksh_numdig(c);
+ else if (ctype(c, C_UPPER))
+ c = ksh_numuc(c) + 10;
+ else if (ctype(c, C_LOWER))
+ c = ksh_numlc(c) + 10;
+ else
+ return (-1);
+ if (c >= base)
+ return (-1);
+ /* handle overflow as truncation */
+ num = num * base + c;
+ } while ((c = (unsigned char)*s++));
+
+ if (neg)
+ num = -num;
+ nump->u = num;
+ return (base);
+}
+
+/*
+ * convert variable vq to integer variable, setting its value from vp
+ * (vq and vp may be the same)
+ */
+struct tbl *
+setint_v(struct tbl *vq, struct tbl *vp, bool arith)
+{
+ int base;
+ mksh_ari_u num;
+
+ if ((base = getint(vp, &num, arith)) == -1)
+ return (NULL);
+ setint_n(vq, num.i, 0);
+ 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, int newbase)
+{
+ if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
+ vq->flag &= ~ALLOC;
+ vq->type = 0;
+ afree(vq->val.s, vq->areap);
+ }
+ vq->val.i = num;
+ if (newbase != 0)
+ vq->type = newbase;
+ vq->flag |= ISSET|INTEGER;
+ if (vq->flag&SPECIAL)
+ setspec(vq);
+}
+
+static char *
+formatstr(struct tbl *vp, const char *s)
+{
+ int olen, nlen;
+ char *p, *q;
+ size_t psiz;
+
+ olen = (int)utf_mbswidth(s);
+
+ if (vp->flag & (RJUST|LJUST)) {
+ if (!vp->u2.field)
+ /* default field width */
+ vp->u2.field = olen;
+ nlen = vp->u2.field;
+ } else
+ nlen = olen;
+
+ p = alloc((psiz = nlen * /* MB_LEN_MAX */ 3 + 1), ATEMP);
+ if (vp->flag & (RJUST|LJUST)) {
+ int slen = olen;
+
+ if (vp->flag & RJUST) {
+ const char *qq;
+ int n = 0;
+
+ qq = utf_skipcols(s, slen, &slen);
+
+ /* strip trailing spaces (AT&T uses qq[-1] == ' ') */
+ while (qq > s && ctype(qq[-1], C_SPACE)) {
+ --qq;
+ --slen;
+ }
+ if (vp->flag & ZEROFIL && vp->flag & INTEGER) {
+ if (!s[0] || !s[1])
+ goto uhm_no;
+ if (s[1] == '#')
+ n = 2;
+ else if (s[2] == '#')
+ n = 3;
+ uhm_no:
+ if (vp->u2.field <= n)
+ n = 0;
+ }
+ if (n) {
+ memcpy(p, s, n);
+ s += n;
+ }
+ while (slen > vp->u2.field)
+ slen -= utf_widthadj(s, &s);
+ if (vp->u2.field - slen)
+ memset(p + n, (vp->flag & ZEROFIL) ? '0' : ' ',
+ vp->u2.field - slen);
+ slen -= n;
+ shf_snprintf(p + vp->u2.field - slen,
+ psiz - (vp->u2.field - slen),
+ "%.*s", slen, s);
+ } else {
+ /* strip leading spaces/zeros */
+ while (ctype(*s, C_SPACE))
+ s++;
+ if (vp->flag & ZEROFIL)
+ while (*s == '0')
+ s++;
+ shf_snprintf(p, psiz, "%-*.*s",
+ vp->u2.field, vp->u2.field, s);
+ }
+ } else
+ memcpy(p, s, strlen(s) + 1);
+
+ if (vp->flag & UCASEV_AL) {
+ for (q = p; *q; q++)
+ *q = ksh_toupper(*q);
+ } else if (vp->flag & LCASEV) {
+ for (q = p; *q; q++)
+ *q = ksh_tolower(*q);
+ }
+
+ return (p);
+}
+
+/*
+ * make vp->val.s be "name=value" for quick exporting.
+ */
+static void
+exportprep(struct tbl *vp, const char *val, size_t cursz)
+{
+ char *cp = (vp->flag & ALLOC) ? vp->val.s : NULL;
+ size_t namelen = strlen(vp->name);
+ size_t vallen = strlen(val) + 1;
+
+ vp->flag |= ALLOC;
+ vp->type = namelen + 1;
+ /* since name+val are both in memory this can go unchecked */
+ vp->val.s = aresizeif(cursz, cp, vp->type + vallen, vp->areap);
+ memmove(vp->val.s + vp->type, val == cp ? vp->val.s : val, vallen);
+ memcpy(vp->val.s, vp->name, namelen);
+ vp->val.s[namelen] = '=';
+}
+
+/*
+ * 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, uint32_t set, uint32_t clr, int field, int base)
+{
+ return (vtypeset(NULL, var, set, clr, field, base));
+}
+static struct tbl *
+vtypeset(int *ep, const char *var, uint32_t set, uint32_t clr,
+ int field, int base)
+{
+ struct tbl *vp;
+ struct tbl *vpbase, *t;
+ char *tvar, tvarbuf[32];
+ const char *val;
+ size_t len;
+ bool vappend = false;
+ enum namerefflag new_refflag = SRF_NOP;
+
+ if (ep)
+ *ep = 0;
+
+ if ((set & (ARRAY | ASSOC)) == ASSOC) {
+ new_refflag = SRF_ENABLE;
+ set &= ~(ARRAY | ASSOC);
+ }
+ if ((clr & (ARRAY | ASSOC)) == ASSOC) {
+ new_refflag = SRF_DISABLE;
+ clr &= ~(ARRAY | ASSOC);
+ }
+
+ /* check for valid variable name, search for value */
+ val = skip_varname(var, false);
+ if (val == var) {
+ /* no variable name given */
+ return (NULL);
+ }
+ if (ord(*val) == ORD('[')) {
+ if (new_refflag != SRF_NOP)
+ return (maybe_errorf(ep, 1, Tf_sD_s, var,
+ "reference variable can't be an array"), NULL);
+ len = array_ref_len(val);
+ if (len == 0)
+ return (NULL);
+ /*
+ * 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 performed on the [expression] which
+ * would be a major security hole.
+ */
+ if (set & IMPORT) {
+ size_t i;
+
+ for (i = 1; i < len - 1; i++)
+ if (!ctype(val[i], C_DIGIT))
+ return (NULL);
+ }
+ val += len;
+ }
+ if (ord(val[0]) == ORD('=')) {
+ len = val - var;
+ tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+ memcpy(tvar, var, len);
+ tvar[len] = '\0';
+ ++val;
+ } else if (set & IMPORT) {
+ /* environment invalid variable name or no assignment */
+ return (NULL);
+ } else if (ord(val[0]) == ORD('+') && ord(val[1]) == ORD('=')) {
+ len = val - var;
+ tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+ memcpy(tvar, var, len);
+ tvar[len] = '\0';
+ val += 2;
+ vappend = true;
+ } else if (val[0] != '\0') {
+ /* other invalid variable names (not from environment) */
+ return (NULL);
+ } else {
+ /* just varname with no value part nor equals sign */
+ len = strlen(var);
+ tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+ memcpy(tvar, var, len);
+ tvar[len] = '\0';
+ val = NULL;
+ /* handle foo[*] => foo (whole array) mapping for R39b */
+ if (len > 3 && ord(tvar[len - 3]) == ORD('[') &&
+ ord(tvar[len - 2]) == ORD('*') &&
+ ord(tvar[len - 1]) == ORD(']'))
+ tvar[len - 3] = '\0';
+ }
+
+ if (new_refflag == SRF_ENABLE) {
+ const char *qval, *ccp;
+
+ /* bail out on 'nameref foo+=bar' */
+ if (vappend)
+ return (maybe_errorf(ep, 1,
+ "appending not allowed for nameref"), NULL);
+ /* find value if variable already exists */
+ if ((qval = val) == NULL) {
+ varsearch(e->loc, &vp, tvar, hash(tvar));
+ if (vp == NULL)
+ goto nameref_empty;
+ qval = str_val(vp);
+ }
+ /* check target value for being a valid variable name */
+ ccp = skip_varname(qval, false);
+ if (ccp == qval) {
+ int c;
+
+ if (!(c = (unsigned char)qval[0]))
+ goto nameref_empty;
+ else if (ctype(c, C_DIGIT) && getn(qval, &c))
+ goto nameref_rhs_checked;
+ else if (qval[1] == '\0') switch (c) {
+ case '$':
+ case '!':
+ case '?':
+ case '#':
+ case '-':
+ goto nameref_rhs_checked;
+ }
+ nameref_empty:
+ return (maybe_errorf(ep, 1, Tf_sD_s, var,
+ "empty nameref target"), NULL);
+ }
+ len = (ord(*ccp) == ORD('[')) ? array_ref_len(ccp) : 0;
+ if (ccp[len]) {
+ /*
+ * works for cases "no array", "valid array with
+ * junk after it" and "invalid array"; in the
+ * latter case, len is also 0 and points to '['
+ */
+ return (maybe_errorf(ep, 1, Tf_sD_s, qval,
+ "nameref target not a valid parameter name"), NULL);
+ }
+ nameref_rhs_checked:
+ /* prevent nameref loops */
+ while (qval) {
+ if (!strcmp(qval, tvar))
+ return (maybe_errorf(ep, 1, Tf_sD_s, qval,
+ "expression recurses on parameter"), NULL);
+ varsearch(e->loc, &vp, qval, hash(qval));
+ qval = NULL;
+ if (vp && ((vp->flag & (ARRAY | ASSOC)) == ASSOC))
+ qval = str_val(vp);
+ }
+ }
+
+ /* prevent typeset from creating a local PATH/ENV/SHELL */
+ if (Flag(FRESTRICTED) && (strcmp(tvar, TPATH) == 0 ||
+ strcmp(tvar, TENV) == 0 || strcmp(tvar, TSHELL) == 0))
+ return (maybe_errorf(ep, 1, Tf_sD_s,
+ tvar, "restricted"), NULL);
+
+ innermost_refflag = new_refflag;
+ vp = (set & LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) :
+ global(tvar);
+ if (new_refflag == SRF_DISABLE && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
+ vp->flag &= ~ASSOC;
+ else if (new_refflag == SRF_ENABLE) {
+ if (vp->flag & ARRAY) {
+ struct tbl *a, *tmp;
+
+ /* free up entire array */
+ for (a = vp->u.array; a; ) {
+ tmp = a;
+ a = a->u.array;
+ if (tmp->flag & ALLOC)
+ afree(tmp->val.s, tmp->areap);
+ afree(tmp, tmp->areap);
+ }
+ vp->u.array = NULL;
+ vp->flag &= ~ARRAY;
+ }
+ vp->flag |= ASSOC;
+ }
+
+ set &= ~(LOCAL|LOCAL_COPY);
+
+ vpbase = (vp->flag & ARRAY) ? global(arrayname(tvar)) : vp;
+
+ /*
+ * only allow export and readonly 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 | RDONLY))))
+ return (maybe_errorf(ep, 2, Tf_ro, tvar), NULL);
+ if (tvar != tvarbuf)
+ afree(tvar, ATEMP);
+
+ /* most calls are with set/clr == 0 */
+ if (set | clr) {
+ bool ok = true;
+
+ /*
+ * 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;
+ char *s = NULL;
+ char *free_me = NULL;
+
+ fake_assign = (t->flag & ISSET) && (!val || t != vp) &&
+ ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) ||
+ ((t->flag & INTEGER) && (clr & INTEGER)) ||
+ (!(t->flag & INTEGER) && (set & INTEGER)));
+ if (fake_assign) {
+ if (t->flag & INTEGER) {
+ s = str_val(t);
+ free_me = NULL;
+ } else {
+ s = t->val.s + t->type;
+ free_me = (t->flag & ALLOC) ? t->val.s :
+ NULL;
+ }
+ t->flag &= ~ALLOC;
+ }
+ if (!(t->flag & INTEGER) && (set & INTEGER)) {
+ t->type = 0;
+ t->flag &= ~ALLOC;
+ }
+ t->flag = (t->flag | set) & ~clr;
+ /*
+ * 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;
+ if (set & (LJUST|RJUST|ZEROFIL))
+ 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.
+ */
+ ok = false;
+ if (t->flag & INTEGER)
+ t->flag &= ~ISSET;
+ else {
+ if (t->flag & ALLOC)
+ afree(t->val.s, t->areap);
+ t->flag &= ~(ISSET|ALLOC);
+ t->type = 0;
+ }
+ }
+ afree(free_me, t->areap);
+ }
+ }
+ if (!ok)
+ return (maybe_errorf(ep, 1, NULL), NULL);
+ }
+
+ if (vappend) {
+ size_t tlen;
+ if ((vp->flag & (ISSET|ALLOC|SPECIAL|INTEGER|UCASEV_AL|LCASEV|LJUST|RJUST)) != (ISSET|ALLOC)) {
+ /* cannot special-case this */
+ strdup2x(tvar, str_val(vp), val);
+ val = tvar;
+ goto vassign;
+ }
+ /* trivial string appending */
+ len = strlen(vp->val.s);
+ tlen = strlen(val) + 1;
+ vp->val.s = aresize(vp->val.s, len + tlen, vp->areap);
+ memcpy(vp->val.s + len, val, tlen);
+ } else if (val != NULL) {
+ vassign:
+ if (vp->flag&INTEGER) {
+ /* do not zero base before assignment */
+ setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
+ /* 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);
+
+ /* came here from vappend? need to free temp val */
+ if (vappend)
+ afree(tvar, ATEMP);
+ }
+
+ /* only x[0] is ever exported, so use vpbase */
+ if ((vpbase->flag & (EXPORT|INTEGER)) == EXPORT &&
+ vpbase->type == 0)
+ exportprep(vpbase, (vpbase->flag & ISSET) ?
+ vpbase->val.s : null, 0);
+
+ return (vp);
+}
+
+/**
+ * Unset a variable. The flags can be:
+ * |1 = tear down entire array
+ * |2 = keep attributes, only unset content
+ */
+void
+unset(struct tbl *vp, int flags)
+{
+ if (vp->flag & ALLOC)
+ afree(vp->val.s, vp->areap);
+ if ((vp->flag & ARRAY) && (flags & 1)) {
+ struct tbl *a, *tmp;
+
+ /* free up entire array */
+ for (a = vp->u.array; a; ) {
+ tmp = a;
+ a = a->u.array;
+ if (tmp->flag & ALLOC)
+ afree(tmp->val.s, tmp->areap);
+ afree(tmp, tmp->areap);
+ }
+ vp->u.array = NULL;
+ }
+ if (flags & 2) {
+ vp->flag &= ~(ALLOC|ISSET);
+ return;
+ }
+ /* 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)
+ /* responsible for 'unspecial'ing var */
+ unsetspec(vp, true);
+}
+
+/*
+ * 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, bool aok)
+{
+ size_t alen;
+
+ if (s && ctype(*s, C_ALPHX)) {
+ do {
+ ++s;
+ } while (ctype(*s, C_ALNUX));
+ if (aok && ord(*s) == ORD('[') && (alen = array_ref_len(s)))
+ s += alen;
+ }
+ return (s);
+}
+
+/* Return a pointer to the first character past any legal variable name */
+const char *
+skip_wdvarname(const char *s,
+ /* skip array de-reference? */
+ bool aok)
+{
+ if (s[0] == CHAR && ctype(s[1], C_ALPHX)) {
+ do {
+ s += 2;
+ } while (s[0] == CHAR && ctype(s[1], C_ALNUX));
+ if (aok && s[0] == CHAR && ord(s[1]) == ORD('[')) {
+ /* skip possible array de-reference */
+ const char *p = s;
+ char c;
+ int depth = 0;
+
+ while (/* CONSTCOND */ 1) {
+ if (p[0] != CHAR)
+ break;
+ c = p[1];
+ p += 2;
+ if (ord(c) == ORD('['))
+ depth++;
+ else if (ord(c) == ORD(']') && --depth == 0) {
+ s = p;
+ break;
+ }
+ }
+ }
+ }
+ return (s);
+}
+
+/* Check if coded string s is a variable name */
+int
+is_wdvarname(const char *s, bool aok)
+{
+ const char *p = skip_wdvarname(s, aok);
+
+ return (p != s && p[0] == EOS);
+}
+
+/* Check if coded string s is a variable assignment */
+int
+is_wdvarassign(const char *s)
+{
+ const char *p = skip_wdvarname(s, true);
+
+ return (p != s && p[0] == CHAR &&
+ (p[1] == '=' || (p[1] == '+' && p[2] == CHAR && p[3] == '=')));
+}
+
+/*
+ * Make the exported environment from the exported names in the dictionary.
+ */
+char **
+makenv(void)
+{
+ ssize_t i;
+ struct block *l;
+ XPtrV denv;
+ struct tbl *vp, **vpp;
+
+ XPinit(denv, 64);
+ 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;
+ struct tbl *vp2;
+ uint32_t h = hash(vp->name);
+
+ /* unexport any redefined instances */
+ for (l2 = l->next; l2 != NULL; l2 = l2->next) {
+ vp2 = ktsearch(&l2->vars, vp->name, h);
+ if (vp2 != NULL)
+ vp2->flag &= ~EXPORT;
+ }
+ if ((vp->flag&INTEGER)) {
+ /* integer to string */
+ char *val;
+ val = str_val(vp);
+ vp->flag &= ~(INTEGER|RDONLY|SPECIAL);
+ /* setstr can't fail here */
+ setstr(vp, val, KSH_RETURN_ERROR);
+ }
+#ifdef __OS2__
+ /* these special variables are not exported */
+ if (!strcmp(vp->name, "BEGINLIBPATH") ||
+ !strcmp(vp->name, "ENDLIBPATH") ||
+ !strcmp(vp->name, "LIBPATHSTRICT"))
+ continue;
+#endif
+ XPput(denv, vp->val.s);
+ }
+ if (l->flags & BF_STOPENV)
+ break;
+ }
+ XPput(denv, NULL);
+ return ((char **)XPclose(denv));
+}
+
+/*
+ * handle special variables with side effects - PATH, SECONDS.
+ */
+
+/* Test if name is a special parameter */
+static int
+special(const char *name)
+{
+ struct tbl *tp;
+
+ tp = ktsearch(&specials, name, hash(name));
+ return (tp && (tp->flag & ISSET) ? tp->type : V_NONE);
+}
+
+/* Make a variable non-special */
+static void
+unspecial(const char *name)
+{
+ struct tbl *tp;
+
+ tp = ktsearch(&specials, name, hash(name));
+ if (tp)
+ ktdelete(tp);
+}
+
+static time_t seconds; /* time SECONDS last set */
+static mksh_uari_t user_lineno; /* what user set $LINENO to */
+
+/* minimum values from the OS we consider sane, lowered for R53 */
+#define MIN_COLS 4
+#define MIN_LINS 2
+
+static void
+getspec(struct tbl *vp)
+{
+ mksh_ari_u num;
+ int st;
+ struct timeval tv;
+
+ switch ((st = special(vp->name))) {
+ case V_COLUMNS:
+ case V_LINES:
+ /*
+ * Do NOT export COLUMNS/LINES. Many applications
+ * check COLUMNS/LINES before checking ws.ws_col/row,
+ * so if the app is started with C/L in the environ
+ * and the window is then resized, the app won't
+ * see the change cause the environ doesn't change.
+ */
+ if (got_winch)
+ change_winsz();
+ break;
+ }
+ switch (st) {
+ case V_BASHPID:
+ num.u = (mksh_uari_t)procpid;
+ break;
+ case V_COLUMNS:
+ num.i = x_cols;
+ break;
+ case V_HISTSIZE:
+ num.i = histsize;
+ break;
+ case V_LINENO:
+ num.u = (mksh_uari_t)current_lineno + user_lineno;
+ break;
+ case V_LINES:
+ num.i = x_lins;
+ break;
+ case V_EPOCHREALTIME: {
+ /* 10(%u) + 1(.) + 6 + NUL */
+ char buf[18];
+
+ vp->flag &= ~SPECIAL;
+ mksh_TIME(tv);
+ shf_snprintf(buf, sizeof(buf), "%u.%06u",
+ (unsigned)tv.tv_sec, (unsigned)tv.tv_usec);
+ setstr(vp, buf, KSH_RETURN_ERROR | 0x4);
+ vp->flag |= SPECIAL;
+ return;
+ }
+ case V_OPTIND:
+ num.i = user_opt.uoptind;
+ break;
+ case V_RANDOM:
+ num.i = rndget();
+ break;
+ case V_SECONDS:
+ /*
+ * On start up the value of SECONDS is used before
+ * it has been set - don't do anything in this case
+ * (see initcoms[] in main.c).
+ */
+ if (vp->flag & ISSET) {
+ mksh_TIME(tv);
+ num.i = tv.tv_sec - seconds;
+ } else
+ return;
+ break;
+ default:
+ /* do nothing, do not touch vp at all */
+ return;
+ }
+ vp->flag &= ~SPECIAL;
+ setint_n(vp, num.i, 0);
+ vp->flag |= SPECIAL;
+}
+
+static void
+setspec(struct tbl *vp)
+{
+ mksh_ari_u num;
+ char *s;
+ int st = special(vp->name);
+
+#ifdef MKSH_DOSPATH
+ switch (st) {
+ case V_PATH:
+ case V_TMPDIR:
+#ifdef __OS2__
+ case V_BEGINLIBPATH:
+ case V_ENDLIBPATH:
+#endif
+ /* convert backslashes to slashes for convenience */
+ if (!(vp->flag&INTEGER)) {
+ s = str_val(vp);
+ do {
+ if (*s == ORD('\\'))
+ *s = '/';
+ } while (*s++);
+ }
+ break;
+ }
+#endif
+
+ switch (st) {
+#ifdef __OS2__
+ case V_BEGINLIBPATH:
+ case V_ENDLIBPATH:
+ case V_LIBPATHSTRICT:
+ setextlibpath(vp->name, str_val(vp));
+ return;
+#endif
+#if HAVE_PERSISTENT_HISTORY
+ case V_HISTFILE:
+ sethistfile(str_val(vp));
+ return;
+#endif
+ case V_IFS:
+ set_ifs(str_val(vp));
+ return;
+ case V_PATH:
+ afree(path, APERM);
+ s = str_val(vp);
+ strdupx(path, s, APERM);
+ /* clear tracked aliases */
+ flushcom(true);
+ return;
+#ifndef MKSH_NO_CMDLINE_EDITING
+ case V_TERM:
+ x_initterm(str_val(vp));
+ return;
+#endif
+ case V_TMPDIR:
+ afree(tmpdir, APERM);
+ tmpdir = NULL;
+ /*
+ * 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 (mksh_abspath(s) && access(s, W_OK|X_OK) == 0 &&
+ stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
+ strdupx(tmpdir, s, APERM);
+ }
+ return;
+ /* common sub-cases */
+ case V_COLUMNS:
+ case V_LINES:
+ if (vp->flag & IMPORT) {
+ /* do not touch */
+ unspecial(vp->name);
+ vp->flag &= ~SPECIAL;
+ return;
+ }
+ /* FALLTHROUGH */
+ case V_HISTSIZE:
+ case V_LINENO:
+ case V_OPTIND:
+ case V_RANDOM:
+ case V_SECONDS:
+ case V_TMOUT:
+ vp->flag &= ~SPECIAL;
+ if (getint(vp, &num, false) == -1) {
+ s = str_val(vp);
+ if (st != V_RANDOM)
+ errorf(Tf_sD_sD_s, vp->name, Tbadnum, s);
+ num.u = hash(s);
+ }
+ vp->flag |= SPECIAL;
+ break;
+#ifdef MKSH_EARLY_LOCALE_TRACKING
+ case V_LANG:
+ case V_LC_ALL:
+ case V_LC_CTYPE:
+ recheck_ctype();
+ return;
+#endif
+ default:
+ /* do nothing, do not touch vp at all */
+ return;
+ }
+
+ /* process the singular parts of the common cases */
+
+ switch (st) {
+ case V_COLUMNS:
+ if (num.i >= MIN_COLS)
+ x_cols = num.i;
+ break;
+ case V_HISTSIZE:
+ sethistsize(num.i);
+ break;
+ case V_LINENO:
+ /* The -1 is because line numbering starts at 1. */
+ user_lineno = num.u - (mksh_uari_t)current_lineno - 1;
+ break;
+ case V_LINES:
+ if (num.i >= MIN_LINS)
+ x_lins = num.i;
+ break;
+ case V_OPTIND:
+ getopts_reset((int)num.i);
+ break;
+ case V_RANDOM:
+ /*
+ * mksh R39d+ no longer has the traditional repeatability
+ * of $RANDOM sequences, but always retains state
+ */
+ rndset((unsigned long)num.u);
+ break;
+ case V_SECONDS:
+ {
+ struct timeval tv;
+
+ mksh_TIME(tv);
+ seconds = tv.tv_sec - num.i;
+ }
+ break;
+ case V_TMOUT:
+ ksh_tmout = num.i >= 0 ? num.i : 0;
+ break;
+ }
+}
+
+static void
+unsetspec(struct tbl *vp, bool dounset)
+{
+ /*
+ * AT&T ksh man page says OPTIND, OPTARG and _ lose special
+ * meaning, but OPTARG does not (still set by getopts) and _ is
+ * also still set in various places. Don't know what AT&T does
+ * for HISTSIZE, HISTFILE. Unsetting these in AT&T ksh does not
+ * loose the 'specialness': IFS, COLUMNS, PATH, TMPDIR
+ */
+
+ switch (special(vp->name)) {
+#ifdef __OS2__
+ case V_BEGINLIBPATH:
+ case V_ENDLIBPATH:
+ case V_LIBPATHSTRICT:
+ setextlibpath(vp->name, "");
+ return;
+#endif
+#if HAVE_PERSISTENT_HISTORY
+ case V_HISTFILE:
+ sethistfile(NULL);
+ return;
+#endif
+ case V_IFS:
+ set_ifs(TC_IFSWS);
+ return;
+ case V_PATH:
+ afree(path, APERM);
+ strdupx(path, def_path, APERM);
+ /* clear tracked aliases */
+ flushcom(true);
+ return;
+#ifndef MKSH_NO_CMDLINE_EDITING
+ case V_TERM:
+ x_initterm(null);
+ return;
+#endif
+ case V_TMPDIR:
+ /* should not become unspecial */
+ if (tmpdir) {
+ afree(tmpdir, APERM);
+ tmpdir = NULL;
+ }
+ return;
+ case V_LINENO:
+ case V_RANDOM:
+ case V_SECONDS:
+ case V_TMOUT:
+ /* AT&T ksh leaves previous value in place */
+ unspecial(vp->name);
+ return;
+#ifdef MKSH_EARLY_LOCALE_TRACKING
+ case V_LANG:
+ case V_LC_ALL:
+ case V_LC_CTYPE:
+ recheck_ctype();
+ return;
+#endif
+ /* should not become unspecial, but allow unsetting */
+ case V_COLUMNS:
+ case V_LINES:
+ if (dounset)
+ unspecial(vp->name);
+ return;
+ }
+}
+
+/*
+ * Search for (and possibly create) a table entry starting with
+ * vp, indexed by val.
+ */
+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] */
+ if (val == 0)
+ return (vp);
+ prev = vp;
+ curr = vp->u.array;
+ while (curr && curr->ua.index < val) {
+ prev = curr;
+ curr = curr->u.array;
+ }
+ if (curr && curr->ua.index == val) {
+ if (curr->flag&ISSET)
+ return (curr);
+ news = curr;
+ } else
+ news = NULL;
+ if (!news) {
+ 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;
+ news->type = vp->type;
+ news->areap = vp->areap;
+ news->u2.field = vp->u2.field;
+ news->ua.index = val;
+
+ 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.
+ *
+ * XXX this should parse the actual arithmetic syntax
+ */
+size_t
+array_ref_len(const char *cp)
+{
+ const char *s = cp;
+ char c;
+ int depth = 0;
+
+ while ((c = *s++) && (ord(c) != ORD(']') || --depth))
+ if (ord(c) == ORD('['))
+ depth++;
+ if (!c)
+ return (0);
+ return (s - cp);
+}
+
+/*
+ * Make a copy of the base of an array name
+ */
+char *
+arrayname(const char *str)
+{
+ const char *p;
+ char *rv;
+
+ if (!(p = cstrchr(str, '[')))
+ /* Shouldn't happen, but why worry? */
+ strdupx(rv, str, ATEMP);
+ else
+ strndupx(rv, str, p - str, ATEMP);
+
+ return (rv);
+}
+
+/* set (or overwrite, if reset) the array variable var to the values in vals */
+mksh_uari_t
+set_array(const char *var, bool reset, const char **vals)
+{
+ struct tbl *vp, *vq;
+ mksh_uari_t i = 0, j = 0;
+ const char *ccp = var;
+ char *cp = NULL;
+ size_t n;
+
+ /* to get local array, use "local foo; set -A foo" */
+ n = strlen(var);
+ if (n > 0 && var[n - 1] == '+') {
+ /* append mode */
+ reset = false;
+ strndupx(cp, var, n - 1, ATEMP);
+ ccp = cp;
+ }
+ vp = global(ccp);
+
+ /* Note: AT&T ksh allows set -A but not set +A of a read-only var */
+ if ((vp->flag&RDONLY))
+ errorfx(2, Tf_ro, ccp);
+ /* This code is quite non-optimal */
+ if (reset) {
+ /* trash existing values and attributes */
+ unset(vp, 1);
+ /* allocate-by-access the [0] element to keep in scope */
+ arraysearch(vp, 0);
+ /* honour set -o allexport */
+ if (Flag(FEXPORT))
+ typeset(ccp, EXPORT, 0, 0, 0);
+ }
+ /*
+ * TODO: would be nice for assignment to completely succeed or
+ * completely fail. Only really effects integer arrays:
+ * evaluation of some of vals[] may fail...
+ */
+ 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);
+ }
+ while ((ccp = vals[i])) {
+#if 0 /* temporarily taken out due to regression */
+ if (ord(*ccp) == ORD('[')) {
+ int level = 0;
+
+ while (*ccp) {
+ if (ord(*ccp) == ORD(']') && --level == 0)
+ break;
+ if (ord(*ccp) == ORD('['))
+ ++level;
+ ++ccp;
+ }
+ if (ord(*ccp) == ORD(']') && level == 0 &&
+ ord(ccp[1]) == ORD('=')) {
+ strndupx(cp, vals[i] + 1, ccp - (vals[i] + 1),
+ ATEMP);
+ evaluate(substitute(cp, 0), (mksh_ari_t *)&j,
+ KSH_UNWIND_ERROR, true);
+ afree(cp, ATEMP);
+ ccp += 2;
+ } else
+ ccp = vals[i];
+ }
+#endif
+
+ vq = arraysearch(vp, j);
+ /* would be nice to deal with errors here... (see above) */
+ setstr(vq, ccp, KSH_RETURN_ERROR);
+ i++;
+ j++;
+ }
+
+ return (i);
+}
+
+void
+change_winsz(void)
+{
+ struct timeval tv;
+
+ mksh_TIME(tv);
+ BAFHUpdateMem_mem(qh_state, &tv, sizeof(tv));
+
+#ifdef TIOCGWINSZ
+ /* check if window size has changed */
+ if (tty_init_fd() < 2) {
+ struct winsize ws;
+
+ if (ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
+ if (ws.ws_col)
+ x_cols = ws.ws_col;
+ if (ws.ws_row)
+ x_lins = ws.ws_row;
+ }
+ }
+#endif
+
+ /* bounds check for sane values, use defaults otherwise */
+ if (x_cols < MIN_COLS)
+ x_cols = 80;
+ if (x_lins < MIN_LINS)
+ x_lins = 24;
+
+#ifdef SIGWINCH
+ got_winch = 0;
+#endif
+}
+
+uint32_t
+hash(const void *s)
+{
+ register uint32_t h;
+
+ BAFHInit(h);
+ BAFHUpdateStr_reg(h, s);
+ BAFHFinish_reg(h);
+ return (h);
+}
+
+uint32_t
+chvt_rndsetup(const void *bp, size_t sz)
+{
+ register uint32_t h;
+
+ /* use LCG as seed but try to get them to deviate immediately */
+ h = lcg_state;
+ (void)rndget();
+ BAFHFinish_reg(h);
+ /* variation through pid, ppid, and the works */
+ BAFHUpdateMem_reg(h, &rndsetupstate, sizeof(rndsetupstate));
+ /* some variation, some possibly entropy, depending on OE */
+ BAFHUpdateMem_reg(h, bp, sz);
+ /* mix them all up */
+ BAFHFinish_reg(h);
+
+ return (h);
+}
+
+mksh_ari_t
+rndget(void)
+{
+ /*
+ * this is the same Linear Congruential PRNG as Borland
+ * C/C++ allegedly uses in its built-in rand() function
+ */
+ return (((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF);
+}
+
+void
+rndset(unsigned long v)
+{
+ register uint32_t h;
+#if defined(arc4random_pushb_fast) || defined(MKSH_A4PB)
+ register uint32_t t;
+#endif
+ struct {
+ struct timeval tv;
+ void *sp;
+ uint32_t qh;
+ pid_t pp;
+ short r;
+ } z;
+
+ /* clear the allocated space, for valgrind and to avoid UB */
+ memset(&z, 0, sizeof(z));
+
+ h = lcg_state;
+ BAFHFinish_reg(h);
+ BAFHUpdateMem_reg(h, &v, sizeof(v));
+
+ mksh_TIME(z.tv);
+ z.sp = &lcg_state;
+ z.pp = procpid;
+ z.r = (short)rndget();
+
+#if defined(arc4random_pushb_fast) || defined(MKSH_A4PB)
+ t = qh_state;
+ BAFHFinish_reg(t);
+ z.qh = (t & 0xFFFF8000) | rndget();
+ lcg_state = (t << 15) | rndget();
+ /*
+ * 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
+ */
+ t = h;
+ BAFHUpdateMem_reg(t, &lcg_state, sizeof(lcg_state));
+ BAFHFinish_reg(t);
+ lcg_state = t;
+#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
+ BAFHUpdateMem_reg(h, &lcg_state, sizeof(lcg_state));
+#else
+ z.qh = qh_state;
+#endif
+
+ BAFHUpdateMem_reg(h, &z, sizeof(z));
+ BAFHFinish_reg(h);
+ lcg_state = h;
+}
+
+void
+rndpush(const void *s)
+{
+ register uint32_t h = qh_state;
+
+ BAFHUpdateStr_reg(h, s);
+ BAFHUpdateOctet_reg(h, 0);
+ qh_state = h;
+}
+
+/* record last glob match */
+void
+record_match(const char *istr)
+{
+ struct tbl *vp;
+
+ vp = local("KSH_MATCH", false);
+ unset(vp, 1);
+ vp->flag = DEFINED | RDONLY;
+ setstr(vp, istr, 0x4);
+}
+
+/* typeset, export and readonly */
+int
+c_typeset(const char **wp)
+{
+ struct tbl *vp, **p;
+ uint32_t fset = 0, fclr = 0, flag;
+ int thing = 0, field = 0, base = 0, i;
+ struct block *l;
+ const char *opts;
+ const char *fieldstr = NULL, *basestr = NULL;
+ bool localv = false, func = false, pflag = false, istset = true;
+ enum namerefflag new_refflag = SRF_NOP;
+
+ switch (**wp) {
+
+ /* export */
+ case 'e':
+ fset |= EXPORT;
+ istset = false;
+ break;
+
+ /* readonly */
+ case 'r':
+ fset |= RDONLY;
+ istset = false;
+ break;
+
+ /* set */
+ case 's':
+ /* called with 'typeset -' */
+ break;
+
+ /* typeset */
+ case 't':
+ localv = true;
+ break;
+ }
+
+ /* see comment below regarding possible opions */
+ opts = istset ? "L#R#UZ#afgi#lnprtux" : "p";
+
+ builtin_opt.flags |= GF_PLUSOPT;
+ /*
+ * AT&T ksh seems to have 0-9 as options which are multiplied
+ * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
+ * sets right justify in a field of 12). This allows options
+ * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
+ * does not allow the number to be specified as a separate argument
+ * Here, the number must follow the RLZi option, but is optional
+ * (see the # kludge in ksh_getopt()).
+ */
+ while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
+ flag = 0;
+ switch (i) {
+ case 'L':
+ flag = LJUST;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'R':
+ flag = RJUST;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'U':
+ /*
+ * AT&T ksh uses u, but this conflicts with
+ * upper/lower case. If this option is changed,
+ * need to change the -U below as well
+ */
+ flag = INT_U;
+ break;
+ case 'Z':
+ flag = ZEROFIL;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'a':
+ /*
+ * this is supposed to set (-a) or unset (+a) the
+ * indexed array attribute; it does nothing on an
+ * existing regular string or indexed array though
+ */
+ break;
+ case 'f':
+ func = true;
+ break;
+ case 'g':
+ localv = (builtin_opt.info & GI_PLUS) ? true : false;
+ break;
+ case 'i':
+ flag = INTEGER;
+ basestr = builtin_opt.optarg;
+ break;
+ case 'l':
+ flag = LCASEV;
+ break;
+ case 'n':
+ new_refflag = (builtin_opt.info & GI_PLUS) ?
+ SRF_DISABLE : SRF_ENABLE;
+ break;
+ /* export, readonly: POSIX -p flag */
+ case 'p':
+ /* typeset: show values as well */
+ pflag = true;
+ if (istset)
+ continue;
+ break;
+ case 'r':
+ flag = RDONLY;
+ break;
+ case 't':
+ flag = TRACE;
+ break;
+ case 'u':
+ /* upper case / autoload */
+ flag = UCASEV_AL;
+ break;
+ case 'x':
+ flag = EXPORT;
+ break;
+ case '?':
+ return (1);
+ }
+ if (builtin_opt.info & GI_PLUS) {
+ fclr |= flag;
+ fset &= ~flag;
+ thing = '+';
+ } else {
+ fset |= flag;
+ fclr &= ~flag;
+ thing = '-';
+ }
+ }
+
+ if (fieldstr && !getn(fieldstr, &field)) {
+ bi_errorf(Tf_sD_s, Tbadnum, fieldstr);
+ return (1);
+ }
+ if (basestr) {
+ if (!getn(basestr, &base)) {
+ bi_errorf(Tf_sD_s, "bad integer base", basestr);
+ return (1);
+ }
+ if (base < 1 || base > 36)
+ base = 10;
+ }
+
+ if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
+ (wp[builtin_opt.optind][0] == '-' ||
+ wp[builtin_opt.optind][0] == '+') &&
+ wp[builtin_opt.optind][1] == '\0') {
+ thing = wp[builtin_opt.optind][0];
+ builtin_opt.optind++;
+ }
+
+ if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
+ new_refflag != SRF_NOP)) {
+ bi_errorf("only -t, -u and -x options may be used with -f");
+ return (1);
+ }
+ if (wp[builtin_opt.optind]) {
+ /*
+ * Take care of exclusions.
+ * At this point, flags in fset are cleared in fclr and vice
+ * versa. This property should be preserved.
+ */
+ if (fset & LCASEV)
+ /* LCASEV has priority over UCASEV_AL */
+ fset &= ~UCASEV_AL;
+ if (fset & LJUST)
+ /* LJUST has priority over RJUST */
+ fset &= ~RJUST;
+ if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
+ /* -Z implies -ZR */
+ fset |= RJUST;
+ fclr &= ~RJUST;
+ }
+ /*
+ * Setting these attributes clears the others, unless they
+ * are also set in this command
+ */
+ if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
+ INTEGER | INT_U | INT_L)) || new_refflag != SRF_NOP)
+ fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
+ LCASEV | INTEGER | INT_U | INT_L);
+ }
+ if (new_refflag != SRF_NOP) {
+ fclr &= ~(ARRAY | ASSOC);
+ fset &= ~(ARRAY | ASSOC);
+ fclr |= EXPORT;
+ fset |= ASSOC;
+ if (new_refflag == SRF_DISABLE)
+ fclr |= ASSOC;
+ }
+
+ /* set variables and attributes */
+ if (wp[builtin_opt.optind] &&
+ /* not "typeset -p varname" */
+ !(!func && pflag && !(fset | fclr))) {
+ int rv = 0, x;
+ struct tbl *f;
+
+ if (localv && !func)
+ fset |= LOCAL;
+ for (i = builtin_opt.optind; wp[i]; i++) {
+ if (func) {
+ f = findfunc(wp[i], hash(wp[i]),
+ tobool(fset & UCASEV_AL));
+ if (!f) {
+ /* AT&T ksh does ++rv: bogus */
+ rv = 1;
+ continue;
+ }
+ if (fset | fclr) {
+ f->flag |= fset;
+ f->flag &= ~fclr;
+ } else {
+ fpFUNCTf(shl_stdout, 0,
+ tobool(f->flag & FKSH),
+ wp[i], f->val.t);
+ shf_putc('\n', shl_stdout);
+ }
+ } else if (!vtypeset(&x, wp[i], fset, fclr,
+ field, base)) {
+ if (x)
+ return (x);
+ bi_errorf(Tf_sD_s, wp[i], Tnot_ident);
+ return (1);
+ }
+ }
+ return (rv);
+ }
+
+ /* list variables and attributes */
+
+ /* no difference at this point.. */
+ flag = fset | fclr;
+ if (func) {
+ for (l = e->loc; l; l = l->next) {
+ for (p = ktsort(&l->funs); (vp = *p++); ) {
+ if (flag && (vp->flag & flag) == 0)
+ continue;
+ if (thing == '-')
+ fpFUNCTf(shl_stdout, 0,
+ tobool(vp->flag & FKSH),
+ vp->name, vp->val.t);
+ else
+ shf_puts(vp->name, shl_stdout);
+ shf_putc('\n', shl_stdout);
+ }
+ }
+ } else if (wp[builtin_opt.optind]) {
+ for (i = builtin_opt.optind; wp[i]; i++) {
+ vp = isglobal(wp[i], false);
+ c_typeset_vardump(vp, flag, thing,
+ last_lookup_was_array ? 4 : 0, pflag, istset);
+ }
+ } else
+ c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset);
+ return (0);
+}
+
+static void
+c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing,
+ bool pflag, bool istset)
+{
+ struct tbl **blockvars, *vp;
+
+ if (l->next)
+ c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset);
+ blockvars = ktsort(&l->vars);
+ while ((vp = *blockvars++))
+ c_typeset_vardump(vp, flag, thing, 0, pflag, istset);
+ /*XXX doesn’t this leak? */
+}
+
+static void
+c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, int any_set,
+ bool pflag, bool istset)
+{
+ struct tbl *tvp;
+ char *s;
+
+ if (!vp)
+ return;
+
+ /*
+ * See if the parameter is set (for arrays, if any
+ * element is set).
+ */
+ for (tvp = vp; tvp; tvp = tvp->u.array)
+ if (tvp->flag & ISSET) {
+ any_set |= 1;
+ break;
+ }
+
+ /*
+ * Check attributes - note that all array elements
+ * have (should have?) the same attributes, so checking
+ * the first is sufficient.
+ *
+ * Report an unset param only if the user has
+ * explicitly given it some attribute (like export);
+ * otherwise, after "echo $FOO", we would report FOO...
+ */
+ if (!any_set && !(vp->flag & USERATTRIB))
+ return;
+ if (flag && (vp->flag & flag) == 0)
+ return;
+ if (!(vp->flag & ARRAY))
+ /* optimise later conditionals */
+ any_set = 0;
+ do {
+ /*
+ * Ignore array elements that aren't set unless there
+ * are no set elements, in which case the first is
+ * reported on
+ */
+ if (any_set && !(vp->flag & ISSET))
+ continue;
+ /* no arguments */
+ if (!thing && !flag) {
+ if (any_set == 1) {
+ shprintf(Tf_s_s_sN, Tset, "-A", vp->name);
+ any_set = 2;
+ }
+ /*
+ * AT&T ksh prints things like export, integer,
+ * leftadj, zerofill, etc., but POSIX says must
+ * be suitable for re-entry...
+ */
+ shprintf(Tf_s_s, Ttypeset, "");
+ if (((vp->flag & (ARRAY | ASSOC)) == ASSOC))
+ shprintf(Tf__c_, 'n');
+ if ((vp->flag & INTEGER))
+ shprintf(Tf__c_, 'i');
+ if ((vp->flag & EXPORT))
+ shprintf(Tf__c_, 'x');
+ if ((vp->flag & RDONLY))
+ shprintf(Tf__c_, 'r');
+ if ((vp->flag & TRACE))
+ shprintf(Tf__c_, 't');
+ if ((vp->flag & LJUST))
+ shprintf("-L%d ", vp->u2.field);
+ if ((vp->flag & RJUST))
+ shprintf("-R%d ", vp->u2.field);
+ if ((vp->flag & ZEROFIL))
+ shprintf(Tf__c_, 'Z');
+ if ((vp->flag & LCASEV))
+ shprintf(Tf__c_, 'l');
+ if ((vp->flag & UCASEV_AL))
+ shprintf(Tf__c_, 'u');
+ if ((vp->flag & INT_U))
+ shprintf(Tf__c_, 'U');
+ } else if (pflag) {
+ shprintf(Tf_s_s, istset ? Ttypeset :
+ (flag & EXPORT) ? Texport : Treadonly, "");
+ }
+ if (any_set)
+ shprintf("%s[%lu]", vp->name, arrayindex(vp));
+ else
+ shf_puts(vp->name, shl_stdout);
+ if ((!thing && !flag && pflag) ||
+ (thing == '-' && (vp->flag & ISSET))) {
+ s = str_val(vp);
+ shf_putc('=', shl_stdout);
+ /* AT&T ksh can't have justified integers... */
+ if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER)
+ shf_puts(s, shl_stdout);
+ else
+ print_value_quoted(shl_stdout, s);
+ }
+ shf_putc('\n', shl_stdout);
+
+ /*
+ * Only report first 'element' of an array with
+ * no set elements.
+ */
+ if (!any_set)
+ return;
+ } while (!(any_set & 4) && (vp = vp->u.array));
+}
diff --git a/shells/mksh/files/var_spec.h b/shells/mksh/files/var_spec.h
new file mode 100644
index 00000000000..d8444dd92fb
--- /dev/null
+++ b/shells/mksh/files/var_spec.h
@@ -0,0 +1,80 @@
+/*-
+ * Copyright (c) 2009, 2011, 2012, 2016, 2018
+ * mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#if defined(VARSPEC_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.11 2018/01/13 21:38:10 tg Exp $");
+#define FN(name) /* nothing */
+#elif defined(VARSPEC_ENUMS)
+#define FN(name) V_##name,
+#define F0(name) V_##name = 0,
+#elif defined(VARSPEC_ITEMS)
+#define F0(name) /* nothing */
+#define FN(name) #name,
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+/* NOTE: F0 are skipped for the ITEMS array, only FN generate names */
+
+/* 0 is always V_NONE */
+F0(NONE)
+
+/* 1 and up are special variables */
+FN(BASHPID)
+#ifdef __OS2__
+FN(BEGINLIBPATH)
+#endif
+FN(COLUMNS)
+#ifdef __OS2__
+FN(ENDLIBPATH)
+#endif
+FN(EPOCHREALTIME)
+#if HAVE_PERSISTENT_HISTORY
+FN(HISTFILE)
+#endif
+FN(HISTSIZE)
+FN(IFS)
+#ifdef MKSH_EARLY_LOCALE_TRACKING
+FN(LANG)
+FN(LC_ALL)
+FN(LC_CTYPE)
+#endif
+#ifdef __OS2__
+FN(LIBPATHSTRICT)
+#endif
+FN(LINENO)
+FN(LINES)
+FN(OPTIND)
+FN(PATH)
+FN(RANDOM)
+FN(SECONDS)
+#ifndef MKSH_NO_CMDLINE_EDITING
+FN(TERM)
+#endif
+FN(TMOUT)
+FN(TMPDIR)
+
+#undef FN
+#undef F0
+#undef VARSPEC_DEFNS
+#undef VARSPEC_ENUMS
+#undef VARSPEC_ITEMS
diff --git a/shells/mksh/patches/manual-Build.sh b/shells/mksh/patches/manual-Build.sh
new file mode 100644
index 00000000000..6a574e4c719
--- /dev/null
+++ b/shells/mksh/patches/manual-Build.sh
@@ -0,0 +1,29 @@
+$NetBSD: manual-Build.sh,v 1.1 2020/07/06 10:11:34 jperkin Exp $
+
+Avoid "test -e", unsupported on legacy Solaris /bin/sh.
+
+--- Build.sh.orig 2020-06-24 10:37:23.000000000 +0000
++++ Build.sh
+@@ -2734,11 +2734,11 @@ if test $legacy = 0; then
+ fi
+ $e
+ $e Installing the manual:
+-if test -e FAQ.htm; then
++if test -f FAQ.htm; then
+ $e "# $i -c -o root -g bin -m 444 FAQ.htm /usr/share/doc/mksh/"
+ fi
+ if test -f mksh.cat1; then
+- if test -e FAQ.htm; then
++ if test -f FAQ.htm; then
+ $e plus either
+ fi
+ $e "# $i -c -o root -g bin -m 444 lksh.cat1" \
+@@ -2751,7 +2751,7 @@ $e "# $i -c -o root -g bin -m 444 lksh.1
+ $e
+ $e Run the regression test suite: ./test.sh
+ $e Please also read the sample file dot.mkshrc and the fine manual.
+-test -e FAQ.htm || \
++test -f FAQ.htm || \
+ $e Run FAQ2HTML.sh and place FAQ.htm into a suitable location as well.
+ exit 0
+
diff --git a/shells/mksh/patches/manual-funcs.c b/shells/mksh/patches/manual-funcs.c
new file mode 100644
index 00000000000..8fc54c834e0
--- /dev/null
+++ b/shells/mksh/patches/manual-funcs.c
@@ -0,0 +1,29 @@
+$NetBSD: manual-funcs.c,v 1.1 2020/07/06 10:11:34 jperkin Exp $
+
+Apple switched from using RLIMIT_RSS (ulimit -m) in 10.3 (xnu-517.12.7) over
+to RLIMIT_AS (ulimit -v) in 10.4 (xnu-792), providing a compatibility define
+for RLIMIT_RSS. The default bash shell since 10.4 supports both -m and -v.
+
+Because the current design of the ULIMIT_CMD_* variables in mk/platform/*.mk
+dictates that we must use a single value, -m is chosen so that all releases
+are supported. However, mksh removes -m support by default if it is
+identical to -v, thus breaking ULIMIT_CMD_memorysize.
+
+In order to provide maximum compatibility in pkgsrc, we apply the following
+patch so that "ulimit -m" continues to work with mksh, acting as an alias
+for -v.
+
+For the record, it appears that /bin/zsh also supports -v but not -m,
+whereas shells/pdksh supports -m but not -v.
+
+--- files/funcs.c.orig 2020-06-25 09:34:59.000000000 +0000
++++ files/funcs.c
+@@ -3240,7 +3240,7 @@ ptest_error(Test_env *te, int ofs, const
+ #else
+ #define ULIMIT_M_IS_RSS
+ #endif
+-#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)
++#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS) && !defined(__APPLE__)
+ #undef ULIMIT_M_IS_RSS
+ #endif
+ #endif