summaryrefslogtreecommitdiff
path: root/shells/mksh
diff options
context:
space:
mode:
authorjperkin <jperkin@pkgsrc.org>2020-07-06 10:11:34 +0000
committerjperkin <jperkin@pkgsrc.org>2020-07-06 10:11:34 +0000
commit94e893af08e7b11b0edd9fba309f572ffa98682d (patch)
treee466faa2247b45b6478099f5b4faef3fb2774b47 /shells/mksh
parent4f52142d7fd7393f2b9fd100c7dfb09c7db276bb (diff)
downloadpkgsrc-94e893af08e7b11b0edd9fba309f572ffa98682d.tar.gz
mksh: Import unpacked mksh-59b sources.
Two patches have been applied on top, both of which are stored in the patches directory as manual-Build.sh and manual-funcs.c, to fix issues on Darwin and SunOS that will be included in the next mksh release. The binary mksh.ico file has been manually removed. This paves the way for mksh to be used as an alternate bootstrap shell instead of shells/pdksh, which has been unmaintained for many years and has some known issues. Initial bulk build results on Darwin and SunOS look good, and having a maintained shell which focuses on portability and speed should provide us with a better long-term option for systems which require a bootstrap shell.
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