mksh/Build.sh010064400000000000000000002134721322647613100103600ustar00#!/bin/sh srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.731 2018/01/13 21:38:06 tg Exp $' #- # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # 2011, 2012, 2013, 2014, 2015, 2016, 2017 # mirabilos # # 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 export LC_ALL 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=______________________________________________________________ case `echo a | tr '\201' X` in X) # EBCDIC build system lfcr='\n\r' ;; *) lfcr='\012\015' ;; esac genopt_die() { if test -n "$1"; then echo >&2 "E: $*" echo >&2 "E: in '$srcfile': '$line'" else echo >&2 "E: invalid input 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 -n "$cond" && o_gen=$o_gen$nl"$cond" o_gen=$o_gen$nl"$line, $optc)" test -n "$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 -n "$cond" && echo "$cond" echo "\"$opts\"" test -n "$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} " $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 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" if test -n "$fl"; then save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $fl" fi if test 1 = $hf; then ac_testn can_$vn '' "$ft" else ac_testn can_$vn '' "$ft" <<-'EOF' /* evil apo'stroph in comment test */ #include int main(void) { return (isatty(0)); } EOF fi eval fv=\$HAVE_CAN_`upper $vn` if test -n "$fl"; then test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS fi test 11 = $fa$fv || CFLAGS=$save_CFLAGS } # 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 ' >>x echo '#include ' >>x echo '#elif HAVE_SYS_TIME_H' >>x echo '#include ' >>x echo '#elif HAVE_TIME_H' >>x echo '#include ' >>x echo '#endif' >>x ;; *) echo "#include <$i>" >>x ;; esac done echo "#include <$hf>" >>x echo '#include ' >>x echo 'int main(void) { return (isatty(0)); }' >>x ac_testn "$hv" "" "<$hf>" /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:combine|c:dragonegg|c:llvm|c:lto) cm=$i last= ;; c:*) echo "$me: Unknown option -c '$i'!" >&2 exit 1 ;; o:*) optflags=$i last= ;; t:*) tfn=$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 ;; :-t) last=t ;; :-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 -z "$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 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 -n "$LDSTATIC" && if test -n "$LDFLAGS"; then LDFLAGS="$LDFLAGS $LDSTATIC" else 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 Build.sh|check.pl|check.t|dot.mkshrc|*.1|*.c|*.h|*.ico|*.opt) ;; *) 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 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 x"$TARGET_OSREV" = x"" && 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 x"$TARGET_OSREV" = x"" && 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" ;; 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 x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r` ;; *) oswarn='; it may or may not work' test x"$TARGET_OSREV" = x"" && 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 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.cpufrequency hw.byteorder hw.cpu64bit_capable >&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. " $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(__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 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 '|' '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" ;; 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 [[ -n $C89_COMPILER ]]; then C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"` else C89_COMPILER=CL.EXE fi if [[ -n $C89_LINKER ]]; then C89_LINKER=`ntpath2posix -c "$C89_LINKER"` else C89_LINKER=LINK.EXE fi vv '|' "$C89_COMPILER /HELP >&2" vv '|' "$C89_LINKER /LINK >&2" ;; esac ;; 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 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 int main(void) { return (isatty(0)); } EOF ;; *) exit 1 ;; esac test 1 = $HAVE_CAN_DELEXE || CFLAGS=$save_CFLAGS ac_testn compiler_still_fails '' 'if the compiler still does not fail correctly' <<-EOF EOF test 1 = $HAVE_COMPILER_STILL_FAILS && 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 x"$i" = x"" && case $ct in hpcc) phase=u ac_flags 1 otwo +O2 phase=x ;; kencc|tcc|tendra) # no special optimisation ;; sunpro) cat >x <<-'EOF' #include 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 ac_flags - 1 stackon "${ccpc}/GZ" 'if stack checks can be enabled' 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 #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 #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 #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 #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 #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 #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 and can both be included' <<-'EOF' #include #include #include #include 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 #include /* 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" #include 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 #include 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 #include 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 #include int main(int ac, char **av) { return ((u_int8_t)(size_t)av[ac]); } EOF ac_test rlim_t <<-'EOF' #include #if HAVE_BOTH_TIME_H #include #include #elif HAVE_SYS_TIME_H #include #elif HAVE_TIME_H #include #endif #if HAVE_SYS_RESOURCE_H #include #endif #include int main(void) { return (((int)(rlim_t)0) + isatty(0)); } EOF # only testn: added later below ac_testn sig_t <<-'EOF' #include #include #include 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 #include #include 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 #include #include 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) { printf("Hello, World!\\n"); return (isatty(0)); } 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 #include #undef flock #if HAVE_SYS_FILE_H #include #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 #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 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 int main(int ac, char *av[]) { return (av[0][killpg(123, ac)]); } EOF ac_test memmove <<-'EOF' #include #include #include #if HAVE_STRINGS_H #include #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 #if HAVE_SYS_FILE_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #include #include 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 int main(void) { return (ftruncate(0, 0)); } EOF ac_test nice <<-'EOF' #include int main(void) { return (nice(4)); } EOF ac_test revoke <<-'EOF' #include #if HAVE_LIBUTIL_H #include #endif #include int main(int ac, char *av[]) { return (ac + revoke(av[0])); } EOF ac_test setlocale_ctype '' 'setlocale(LC_CTYPE, "")' <<-'EOF' #include #include int main(void) { return ((int)(size_t)(void *)setlocale(LC_CTYPE, "")); } EOF ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF' #include #include int main(void) { return ((int)(size_t)(void *)nl_langinfo(CODESET)); } EOF ac_test select <<-'EOF' #include #if HAVE_BOTH_TIME_H #include #include #elif HAVE_SYS_TIME_H #include #elif HAVE_TIME_H #include #endif #if HAVE_SYS_BSDTYPES_H #include #endif #if HAVE_SYS_SELECT_H #include #endif #if HAVE_BSTRING_H #include #endif #include #include #include #if HAVE_STRINGS_H #include #endif #include 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 #include int main(void) { return (setresuid(0,0,0) + setresgid(0,0,0)); } EOF ac_test setgroups setresugid 0 <<-'EOF' #include #if HAVE_GRP_H #include #endif #include 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 #include int main(void) { return (strsignal(1)[0]); } EOF ac_test strlcpy <<-'EOF' #include 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 #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 # # 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 /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 #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 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 ' >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=563 $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 test -n "\$KSH_VERSION" || exit 1 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 $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 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 mksh.cat1; then $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. 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 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) === 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 -c lto && ./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 http://anonscm.debian.org/cgit/collab-maint/mksh.git/plain/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 -c lto EOD mksh/check.pl010064400000000000000000001074571310316600600103730ustar00# $MirOS: src/bin/mksh/check.pl,v 1.49 2017/05/05 21:17:31 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 # # 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 = < 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', '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 () { $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; } mksh/check.t010064400000000000000000010565261322653332000102270ustar00# $MirOS: src/bin/mksh/check.t,v 1.801 2018/01/14 01:47:33 tg Exp $ # -*- mode: sh -*- #- # Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # 2011, 2012, 2013, 2014, 2015, 2016, 2017 # mirabilos # # 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: @(#)MIRBSD KSH R56 2018/01/14 description: Check base version of full shell stdin: echo ${KSH_VERSION%%' +'*} name: KSH_VERSION category: !shell:legacy-yes --- expected-stdout: @(#)LEGACY KSH R56 2018/01/14 description: Check base version of 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: 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: 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: 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: 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: --- 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: 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 '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$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: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 <}> . 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 <{}b> <}> . 30 . 32 . 33 . 34 . 35 . 36 . 37 . 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>&- <${a#\~}> <${b:-~}> <${b:-\~}> <${c:=~}><$c> <${a/~}> <${a/x/~}> <${a/x/\~}>" expected-stdout: <~/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: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: --- 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 Unicode 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:darwin,!os:msys,!os:nto,!os:os2,!os:os390 need-pass: no file-setup: file 644 "ac" stdin: echo a[-]* expected-stdout: ac --- 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-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-9a description: Check that here strings work. stdin: bar="bar baz" tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <</dev/null) expected-stdout: baz --- name: heredoc-9f description: Check long here strings stdin: cat <<< "$( : )aa" expected-stdout: aa --- name: heredoc-10 description: Check direct here document assignment category: !shell:ebcdic-yes stdin: x=u va=<" y=$(<<-EOF hi! $foo) is not a problem EOF) echo "7<$y>" expected-stdout: 3 7 --- name: heredoc-subshell-1 description: Tests for here documents in subshells, taken from Austin ML stdin: (cat <&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> . <2> . <3> . <4> . --- 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> . <2> . <3> . <4> . --- 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> . <2> . <3> . <4> . --- name: IFS-space-colon-1 description: Simple test, IFS=: 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=: 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=: 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=: 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=: 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> <> <> . <4> <:b::> . 5: [a] [b] <6> . 7: [a] [] [c] <8> <> . 9: [h] [ith] [ere] <10> . <11> . 12: [A] [B] [] [D] <13> <> . --- 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> <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> <> . <2> . --- 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> . <1> . <2> . --- 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: =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: =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 =nqs = qk =nqk ~ qs ~nqs ~ qk ~nqk --- 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 ~ qs =nqs ~nqs = qk ~ qk =nqk ~nqk --- 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 ~ qs =nqs ~nqs = qk ~ qk =nqk ~nqk --- 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 ~ qs =nqs ~nqs = qk ~ qk =nqk ~nqk --- 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 ~ qs =nqs ~nqs = qk ~ qk =nqk ~nqk --- 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] =b1zns [ABC] =b2zqs [A] [B] [C] =b3zna [A] [B] [] [] [C] =b4zqa [A] [B] [] [] [C] =b5ins [A,B,,,C] =b6iqs [A] [B] [] [] [C] =b7ina [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: . --- 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> . --- 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> . --- 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 . --- 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*: \[[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 <$d>" expected-stdout: --- 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: --- 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 " 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= x1b= x2a=1 x2b=1 x2c=0 x3a= --- 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 ) 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 ) 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` $(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]> --- 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: :>a test a -nt b && echo nt OK || echo nt BAD test b -ot a && echo ot OK || echo ot BAD expected-stdout: nt OK ot OK --- 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: 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 \ works inside of ${} stdin: abc=2 echo ${ab\ c} expected-stdout: 2 --- name: xxx-quoted-newline-2 description: Check that \ 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 \ 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 (ksh93, SUSv4) 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- x2-3- z1- --- 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: 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: not found\n=2\n.*: foo: not found\n=3\n.*: ls: 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: X X < c> X X <> XX < > X X X X <:c> X X <> XX <:> X X X X 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-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-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' '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]> 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]=~:~ global 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> . <3> <~> <~> . <4> <1 b=2> <> . <5> . <6> <1> <2> . <7> <1 b=2> <> . <8> . <9> <1> <2> . 10 ksh a=/tmp <11> . 12 posix a=~ <13> . <14> <~> . <> <1> <~> <18> <~> . <19> <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-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 ksh93 does. GNU bash does not run ERtrap in ±e eval-undef but runs it twice (bug?) in +e eval-false, so does ksh93 (bug?), which also has a bug to continue execution (echoing "and out" and returning 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*: \[[0-9]*]PROG" } xe=-e echo : $xe runtest x $xe true echo = eval-true $(' 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-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 [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || 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} [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex set -o braceexpand echo {a,b,c} [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex expected-stdout: nosh brex a b c {a,b,c} a b c {a,b,c} sh nobrex a b c sh brex --- name: sh-mode-2a description: Check that posix or sh mode is *not* automatically turned on category: !binsh stdin: ln -s "$__progname" ksh || cp "$__progname" ksh ln -s "$__progname" sh || cp "$__progname" sh ln -s "$__progname" ./-ksh || cp "$__progname" ./-ksh ln -s "$__progname" ./-sh || cp "$__progname" ./-sh for shell in {,-}{,k}sh; do print -- $shell $(./$shell +l -c \ '[[ $(set +o) == *"-o "@(sh|posix)@(| *) ]] && echo sh || echo nosh') done expected-stdout: sh nosh ksh nosh -sh nosh -ksh nosh --- name: sh-mode-2b description: Check that posix or sh mode *is* automatically turned on category: binsh stdin: ln -s "$__progname" ksh || cp "$__progname" ksh ln -s "$__progname" sh || cp "$__progname" sh ln -s "$__progname" ./-ksh || cp "$__progname" ./-ksh ln -s "$__progname" ./-sh || cp "$__progname" ./-sh for shell in {,-}{,k}sh; do print -- $shell $(./$shell +l -c \ '[[ $(set +o) == *"-o "@(sh|posix)@(| *) ]] && echo sh || echo nosh') done expected-stdout: sh sh ksh nosh -sh sh -ksh nosh --- 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> <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: 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 Unicode 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" 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 -- "" x=$a x+=$b print -r -- "" b=$'\u0301'b x=$a x+=$b print -r -- "" expected-stdout: --- 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 . <> --- 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 012 = a b 0 = 0<> b c 012 = c d 012 = 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: Check that some things that work in bash fail. This is by design. Oh and vice versa, nowadays. 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' # next fails only in bash "$__progname" -c 'x=abcdef;y=123;echo ${x:${y:2:1}:2}' >/dev/null 2>&1; echo $? expected-stdout: vcdefghix wcdefghix zabcdefghix 0 expected-stderr-pattern: /x:n.*bad substitution.*\n.*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 -- "" s="u=${i@Q} v=${j@Q} w=${k@Q}" print -r -- "s=\"$s\"" eval "$s" typeset -p u v w expected-stdout: 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 -- "" s="u=${i@Q} v=${j@Q} w=${k@Q}" print -r -- "s=\"$s\"" eval "$s" typeset -p u v w expected-stdout: s="u=x v='a b' w=$'c\nde\u20ACf'" typeset u=x typeset v='a b' typeset w=$'c\nde\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 |.<.| 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 ] {.5} {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 ] {.4} {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 ] {.4} {') 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 <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> <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 Unicode 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 Unicode; NUL and binary safe stdin: set -U { print 'Hello, World!\\\nこんにちは!' typeset -Uui16 i=0x100 # change that to 0xFF once we can handle embedded # NUL characters in strings / here documents while (( i++ < 0x1FF )); do print -n "\u${i#16#1}" done print print \\xff # invalid utf-8 print \\xc2 # invalid 2-byte print \\xef\\xbf\\xc0 # invalid 3-byte print \\xc0\\x80 # non-minimalistic print \\xe0\\x80\\x80 # non-minimalistic print '�￾￿' # end of range print '\0z' # embedded NUL } | { # integer-base-one-3Wr typeset -Uui16 -Z11 pos=0 typeset -Uui16 -Z7 hv=2147483647 dasc= if read -arN -1 line; then typeset -i1 line i=0 while (( i < ${#line[*]} )); do hv=${line[i++]} if (( (hv < 32) || \ ((hv > 126) && (hv < 160)) )); then dch=. elif (( (hv & 0xFF80) == 0xEF80 )); then dch=� else dch=${line[i-1]#1#} fi if (( (pos & 7) == 7 )); then dasc=$dasc$dch dch= elif (( (pos & 7) == 0 )); then (( pos )) && print -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 Unicode safe category: !shell:ebcdic-yes stdin: set +U print 'a\0b\xfdz' >x read -a y x read -a y x read -a y 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; echo 2 "$("$__progname" -o posix -c 'echo foo>/dev/null&>x echo bar')" = "$(x; echo 3 "$("$__progname" -o sh -c 'echo foo>/dev/null&>x echo bar')" = "$(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 != xfoo ]; 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 != xfoo ]; 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: 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>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 <&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>bar } inline_IOREAD_IOCAT() { \tr x u >bar } function comsub_IOREAD_IOCAT { x=$( tr x u 0>bar ); } function comsub_IOREAD_IOCAT { x=$(\tr x u >bar ) } function reread_IOREAD_IOCAT { x=$(( tr x u 0>bar )|tr u x); } function reread_IOREAD_IOCAT { x=$( ( \tr x u >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 <&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/^[^]]*]//' -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=FGJPRSX "$__progname" -c 'x=$(tr z r <<?@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="abcdefghijklmnopqrƤ~stuvwxyz[ޮݨ]{ABCDEFGHI}JKLMNOPQR\\STUVWXYZ0123456789\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 . --- mksh/dot.mkshrc010064400000000000000000000420361314242433000107470ustar00# $Id$ # $MirOS: src/bin/mksh/dot.mkshrc,v 1.121 2017/08/08 21:10:21 tg Exp $ #- # Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, # 2011, 2012, 2013, 2014, 2015, 2016, 2017 # mirabilos # # 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' \: "${HOSTNAME:=$(\\builtin ulimit -c 0; \\builtin print -r -- $(hostname \ 2>/dev/null))}${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit \ -c 0; id -un 2>/dev/null)}${USER:=?}" [[ $HOSTNAME = ?(?(ip6-)localhost?(6)) ]] && HOSTNAME=nil; \\builtin unalias ls \\builtin export EDITOR HOSTNAME TERM USER # minimal support for lksh users if [[ $KSH_VERSION = *LEGACY\ KSH* ]]; then PS1='$USER@${HOSTNAME%%.*}:$PWD>' \\builtin return 0 fi # mksh-specific from here \: "${MKSH:=$(\\builtin whence -p mksh)}${MKSH:=/bin/mksh}" \\builtin export MKSH # 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|" } # 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] [+].' \\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] [|+].' \\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++]=global 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++]=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 follow — you are supposed to adjust these 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 LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \ # LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME #p=en_GB.UTF-8 #\\builtin export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p #\\builtin set -U \\builtin unset p \: place customisations above this line mksh/edit.c010064400000000000000000003414551322651711700100610ustar00/* $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 * mirabilos * * 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.342 2018/01/14 00:03:00 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(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 */ cp = shf_smprintf(Tf_sSs, dp, cp); 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 char *x_mapin(const char *, Area *); static char *x_mapout(int); static void x_mapout2(int, char **); static void x_print(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_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 static char * x_mapin(const char *cp, Area *ap) { char *news, *op; strdupx(news, cp, ap); op = news; while (*cp) { switch (*cp) { case '^': cp++; *op++ = ksh_toctrl(*cp); break; case '\\': if (cp[1] == '\\' || cp[1] == '^') ++cp; /* FALLTHROUGH */ default: *op++ = *cp; } cp++; } *op = '\0'; return (news); } static void x_mapout2(int c, char **buf) { char *p = *buf; if (ksh_isctrl(c)) { *p++ = '^'; *p++ = ksh_unctrl(c); } else *p++ = c; *p = 0; *buf = p; } static char * x_mapout(int c) { static char buf[8]; char *bp = buf; x_mapout2(c, &bp); return (buf); } static void x_print(int prefix, int key) { int f = x_tab[prefix][key]; if (prefix) /* prefix == 1 || prefix == 2 || prefix == 3 */ shf_puts(x_mapout(prefix == 1 ? CTRL_BO : prefix == 2 ? CTRL_X : 0), shl_stdout); #ifdef MKSH_SMALL shprintf("%s = ", x_mapout(key)); #else shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : ""); if (XFUNC_VALUE(f) != XFUNC_ins_string) #endif shprintf(Tf_sN, x_ftab[XFUNC_VALUE(f)].xf_name); #ifndef MKSH_SMALL else shprintf("'%s'\n", x_atab[prefix][key]); #endif } int x_bind(const char *a1, const char *a2, #ifndef MKSH_SMALL /* bind -m */ bool macro, #endif /* bind -l */ bool list) { unsigned char f; int prefix, key; char *m1, *m2; #ifndef MKSH_SMALL char *sp = NULL; bool hastilde; #endif if (x_tab == NULL) { bi_errorf("can't bind, not a tty"); return (1); } /* List function names */ if (list) { 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); } if (a1 == NULL) { for (prefix = 0; prefix < X_NTABS; prefix++) for (key = 0; key < X_TABSZ; key++) { f = XFUNC_VALUE(x_tab[prefix][key]); if (f == XFUNC_insert || f == XFUNC_error #ifndef MKSH_SMALL || (macro && f != XFUNC_ins_string) #endif ) continue; x_print(prefix, key); } return (0); } m2 = m1 = x_mapin(a1, ATEMP); prefix = 0; for (;; m1++) { key = (unsigned char)*m1; f = XFUNC_VALUE(x_tab[prefix][key]); if (f == XFUNC_meta1) prefix = 1; else if (f == XFUNC_meta2) prefix = 2; else if (f == XFUNC_meta3) prefix = 3; else break; } if (*++m1 #ifndef MKSH_SMALL && ((*m1 != '~') || *(m1 + 1)) #endif ) { char msg[256]; const char *c = a1; m1 = msg; while (*c && (size_t)(m1 - msg) < sizeof(msg) - 3) x_mapout2(*c++, &m1); bi_errorf("too long key sequence: %s", msg); return (1); } #ifndef MKSH_SMALL hastilde = tobool(*m1); #endif afree(m2, ATEMP); if (a2 == NULL) { x_print(prefix, key); return (0); } if (*a2 == 0) { f = XFUNC_insert; #ifndef MKSH_SMALL } else if (macro) { f = XFUNC_ins_string; sp = x_mapin(a2, AEDIT); #endif } else { for (f = 0; f < NELEM(x_ftab); f++) if (!strcmp(x_ftab[f].xf_name, a2)) break; if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) { bi_errorf("%s: no such function", a2); return (1); } } #ifndef MKSH_SMALL if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string && x_atab[prefix][key]) afree(x_atab[prefix][key], AEDIT); #endif x_tab[prefix][key] = f #ifndef MKSH_SMALL | (hastilde ? 0x80 : 0) #endif ; #ifndef MKSH_SMALL x_atab[prefix][key] = sp; #endif /* Track what the user has bound so x_mode(true) won't toast things */ if (f == 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 ! " # $ % & ' */ 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 /* ^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 int fsavecmd = ' '; /* last find command */ static int fsavech; /* character to find */ static char lastcmd[MAXVICMD]; /* last non-move command */ static int lastac; /* argcnt for lastcmd */ static int lastsearch = ' '; /* 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) switch (cmdlen = 0, (ch = x_getc())) { case 71: ch = '0'; goto pseudo_vi_command; case 72: ch = 'k'; goto pseudo_vi_command; case 73: ch = 'A'; goto vi_xfunc_search_up; case 75: ch = 'h'; goto pseudo_vi_command; case 77: ch = 'l'; goto pseudo_vi_command; case 79: ch = '$'; goto pseudo_vi_command; case 80: ch = 'j'; goto pseudo_vi_command; case 83: ch = 'x'; goto pseudo_vi_command; default: ch = 0; goto vi_insert_failed; } if (insert != 0) { if (ch == CTRL_V) { state = VLIT; ch = '^'; } 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(ch == '/' ? "/" : "?", 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_up: state = VFAIL; switch (ch) { case 'A': /* 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 the current line up to the cursor */ memmove(srchpat + 1, vs->cbuf, vs->cursor); srchpat[vs->cursor + 1] = '\0'; /* set a magic flag */ argc1 = 2 + (int)vs->cursor; /* and emulate a backwards history search */ lastsearch = '/'; *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 == ' ') return (-1); if (lastsearch == '?') 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 == ' ') return (-1); i = ksh_eq(fsavecmd, 'F', 'f'); t = fsavecmd > '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 *)¯o, 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; /* this is what tmux uses, don't ask me about it */ if (!strcmp(termtype, "screen") || !strncmp(termtype, "screen-", 7)) x_term_mode = 1; } #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 cp = NULL; quitenv(NULL); return (cp); } static int x_eval_region(int c MKSH_A_UNUSED) { char *evbeg, *evend, *cp; size_t newlen; /* only for LINE overflow checking */ size_t restlen; if (xmp == NULL) { evbeg = xbuf; evend = xep; } else if (xmp < xcp) { evbeg = xmp; evend = xcp; } else { evbeg = xcp; evend = xmp; } x_e_putc2('\r'); x_clrtoeol(' ', false); x_flush(); x_mode(false); cp = x_eval_region_helper(evbeg, evend - evbeg); x_mode(true); if (cp == NULL) { /* command cannot be parsed */ x_eval_region_err: x_e_putc2(KSH_BEL); x_redraw('\r'); return (KSTD); } newlen = strlen(cp); restlen = xep - evend; /* check for LINE overflow, until this is dynamically allocated */ if (evbeg + newlen + restlen >= xend) goto x_eval_region_err; xmp = evbeg; xcp = evbeg + newlen; xep = xcp + restlen; memmove(xcp, evend, restlen + /* NUL */ 1); memcpy(xmp, cp, newlen); afree(cp, AEDIT); x_adjust(); x_modified(); return (KSTD); } #endif /* !MKSH_SMALL */ #endif /* !MKSH_NO_CMDLINE_EDITING */ mksh/emacsfn.h010064400000000000000000000072511276202306700105460ustar00/*- * Copyright (c) 2009, 2010, 2015, 2016 * mirabilos * * 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.10 2016/09/01 12:59:09 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) 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 mksh/eval.c010064400000000000000000001320741322653124600100550ustar00/* $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 * mirabilos * * 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.219 2018/01/14 01:29:47 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 */ #define XSUB 1 /* expanding ${} string */ #define XARGSEP 2 /* ifs0 between "$*" */ #define XARG 3 /* expanding $*, $@ */ #define XCOM 4 /* expanding $() */ #define XNULLSUB 5 /* "$@" when $# is 0 (don't generate word) */ #define XSUBMID 6 /* middle of expanding ${} */ /* 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 *, 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 */ 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; 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: x.str = shf_smprintf("%08X", (unsigned int)hash(str_val(st->var))); break; 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); 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, *end; char *pat = NULL, *rrep = null; char fpat = 0, *tpat1, *tpat2; char *ws, *wpat, *wrep; 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); wrep = d ? p : NULL; if (!(stype & STYPE_AT)) { rrep = wrep ? evalstr(wrep, DOTILDE | DOSCALAR) : null; } /* 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; end = strnul(s); /* now anchor the beginning of the match */ if (ord(fpat) != ORD('#')) while (sbeg <= end) { if (gmatchx(sbeg, tpat2, false)) break; else sbeg++; } /* now anchor the end of the match */ p = end; 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--; } strndupx(end, sbeg, p - sbeg, ATEMP); record_match(end); afree(end, ATEMP); if (stype & STYPE_AT) { if (rrep != null) afree(rrep, ATEMP); rrep = wrep ? evalstr(wrep, DOTILDE | DOSCALAR) : null; } strndupx(end, s, sbeg - s, ATEMP); d = shf_smprintf(Tf_sss, end, rrep, p); afree(end, ATEMP); sbeg = d + (sbeg - s) + strlen(rrep); afree(s, ATEMP); s = d; 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('0'): case ORD('/') | STYPE_AT: case ORD('/'): case ORD('#') | STYPE_AT: case ORD('Q') | STYPE_AT: 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 XSUB: case XSUBMID: if ((c = ord(*x.str++)) == 0) { 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 (type == XSUB && ctype(c, C_IFS) && !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) { *(cp = alloc(1, ATEMP)) = '\0'; XPput(*wp, cp); type = XSUBMID; } if (word != IFS_NWS) word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS; } else { if (type == XSUB) { if (word == IFS_NWS && Xlength(ds, dp) == 0) { *(cp = alloc(1, ATEMP)) = '\0'; XPput(*wp, cp); } type = XSUBMID; } /* 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, int *stypep, /* becomes qualifier type */ int *slenp) /* " " len (=, :=, etc.) valid iff *stypep != 0 */ { int c; int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ int stype; /* substitution type */ int slen = 0; const char *p; struct tbl *vp; bool zero_ok = false; if ((stype = ord(sp[0])) == '\0') /* Bad variable name */ return (-1); xp->var = NULL; /*- * ${#var}, string length (-U: characters, +U: octets) or array size * ${%var}, string width (-U: screen columns, +U: octets) */ c = ord(sp[1]); if ((unsigned int)stype == ORD('%') && c == '\0') return (-1); if (ctype(stype, C_SUB2) && c != '\0') { /* Can't have any modifiers for ${#...} or ${%...} */ if (*word != CSUBST) return (-1); sp++; /* Check for size of array */ if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') || ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) { int n = 0; if ((unsigned int)stype != ORD('#')) return (-1); vp = global(arrayname(sp)); if (vp->flag & (ISSET|ARRAY)) zero_ok = true; for (; vp; vp = vp->u.array) if (vp->flag & ISSET) n++; c = n; } else if ((unsigned int)c == ORD('*') || (unsigned int)c == ORD('@')) { if ((unsigned int)stype != ORD('#')) return (-1); c = e->loc->argc; } else { p = str_val(global(sp)); zero_ok = p != null; if ((unsigned int)stype == ORD('#')) c = utflen(p); else { /* partial utf_mbswidth reimplementation */ const char *s = p; unsigned int wc; size_t len; int cw; c = 0; while (*s) { if (!UTFMODE || (len = utf_mbtowc(&wc, s)) == (size_t)-1) /* not UTFMODE or not UTF-8 */ wc = rtt2asc(*s++); else /* UTFMODE and UTF-8 */ s += len; /* wc == char or wchar at s++ */ if ((cw = utf_wcwidth(wc)) == -1) { /* 646, 8859-1, 10646 C0/C1 */ c = -1; break; } c += cw; } } } if (Flag(FNOUNSET) && c == 0 && !zero_ok) errorf(Tf_parm, sp); /* unqualified variable/string substitution */ *stypep = 0; xp->str = shf_smprintf(Tf_d, c); return (XSUB); } if ((unsigned int)stype == ORD('!') && c != '\0' && *word == CSUBST) { sp++; if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') || ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) { c = ORD('!'); stype = 0; goto arraynames; } xp->var = global(sp); xp->str = p ? shf_smprintf("%s[%lu]", xp->var->name, arrayindex(xp->var)) : xp->var->name; *stypep = 0; return (XSUB); } /* Check for qualifiers in word part */ stype = 0; c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0; if ((unsigned int)c == ORD(':')) { slen += 2; stype = STYPE_DBL; c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0; } if (!stype && (unsigned int)c == ORD('/')) { slen += 2; stype = c; if (word[slen] == ADELIM && ord(word[slen + 1]) == (unsigned int)c) { slen += 2; stype |= STYPE_DBL; } } else if (stype == STYPE_DBL && ((unsigned int)c == ORD(' ') || (unsigned int)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]) == (unsigned int)c) { stype |= STYPE_DBL; slen += 2; } } else if ((unsigned int)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); c = ord(sp[0]); if ((unsigned int)c == ORD('*') || (unsigned int)c == ORD('@')) { switch (stype & STYPE_SINGLE) { /* can't assign to a vector */ case ORD('='): /* can't trim a vector (yet) */ case ORD('%'): case ORD('#'): case ORD('?'): case ORD('0'): case ORD('/') | STYPE_AT: case ORD('/'): case ORD('#') | STYPE_AT: case ORD('Q') | STYPE_AT: return (-1); } if (e->loc->argc == 0) { xp->str = null; xp->var = global(sp); state = (unsigned int)c == ORD('@') ? XNULLSUB : XSUB; } else { xp->u.strv = (const char **)e->loc->argv + 1; xp->str = *xp->u.strv++; /* $@ */ xp->split = tobool((unsigned int)c == ORD('@')); state = XARG; } /* POSIX 2009? */ zero_ok = true; } else if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') || ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) { XPtrV wv; switch (stype & STYPE_SINGLE) { /* can't assign to a vector */ case ORD('='): /* can't trim a vector (yet) */ case ORD('%'): case ORD('#'): case ORD('?'): case ORD('0'): case ORD('/') | STYPE_AT: case ORD('/'): case ORD('#') | STYPE_AT: case ORD('Q') | STYPE_AT: return (-1); } c = 0; arraynames: XPinit(wv, 32); vp = global(arrayname(sp)); for (; vp; vp = vp->u.array) { if (!(vp->flag&ISSET)) continue; XPput(wv, (unsigned int)c == ORD('!') ? shf_smprintf(Tf_lu, arrayindex(vp)) : str_val(vp)); } if (XPsize(wv) == 0) { xp->str = null; state = ord(p[1]) == ORD('@') ? XNULLSUB : XSUB; XPfree(wv); } else { XPput(wv, 0); xp->u.strv = (const char **)XPptrv(wv); xp->str = *xp->u.strv++; /* ${foo[@]} */ xp->split = tobool(ord(p[1]) == ORD('@')); state = XARG; } } else { xp->var = global(sp); xp->str = str_val(xp->var); /* can't assign things like $! or $1 */ if ((unsigned int)(stype & STYPE_SINGLE) == ORD('=') && !*xp->str && ctype(*sp, C_VAR1 | C_DIGIT)) return (-1); state = XSUB; } 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) : (unsigned int)c == ORD('+')))) || (unsigned int)stype == (ORD('0') | STYPE_DBL) || (unsigned int)stype == (ORD('#') | STYPE_AT) || (unsigned int)stype == (ORD('Q') | STYPE_AT) || (unsigned int)(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 && (unsigned int)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) { /* $(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("REPLY", false); if (!kshsetjmp(e->jbuf)) execute(t, XXCOM | XERROK, NULL); if (vp) strdupx(cp, str_val(vp), ap); quitenv(NULL); return (cp); } mksh/exec.c010064400000000000000000001264661316750423700100660ustar00/* $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 * mirabilos * * 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.201 2017/10/11 21:09:24 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=<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; dp = shf_smprintf(Tf_ss, 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(); { 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 errorf(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)) { if (Flag(FERREXIT) & 0x80) { /* inside eval */ Flag(FERREXIT) = 0; } else { trapsig(ksh_SIGERR); if (Flag(FERREXIT)) unwind(LERROR); } } 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_PATH | FC_FUNC); if (ext_cmd && (ext_cmd->type != CTALIAS || (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(); /* ksh functions don't keep assignments, POSIX functions do. */ if (!resetspec && tp && tp->type == CFUNC && !(tp->flag & FKSH)) type_flags = EXPORT; else 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: rv = call_builtin(tp, (const char **)ap, null, resetspec); if (resetspec && tp->val.f == c_shift) { l_expand->argc = l_assign->argc; l_expand->argv = l_assign->argv; } 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: 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, cp, 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(LLEAVE); } 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)) >= 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 programme 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; 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" */ 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 | DOHEREDOC); } 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, "-r", "REPLY", 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("REPLY"))) 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("REPLY")))) 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; 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); } mksh/expr.c010064400000000000000000000606451322653124600101100ustar00/* $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 * mirabilos * * 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.103 2018/01/14 01:29:47 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 && kshuid == 0 && (mode & X_OK) && (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) && (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) rv = -1; return (rv); #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 Unicode Character Database, Version 10.0.0, by * MirOS: contrib/code/Snippets/eawparse,v 1.12 2017/09/06 16:05:45 tg Exp $ */ 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 }, { 0x0816, 0x0819 }, { 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D }, { 0x0859, 0x085B }, { 0x08D4, 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 }, { 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 }, { 0x0B56, 0x0B56 }, { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C00, 0x0C00 }, { 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 }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 }, { 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 }, { 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 }, { 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D }, { 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 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, 0x1ABE }, { 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 }, { 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 }, { 0xA926, 0xA92D }, { 0xA947, 0xA951 }, { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 }, { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BC }, { 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 (Unicode 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 mksh/exprtok.h010064400000000000000000000074641275337673700106530ustar00/*- * Copyright (c) 2016 * mirabilos * * 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.2 2016/08/12 16:48:05 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 */ FN("++", 2, P_PRIMARY, O_PLUSPLUS = 0) /* 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 mksh/funcs.c010064400000000000000000002272031322653076400102470ustar00/* $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 * mirabilos * * 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 #endif #if HAVE_SYS_SELECT_H #include #endif #if HAVE_BSTRING_H #include #endif #endif __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.353 2018/01/14 01:26:49 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}, /* deprecated, replaced by typeset -g */ {"^=global", c_typeset}, {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}, {Tsgset, 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; }; static const struct t_op { char op_text[4]; Test_op op_num; } u_ops[] = { {"-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 }, {"-n", TO_STNZE }, {"-O", TO_FILUID }, {"-o", TO_OPTION }, {"-p", TO_FILFIFO }, {"-r", TO_FILRD }, {"-S", TO_FILSOCK }, {"-s", TO_FILGZ }, {"-t", TO_FILTT }, {"-u", TO_FILSETU }, {"-v", TO_ISSET }, {"-w", TO_FILWR }, {"-x", TO_FILEX }, {"-z", TO_STZER }, {"", TO_NONOP } }; static 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 Flag(FAS_BUILTIN)) { /* BSD "echo" cmd, Debian Policy 10.4 compliant */ ++wp; bsd_echo: if (*wp && !strcmp(*wp, "-n")) { 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 Unicode */ 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) { shprintf("%s is an %s%s for ", id, (tp->flag & EXPORT) ? "exported " : "", Talias); } else if (iscommand) shprintf("%s %s=", Talias, id); 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) { if (ord(*cp) == ORD('-')) return (false); if (ord(cp[0]) == ORD('[') && ord(cp[1]) == ORD('[') && !cp[2]) return (false); 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); shf_puts(ap->name, shl_stdout); 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); shf_puts(ap->name, shl_stdout); 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 bool list = false; const char *cp; char *up; while ((optc = ksh_getopt(wp, &builtin_opt, #ifndef MKSH_SMALL "lm" #else "l" #endif )) != -1) switch (optc) { case 'l': list = true; break; #ifndef MKSH_SMALL case 'm': macro = true; break; #endif case '?': return (1); } wp += builtin_opt.optind; if (*wp == NULL) /* list all */ rv = x_bind(NULL, NULL, #ifndef MKSH_SMALL false, #endif list); for (; *wp != NULL; wp++) { if ((cp = cstrchr(*wp, '=')) == NULL) up = NULL; else { strdupx(up, *wp, ATEMP); up[cp++ - *wp] = '\0'; } if (x_bind(up ? up : *wp, cp, #ifndef MKSH_SMALL macro, #endif false)) rv = 1; afree(up, ATEMP); } return (rv); } #endif int c_shift(const char **wp) { struct block *l = e->loc; int n; mksh_ari_t val; const char *arg; 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); } static const char REPLY[] = "REPLY"; 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, "-u", builtin_opt.optarg, ccp); return (2); } break; case '?': return (2); } wp += builtin_opt.optind; if (*wp == NULL) *--wp = REPLY; 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; unsigned char savef; 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; savef = Flag(FERREXIT); Flag(FERREXIT) |= 0x80; rv = shell(s, 2); Flag(FERREXIT) = savef; 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; const char **owp; 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) { 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 ::= */ /* 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((name), (buffer)) #else #define test_access(name, mode) access((name), (mode)) #define test_stat(name, buffer) stat((name), (buffer)) #endif int test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, bool do_eval) { 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 (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode)); /* -c */ case TO_FILCDEV: return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode)); /* -b */ case TO_FILBDEV: return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode)); /* -p */ case TO_FILFIFO: return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode)); /* -h or -L */ case TO_FILSYM: #ifdef MKSH__NO_SYMLINK return (0); #else return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode)); #endif /* -S */ case TO_FILSOCK: return (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 = (stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode)); afree(nv, ATEMP); return (i); } #else return (0); #endif /* -u */ case TO_FILSETU: return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISUID) == S_ISUID); /* -g */ case TO_FILSETG: return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISGID) == S_ISGID); /* -k */ case TO_FILSTCK: #ifdef S_ISVTX return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISVTX) == S_ISVTX); #else return (0); #endif /* -s */ case TO_FILGZ: return (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 (stat(opnd1, &b1) == 0 && (uid_t)b1.st_uid == ksheuid); /* -G */ case TO_FILGID: return (stat(opnd1, &b1) == 0 && (gid_t)b1.st_gid == getegid()); /* * 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); /* -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 */ /* -nt */ case TO_FILNT: /* * ksh88/ksh93 succeed if file2 can't be stated * (subtly different from 'does not exist'). */ return (stat(opnd1, &b1) == 0 && (((s = stat(opnd2, &b2)) == 0 && b1.st_mtime > b2.st_mtime) || s < 0)); /* -ot */ case TO_FILOT: /* * ksh88/ksh93 succeed if file1 can't be stated * (subtly different from 'does not exist'). */ return (stat(opnd2, &b2) == 0 && (((s = stat(opnd1, &b1)) == 0 && b1.st_mtime < b2.st_mtime) || s < 0)); /* -ef */ case TO_FILEQ: return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 && b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); /* all other cases */ case TO_NONOP: case TO_NONNULL: /* throw the error */ break; } (*te->error)(te, 0, "internal error: unknown op"); return (1); } 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[] = { "-o", "-a", "!", "(", ")" }; 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) #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 = ""; 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, "u")) != -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, "", 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 mksh/histrap.c010064400000000000000000001065471314217336000106020ustar00/* $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 * mirabilos * * 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 #endif __RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.166 2017/08/07 23:25:09 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 > 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 * */ #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: histfsize = lseek(histfd, (off_t)0, SEEK_END); 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); sizenow = lseek(histfd, (off_t)0, SEEK_END); 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; } histfsize = lseek(histfd, (off_t)0, SEEK_END); 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 mksh/jehanne.c010064400000000000000000000025071321723144300105270ustar00/*- * Copyright (c) 2017 * Giacomo Tesio * * 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 #include #include void __application_newlib_init(int argc, char *argv[]) { rfork(RFFDG | RFREND | RFNOTEG); libposix_emulate_SIGCHLD(); } mksh/jobs.c010064400000000000000000001316421322375553500100710ustar00/* $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 * mirabilos * * 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.125 2018/01/05 20:08:34 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 { 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_UNEMPLOYED bool mflagset = Flag(FMONITOR) != 127; Flag(FMONITOR) = 0; #endif #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 (!mflagset && Flag(FTALKING)) Flag(FMONITOR) = 1; /* * 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 newj = alloc(sizeof(Job), APERM); /* brute force method */ for (i = 1; ; i++) { for (j = job_list; j && j->job != i; j = j->next) ; if (j == NULL) break; } 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_sD_s_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; } } mksh/lalloc.c010064400000000000000000000112601266414457200103740ustar00/*- * Copyright (c) 2009, 2010, 2011, 2013, 2014, 2016 * mirabilos * * 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 #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); } } mksh/lex.c010064400000000000000000001201751322653277400077240ustar00/* $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 * * 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.247 2018/01/14 01:44:01 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; } Lex_state; #define ls_base u.base #define ls_start u.start #define ls_bool u.abool #define ls_adelim u.adelim typedef struct { Lex_state *base; Lex_state *end; } State_info; static void readhere(struct ioword *); static 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 { \ if (++statep == state_info.end) \ statep = push_state_i(&state_info, statep); \ state = statep->type = (s); \ } 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; /* 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 ((cf & 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 ((cf & 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); 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); PUSH_STATE(SADELIM); statep->ls_adelim.delimiter = ':'; statep->ls_adelim.num = 1; statep->nparen = 0; break; } else if (ctype(c, C_DIGIT | C_DOLAR | C_SPC) || /*XXX what else? */ c == '(' /*)*/) { /* substring subst. */ if (c != ' ') { *wp++ = CHAR; *wp++ = ' '; } ungetsc(c); PUSH_STATE(SBRACE); 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); 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); } else { ungetsc(c); if (state == SDQUOTE || state == SQBRACE) PUSH_STATE(SQBRACE); else PUSH_STATE(SBRACE); } } 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 ((cf & 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 <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 "<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 (c == *eofp++) /* store; then read and compare next character */ goto heredoc_store_and_loop; /* nope, mismatch; read until end of line */ while (c != '\n') { if (!c) /* oops, reached EOF */ yyerror(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 = tobool(interactive && (s->flags & SF_TTY)); /* 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); } mksh/lksh.1010064400000000000000000000177521307017732500100130ustar00.\" $MirOS: src/bin/mksh/lksh.1,v 1.23 2017/04/02 13:38:02 tg Exp $ .\"- .\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017 .\" mirabilos .\" .\" 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: April 2 2017 $ .\" .\" 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 .ds tT bsd .\} .el .ds tT ucb .\" .\" Implement .Mx (MirBSD) .\" .ie "\*(tT"gnu" \{\ . eo . de Mx . nr curr-font \n[.f] . nr curr-size \n[.ps] . ds str-Mx \f[\n[curr-font]]\s[\n[curr-size]u] . ds str-Mx1 \*[Tn-font-size]\%MirOS\*[str-Mx] . if !\n[arg-limit] \ . if \n[.$] \{\ . ds macro-name Mx . parse-args \$@ . \} . if (\n[arg-limit] > \n[arg-ptr]) \{\ . nr arg-ptr +1 . ie (\n[type\n[arg-ptr]] == 2) \ . as str-Mx1 \~\*[arg\n[arg-ptr]] . el \ . nr arg-ptr -1 . \} . ds arg\n[arg-ptr] "\*[str-Mx1] . nr type\n[arg-ptr] 2 . ds space\n[arg-ptr] "\*[space] . nr num-args (\n[arg-limit] - \n[arg-ptr]) . nr arg-limit \n[arg-ptr] . if \n[num-args] \ . parse-space-vector . print-recursive .. . ec . ds sP \s0 . ds tN \*[Tn-font-size] .\} .el \{\ . de Mx . nr cF \\n(.f . nr cZ \\n(.s . ds aa \&\f\\n(cF\s\\n(cZ . if \\n(aC==0 \{\ . ie \\n(.$==0 \&MirOS\\*(aa . el .aV \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 . \} . if \\n(aC>\\n(aP \{\ . nr aP \\n(aP+1 . ie \\n(C\\n(aP==2 \{\ . as b1 \&MirOS\ #\&\\*(A\\n(aP\\*(aa . ie \\n(aC>\\n(aP \{\ . nr aP \\n(aP+1 . nR . \} . el .aZ . \} . el \{\ . as b1 \&MirOS\\*(aa . nR . \} . \} .. .\} .\"- .Dt 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 using the mailing list at .Aq miros\-mksh@mirbsd.org 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 . mksh/main.c010064400000000000000000001364071322647677600100760ustar00/* $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 * mirabilos * * 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 #endif #if HAVE_SETLOCALE_CTYPE #include #endif __RCSID("$MirOS: src/bin/mksh/main.c,v 1.347 2018/01/13 21:45:07 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, "-r", initvsn, NULL, Ttypeset, "-x", "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, "-r", TPATH, "ENV", TSHELL, NULL }; static bool initio_done; /* top-level parsing and execution environment */ static struct env env; struct env *e = &env; /* compile-time assertions */ #define cta(name, expr) struct cta_ ## name { char t[(expr) ? 1 : -1]; } /* this one should be defined by the standard */ cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) && (sizeof(unsigned char) == 1)); cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) && ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF)); /* the next assertion is probably not really needed */ cta(short_is_2_char, sizeof(short) == 2); cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short)); /* the next assertion is probably not really needed */ cta(int_is_4_char, sizeof(int) == 4); cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int)); cta(long_ge_int, sizeof(long) >= sizeof(int)); cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long)); #ifndef MKSH_LEGACY_MODE /* the next assertion is probably not really needed */ cta(ari_is_4_char, sizeof(mksh_ari_t) == 4); /* but this is */ cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1)); /* the next assertion is probably not really needed */ cta(uari_is_4_char, sizeof(mksh_uari_t) == 4); /* but the next three are; we REQUIRE unsigned integer wraparound */ cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1)); cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3)); cta(uari_wrap_32_bit, (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) > (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4)); #endif /* these are always required */ cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0); cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0); /* we require these to have the precisely same size and assume 2s complement */ cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t)); cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t)); cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *)); cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void))); /* our formatting routines assume this */ cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long)); cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long)); static mksh_uari_t rndsetup(void) { 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, 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); #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; if (*ccp == '-') ++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 54 builtins: 75% of 128 = 2^7 */ 7); for (i = 0; mkshbuiltins[i].name != NULL; i++) if (!strcmp(ccp, builtin(mkshbuiltins[i].name, mkshbuiltins[i].func))) Flag(FAS_BUILTIN) = 1; if (!Flag(FAS_BUILTIN)) { /* check for -T option early */ argi = parse_args(argv, OF_FIRSTTIME, NULL); if (argi < 0) return (1); #if defined(MKSH_BINSHPOSIX) || defined(MKSH_BINSHREDUCED) /* are we called as -sh or /bin/sh or so? */ if (!strcmp(ccp, "sh" MKSH_EXE_EXT)) { /* 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; /* this to note if monitor is set on command line (see below) */ #ifndef MKSH_UNEMPLOYED Flag(FMONITOR) = 127; #endif /* this to note if utf-8 mode is set on command line (see below) */ UTFMODE = 2; if (!Flag(FAS_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 (Flag(FAS_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, 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 = ""; 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 (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) && Flag(FTALKING)) 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 (Flag(FAS_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; /* * 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)) { 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); } } else { include(MKSH_SUID_PROFILE, 0, NULL, true); /* turn off -p if not set explicitly */ if (Flag(FPRIVILEGED) != 1) change_flag(FPRIVILEGED, OF_INTERNAL, false); } if (restricted_shell) { c_builtin(restr_com); /* After typeset command... */ Flag(FRESTRICTED) = 1; } Flag(FERREXIT) = errexit; if (Flag(FTALKING) && s) hist_init(s); else /* set after ENV */ Flag(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 (Flag(FAS_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, 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: /* 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 (interactive) really_exit = false; switch ((i = kshsetjmp(e->jbuf))) { case 0: break; case LBREAK: case LCONTIN: if (level != 2) { source = old_source; quitenv(NULL); internal_errorf(Tf_cant_s, Tshell, i == LBREAK ? Tbreak : Tcontinue); /* NOTREACHED */ } /* assert: interactive == false */ /* FALLTHROUGH */ case LINTR: /* we get here if SIGINT not caught or ignored */ case LERROR: 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); /* 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(); } quitenv(NULL); source = old_source; return (exstat & 0xFF); } /* return to closest error handler or shell(), exit if none found */ /* note: i MUST NOT be 0 */ void unwind(int i) { /* * This is a kludge. We need to restore everything that was * changed in the new environment, see cid 1005090337C7A669439 * and 10050903386452ACBF1, but fail to even save things most of * the time. funcs.c:c_eval() changes FERREXIT temporarily to 0, * which needs to be restored thus (related to Debian #696823). * We did not save the shell flags, so we use a special or'd * value here... this is mostly to clean up behind *other* * callers of unwind(LERROR) here; exec.c has the regular case. */ if (Flag(FERREXIT) & 0x80) { /* GNU bash does not run this trapsig */ trapsig(ksh_SIGERR); Flag(FERREXIT) &= ~0x80; } /* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */ if (i == LEXIT || ((i == LERROR || 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) == 1 && (i == LERROR || i == LINTR)) { ++trap_nested; runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1); --trap_nested; i = LLEAVE; } 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 = 0; /* jump buffer is invalid because flags == 0 */ 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); } } /* 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"); lfp = shf_smprintf(Tf_sSs, lfp, "mksh-dbg.txt"); } 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 & OSF/1 processes discard(flush) when ~icanon */ #if defined(VDISCARD) && defined(_POSIX_VDISABLE) cb.c_cc[VDISCARD] = _POSIX_VDISABLE; #endif cb.c_cc[VTIME] = 0; cb.c_cc[VMIN] = 1; 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') { xp = shf_smprintf(Tf_sSs, MKSH_ENVDIR, dent->d_name); 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; 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)) warningf(true, "early locale tracking enabled UTF-8 mode while in POSIX mode, you are now noncompliant"); } #endif mksh/mirhash.h010064400000000000000000000205131262663013000105530ustar00/*- * Copyright © 2011, 2014, 2015 * mirabilos * * 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 __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 mksh/misc.c010064400000000000000000001605711322651712200100610ustar00/* $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 * mirabilos * Copyright (c) 2015 * Daniel Richard G. * * 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 #endif #if HAVE_GRP_H #include #endif __RCSID("$MirOS: src/bin/mksh/misc.c,v 1.291 2018/01/14 00:03:03 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 void printoptions(bool); /* 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 void printoptions(bool verbose) { size_t i = 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); while (i < NELEM(options)) { if (Flag(i) && OFN(i)[0]) shprintf(" -o %s", OFN(i)); ++i; } shf_putc('\n', shl_stdout); } } 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; unsigned char newval = (newset ? 1 : 0); if (f == FXTRACE) { change_xtrace(newval, true); return; } oldval = Flag(f); Flag(f) = newval = (newset ? 1 : 0); #ifndef MKSH_UNEMPLOYED if (f == FMONITOR) { if (what != OF_CMDLINE && newval != oldval) j_change(); } else #endif #ifndef MKSH_NO_CMDLINE_EDITING if (( #if !MKSH_S_NOVI f == FVI || #endif f == FEMACS || f == FGMACS) && newval) { #if !MKSH_S_NOVI Flag(FVI) = #endif Flag(FEMACS) = Flag(FGMACS) = 0; Flag(f) = newval; } else #endif if (f == FPRIVILEGED && oldval && !newval) { /* Turning off -p? */ /*XXX this can probably be optimised */ kshegid = kshgid = getgid(); ksheuid = kshuid = getuid(); #if HAVE_SETRESUGID DO_SETUID(setresgid, (kshegid, kshegid, kshegid)); #if HAVE_SETGROUPS /* setgroups doesn't EAGAIN on Linux */ setgroups(1, &kshegid); #endif DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid)); #else /* !HAVE_SETRESUGID */ /* setgid, setegid, seteuid don't EAGAIN on Linux */ setgid(kshegid); #ifndef MKSH__NO_SETEUGID setegid(kshegid); #endif DO_SETUID(setuid, (ksheuid)); #ifndef MKSH__NO_SETEUGID seteuid(ksheuid); #endif #endif /* !HAVE_SETRESUGID */ } else if ((f == FPOSIX || f == FSH) && newval) { /* Turning on -o posix or -o sh? */ Flag(FBRACEEXPAND) = 0; /* Turning on -o posix? */ if (f == FPOSIX) { /* C locale required for compliance */ UTFMODE = 0; } } else if (f == FTALKING) { /* Changing interactive flag? */ if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid) Flag(FTALKING_I) = newval; } } 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; const char *array = NULL; Getopt go; size_t i; int optc, arrayset = 0; bool sortargs = false; bool fcompatseen = false; if (what == OF_CMDLINE) { const char *p = argv[0], *q; /* * Set FLOGIN before parsing options so user can clear * flag using +l. */ if (*p != '-') for (q = p; *q; ) if (mksh_cdirsep(*q++)) p = q; Flag(FLOGIN) = (*p == '-'); opts = cmd_opts; } else if (what == OF_FIRSTTIME) { opts = cmd_opts; } else opts = set_opts; 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). */ printoptions(set); 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 { 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, 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 */ ipath = shf_smprintf(Tf_sss, ldest, upath[2] ? "/" : "", upath + 2); #endif } else { /* upath is a relative pathname, prepend cwd */ if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp)) return (NULL); ipath = shf_smprintf(Tf_sss, tp, "/", upath); afree(tp, ATEMP); } /* ipath and upath are in memory at the same time -> unchecked */ Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); /* now jump into the deep of the loop */ goto beginning_of_a_pathname; while (*ip) { /* skip slashes in input */ while (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 */ tp = shf_smprintf(Tf_sss, ldest, *ip ? "/" : "", ip); 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))) { dir = shf_smprintf(Tf_sss, tryp, dir[2] ? "/" : "", dir + 2); 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), Unicode (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: Unicode if >0xFF) * u/U: look for a hexadecimal number with up to * four (U: eight) digits; convert to Unicode */ 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') /* Unicode marker */ wc += 0x100; break; case '\'': if (!cstyle) goto unknown_escape; wc = '\''; break; case '\\': wc = '\\'; break; default: unknown_escape: (*fp)(fc); return (-1); } return (wc); } mksh/mksh.1010064400000000000000000005262211314513573100100060ustar00.\" $MirOS: src/bin/mksh/mksh.1,v 1.451 2017/08/16 21:40:14 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 .\" mirabilos .\" .\" 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: August 16 2017 $ .\" .\" 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 .ds tT bsd .\} .el .ds tT ucb .\" .\" Implement .Mx (MirBSD) .\" .ie "\*(tT"gnu" \{\ . eo . de Mx . nr curr-font \n[.f] . nr curr-size \n[.ps] . ds str-Mx \f[\n[curr-font]]\s[\n[curr-size]u] . ds str-Mx1 \*[Tn-font-size]\%MirOS\*[str-Mx] . if !\n[arg-limit] \ . if \n[.$] \{\ . ds macro-name Mx . parse-args \$@ . \} . if (\n[arg-limit] > \n[arg-ptr]) \{\ . nr arg-ptr +1 . ie (\n[type\n[arg-ptr]] == 2) \ . as str-Mx1 \~\*[arg\n[arg-ptr]] . el \ . nr arg-ptr -1 . \} . ds arg\n[arg-ptr] "\*[str-Mx1] . nr type\n[arg-ptr] 2 . ds space\n[arg-ptr] "\*[space] . nr num-args (\n[arg-limit] - \n[arg-ptr]) . nr arg-limit \n[arg-ptr] . if \n[num-args] \ . parse-space-vector . print-recursive .. . ec . ds sP \s0 . ds tN \*[Tn-font-size] .\} .el \{\ . de Mx . nr cF \\n(.f . nr cZ \\n(.s . ds aa \&\f\\n(cF\s\\n(cZ . if \\n(aC==0 \{\ . ie \\n(.$==0 \&MirOS\\*(aa . el .aV \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 . \} . if \\n(aC>\\n(aP \{\ . nr aP \\n(aP+1 . ie \\n(C\\n(aP==2 \{\ . as b1 \&MirOS\ #\&\\*(A\\n(aP\\*(aa . ie \\n(aC>\\n(aP \{\ . nr aP \\n(aP+1 . nR . \} . el .aZ . \} . el \{\ . as b1 \&MirOS\\*(aa . nR . \} . \} .. .\} .\"- .Dt MKSH 1 .Os MirBSD .Sh NAME .Nm mksh , .Nm sh .Nd MirBSD Korn shell .Sh SYNOPSIS .Nm .Bk -words .Op Fl +abCefhiklmnprUuvXx .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 see the FAQ at the end of this document. .Ss Invocation Most builtins can be called directly, for example if a link points from its name to the shell; not all make sense, have been tested or work at all though. .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 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 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 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 basename the shell was called with (i.e. argv[0]) is used. .Pp The exit status of the shell is 127 if the command file specified on the command line could not be opened, or non-zero if a fatal syntax error occurred during the execution of a script. In the absence of fatal errors, the exit status is that of the last command executed, or zero if no command is executed. .Ss Startup files For the actual location of these files, see .Sx FILES . A login shell processes the system profile first. A privileged shell then processes the suid profile. A non-privileged login shell processes the user profile next. A non-privileged interactive shell checks the value of the .Ev ENV parameter after subjecting it to parameter, command, arithmetic and tilde .Pq 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 \*(en everything after the .Ql # up to the nearest newline is ignored; .Ql $ is used to introduce parameter, command and arithmetic substitutions (see .Sx Substitution below); .Ql \` introduces an old-style command substitution (see .Sx Substitution below); .Ql \*(TI begins a directory expansion (see .Sx Tilde expansion below); .Ql { and .Ql } delimit .Xr csh 1 Ns -style 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 is the opposite \*(en .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 Pq Ar list Execute .Ar list in a subshell. There is no implicit way to pass environment changes from a subshell back to its parent. .It { Ar list ; No } Compound construct; .Ar list is executed, but not in a subshell. Note that .Dq Li { and .Dq Li } are reserved words, not meta-characters. .It Xo case Ar word No in .Oo Op \&( .Ar pattern .Op \*(Ba Ar pattern .No ... Ns ) .Ar list .Ic terminator .Oc No ... esac .Xc The .Ic case statement attempts to match .Ar word against a specified .Ar pattern ; the .Ar list associated with the first successfully matched pattern is executed. Patterns used in .Ic case statements are the same as those used for file name patterns except that the restrictions regarding .Ql \&. and .Ql / are dropped. Note that any unquoted space before and after a pattern is stripped; any space within a pattern must be quoted. Both the word and the patterns are subject to parameter, command and arithmetic substitution, as well as tilde substitution. .Pp For historical reasons, open and close braces may be used instead of .Ic in and .Ic esac e.g.\& .Ic case $foo { *) echo bar ;; } . .Pp The list .Ic terminator 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 for Ar name .Oo in Ar word No ... Oc ; .No do Ar list ; No done .Xc For each .Ar word in the specified word list, the parameter .Ar name is set to the word and .Ar list is executed. If .Ic in is not used to specify a word list, the positional parameters ($1, $2, etc.) are used instead. For historical reasons, open and close braces may be used instead of .Ic do and .Ic done e.g.\& .Ic for i; { echo $i; } . The exit status of a .Ic for statement is the last exit status of .Ar list ; if .Ar list is never executed, the exit status is zero. .It Xo if Ar list ; .No then Ar list ; .Oo elif Ar list ; .No then Ar list ; Oc .No ... .Oo else Ar list ; Oc .No fi .Xc If the exit status of the first .Ar list is zero, the second .Ar list is executed; otherwise, the .Ar list following the .Ic elif , if any, is executed with similar consequences. If all the lists following the .Ic if and .Ic elif Ns s fail (i.e. exit with non-zero status), the .Ar list following the .Ic else is executed. The exit status of an .Ic if statement is that of non-conditional .Ar list that is executed; if no non-conditional .Ar list is executed, the exit status is zero. .It Xo select Ar name .Oo in Ar word No ... Oc ; .No do Ar list ; No done .Xc The .Ic select statement provides an automatic method of presenting the user with a menu and selecting from it. An enumerated list of the specified .Ar word Ns (s) is printed on standard error, followed by a prompt .Po .Ev PS3 : normally .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/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. If .Dq in Ar word ... is omitted, the positional parameters are used (i.e. $1, $2, etc.). For historical reasons, open and close braces may be used instead of .Ic do and .Ic done e.g.\& .Ic select i; { echo $i; } . The exit status of a .Ic select statement is zero if a .Ic break statement is used to exit the loop, non-zero otherwise. .It Xo until Ar list ; .No do Ar list ; .No done .Xc This works like .Ic while , except that the body is executed only while the exit status of the first .Ar list is non-zero. .It Xo while Ar list ; .No do Ar list ; .No done .Xc A .Ic while is a pre-checked loop. Its body is executed as often as the exit status of the first .Ar list is zero. The exit status of a .Ic while statement is the last exit status of the .Ar list in the body of the loop; if the body is not executed, the exit status is zero. .It Xo function Ar name .No { Ar list ; No } .Xc Defines the function .Ar name (see .Sx Functions below). Note that redirections specified after a function definition are performed whenever the function is executed, not when the function definition is executed. .It Ar name Ns \&() Ar command Mostly the same as .Ic function (see .Sx Functions below). Whitespace (space or tab) after .Ar name will be ignored most of the time. .It Xo function Ar name Ns \&() .No { Ar list ; No } .Xc The same as .Ar name Ns \&() .Pq Nm bash Ns ism . The .Ic function keyword is ignored. .It Xo Ic time Op Fl p .Op Ar pipeline .Xc The .Sx Command execution section describes the .Ic time reserved word. .It \&(( Ar expression No )) The arithmetic expression .Ar expression is evaluated; equivalent to .Dq Li let \&" Ns Ar expression Ns \&" (see .Sx Arithmetic expressions and the .Ic let command, below) in a compound construct. .It Bq Bq Ar \ \&expression\ \& Similar to the .Ic test and .Ic \&[ ... \&] commands (described later), with the following exceptions: .Bl -bullet .It Field splitting and file name generation are not performed on arguments. .It The .Fl a .Pq AND and .Fl o .Pq OR operators are replaced with .Dq Li && and .Dq Li \*(Ba\*(Ba , respectively. .It Operators (e.g.\& .Dq Li \-f , .Dq Li = , .Dq Li \&! ) must be unquoted. .It Parameter, command and arithmetic substitutions are performed as expressions are evaluated and lazy expression evaluation is used for the .Dq Li && and .Dq Li \*(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 Li != and .Dq Li = expressions are a subset of patterns (e.g. the comparison .Ic \&[[ foobar = f*r ]] succeeds). This even works indirectly: .Bd -literal -offset indent $ bar=foobar; baz=\*(aqf*r\*(aq $ [[ $bar = $baz ]]; echo $? $ [[ $bar = \&"$baz" ]]; echo $? .Ed .Pp Perhaps surprisingly, the first comparison succeeds, whereas the second doesn't. This does not apply to all extglob metacharacters, currently. .El .El .Ss Quoting Quoting is used to prevent the shell from treating characters or words specially. There are three methods of quoting. First, .Ql \e quotes the following character, unless it is at the end of a line, in which case both the .Ql \e and the newline are stripped. Second, a single quote .Pq 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#### , .Dq # means a hexadecimal digit, of which there may be none up to four or eight; these escapes translate a Unicode codepoint to UTF-8. Furthermore, .Dq Li \eE and .Dq Li \ee expand to the escape character. .Pp In the .Ic print builtin mode, .Dq Li \e" , .Dq Li \e\*(aq and .Dq Li \e? are explicitly excluded; octal sequences must have the none up to three octal digits .Dq # prefixed with the digit zero .Pq Dq Li \e0### ; hexadecimal sequences .Dq Li \ex## are limited to none up to two hexadecimal digits .Dq # ; both octal and hexadecimal sequences convert to raw octets; .Dq Li \e# , where # is none of the above, translates to \e# (backslashes are retained). .Pp Backslash expansion in the C style mode slightly differs: octal sequences .Dq Li \e### must have no digit zero prefixing the one up to three octal digits .Dq # and yield raw octets; hexadecimal sequences .Dq Li \ex#* greedily eat up as many hexadecimal digits .Dq # as they can and terminate with the first non-hexadecimal digit; these translate a Unicode codepoint to UTF-8. The sequence .Dq Li \ec# , where .Dq # is any octet, translates to Ctrl-# (which basically means, .Dq Li \ec? becomes DEL, everything else is bitwise ANDed with 0x1F). Finally, .Dq Li \e# , where # is none of the above, translates to # (has the backslash trimmed), even if it is a newline. .Ss Aliases There are two types of aliases: normal command aliases and tracked aliases. Command aliases are normally used as a short hand for a long or often used command. The shell expands command aliases (i.e. substitutes the alias name for its value) when it reads the first word of a command. An expanded alias is re-processed to check for more aliases. If a command alias ends in a space or tab, the following word is also checked for alias expansion. The alias expansion process stops when a word that is not an alias is found, when a quoted word is found, or when an alias word that is currently being expanded is found. 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. 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 immediately concatenated. The fourth way of setting a parameter is with the .Ic export , .Ic global , .Ic readonly and .Ic typeset commands; see their descriptions in the .Sx Command execution section. Fifth, .Ic for and .Ic select loops set parameters as well as the .Ic getopts , .Ic read and .Ic set 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 (if .Ar name is an array, the element with the key .Dq 0 will be substituted in scalar context): .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. Cannot be applied to a vector .Pq ${*} or ${@} or ${array[*]} or ${array[@]} . .Pp .Sm off .It Xo .Pf ${ Ar name .Pf % Ar pattern No } .Xc .It Xo .Pf ${ Ar name .Pf %% Ar pattern No } .Xc .Sm on Like ${...#...} substitution, but it deletes from the end of the value. Cannot be applied to a vector. .Pp .Sm off .It Xo .Pf ${ Ar name .Pf / Ar pattern / Ar string No } .Xc .It Xo .Pf ${ Ar name .Pf /# Ar pattern / Ar string No } .Xc .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. Cannot be applied to a vector. Inefficiently implemented, may be slow. .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. .Pp .Sm off .It Xo .Pf ${ Ar name : Ns Ar pos .Pf : Ns Ar len Ns } .Xc .Sm on The first .Ar len characters of .Ar name , starting at position .Ar pos , are substituted. Both .Ar pos and .Pf : Ns Ar len are optional. If .Ar pos is negative, counting starts at the end of the string; if it is omitted, it defaults to 0. If .Ar len is omitted or greater than the length of the remaining string, all of it is substituted. Both .Ar pos and .Ar len are evaluated as arithmetic expressions. Currently, .Ar pos must start with a space, opening parenthesis or digit to be recognised. Cannot be applied to a vector. .Pp .It Pf ${ Ns Ar name Ns @#} The 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 basename the shell was invoked with (i.e.\& .Li argv[0] ) . .Ev $0 is also set to the name of the current script or the name of the current function, if it was defined with the .Ic function keyword (i.e. a Korn shell style function). .It Ev 1 No .. Ev 9 The first nine positional parameters that were supplied to the shell, function, or script sourced using the .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. Always set, defaults to 80, unless the value as reported by .Xr stty 1 is non-zero and sane enough (minimum is 12x3); 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. Do not set this value to insanely high values such as 1000000000 because .Nm can then not allocate enough memory for the history and will not start. .It Ev HOME The default directory for the .Ic cd command and the value substituted for an unqualified .Ic \*(TI (see .Sx Tilde expansion below). .It Ev IFS Internal field separator, used during substitution and by the .Ic read command, to split values into distinct arguments; normally set to space, tab and newline. See .Sx Substitution above for details. .Pp .Sy Note : This parameter is not imported from the environment when the shell is started. .It Ev KSHEGID The effective group id of the shell. .It Ev KSHGID The real group id of the shell. .It Ev KSHUID The real user id of the shell. .It Ev KSH_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 and version of the shell (read-only). See also the version commands in .Sx Emacs editing mode and .Sx Vi editing mode sections, below. .It Ev LINENO The line number of the function or shell script that is currently being executed. .It Ev LINES Set to the number of lines on the terminal or window. Always set, defaults to 24. 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. .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 Unicode 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. In Unicode mode, raw octets are mapped into the range EF80..EFFF as in OPTU-8, which is in the PUA and has been assigned by CSUR for this use. If more than one octet in ASCII mode, or a sequence of more than one octet not forming a valid and minimal CESU-8 sequence is passed, the behaviour is undefined (usually, the shell aborts with a parse error, but rarely, it succeeds, e.g. on the sequence C2 20). That's why you should always use ASCII mode unless you know that the input is well-formed UTF-8 in the range of 0000..FFFD if you use this feature, as opposed to .Ic read Fl a . .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. In the original Korn shell, exported functions are visible to shell scripts that are executed. .Pp Since functions are executed in the current shell environment, parameter assignments made inside functions are visible after the function completes. If this is not the desired effect, the .Ic typeset command can be used inside a function to create a local parameter. Note that .At .Nm ksh93 uses static scoping (one global scope, one local scope per function) 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 below). .Pp Functions defined with the .Ic function reserved word are treated differently in the following ways from functions defined with the .Ic \&() notation: .Bl -bullet .It The $0 parameter is set to the name of the function (Bourne-style functions leave $0 untouched). .It Parameter assignments preceding function calls are not kept in the shell environment (executing Bourne-style functions will keep assignments). .It .Ev OPTIND is saved/reset and restored on entry and exit from the function so .Ic getopts can be used properly both inside and outside the function (Bourne-style functions leave .Ev OPTIND untouched, so using .Ic getopts inside a function interferes with using .Ic getopts outside the function). .It 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 The original .Nm ksh and POSIX differ somewhat in which commands are considered special or regular. .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 global , source , typeset .Pp Builtins that are not special: .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: .Pp .Bl -tag -width false -compact .It Ic \&. Ar file Op Ar arg ... This is called the .Dq dot command. Execute the commands in .Ar file in the current environment. The file is searched for in the directories of .Ev PATH . If arguments are given, the positional parameters may be used to access them while .Ar file is being executed. If no arguments are given, the positional parameters are those of the environment the command is used in. .Pp .It Ic \&: Op Ar ... The null command. Exit status is set to zero. .Pp .It Ic \&[ Ar expression Ic \&] See .Ic test . .Pp .It Xo Ic alias .Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba .Cm +\-x Oc .Op Fl p .Op Cm + .Oo Ar name .Op Ns = Ns Ar value .Ar ... Oc .Xc Without arguments, .Ic alias lists all aliases. For any name without a value, the existing alias is listed. Any name with a value defines an alias; see .Sx Aliases above. .Li \&[][A\-Za\-z0\-9_!%,.@:\-] are valid in names, except they may not begin with a 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. If options were preceded with .Ql + , or a lone .Ql + is given on the command line, only .Ar name is printed. .Pp The .Fl d option causes directory aliases which are used in tilde expansion to be listed or set (see .Sx Tilde expansion above). .Pp If the .Fl p option is used, each alias is prefixed with the string .Dq Li alias\ \& . .Pp The .Fl t option indicates that tracked aliases are to be listed/set (values specified on the command line are ignored for tracked aliases). The .Fl r option indicates that all tracked aliases are to be reset. .Pp The .Fl x option sets .Pq Ic +x No clears the export attribute of an alias, or, if no names are given, lists the aliases with the export attribute (exporting an alias has no effect). .Pp .It Ic bg Op Ar job ... Resume the specified stopped job(s) in the background. If no jobs are specified, .Ic %+ is assumed. See .Sx Job control below for more information. .Pp .It Ic bind Op Fl l The current bindings are listed. If the .Fl l flag is given, .Ic bind instead lists the names of the functions to which keys may be bound. See .Sx Emacs editing mode for more information. .Pp .It Xo Ic bind Op Fl m .Ar string Ns = Ns Op Ar substitute .Ar ... .Xc .It Xo Ic bind .Ar string Ns = Ns Op Ar editing-command .Ar ... .Xc The specified editing command is bound to the given .Ar string , which should consist of a control character optionally preceded by one of the two prefix characters and optionally succeeded by a tilde character. Future input of the .Ar string will cause the editing command to be immediately invoked. If the .Fl m flag is given, the specified input .Ar string will afterwards be immediately replaced by the given .Ar substitute string which may contain editing commands but not other macros. If a tilde postfix is given, a tilde trailing the one or two prefices and the control character is ignored, any other trailing character will be processed afterwards. .Pp Control characters may be written using caret notation i.e. \*(haX represents Ctrl-X. The caret itself can be escaped by a backslash, which also escapes itself. Note that although only three prefix characters (usually ESC, \*(haX and NUL) are supported, some multi-character sequences can be supported. .Pp The following default bindings show how the arrow keys, the home, end and delete key on a BSD wsvt25, xterm\-xfree86 or GNU screen terminal are bound (of course some escape sequences won't work out quite this nicely): .Bd -literal -offset indent bind \*(aq\*(haX\*(aq=prefix\-2 bind \*(aq\*(ha[[\*(aq=prefix\-2 bind \*(aq\*(haXA\*(aq=up\-history bind \*(aq\*(haXB\*(aq=down\-history bind \*(aq\*(haXC\*(aq=forward\-char bind \*(aq\*(haXD\*(aq=backward\-char bind \*(aq\*(haX1\*(TI\*(aq=beginning\-of\-line bind \*(aq\*(haX7\*(TI\*(aq=beginning\-of\-line bind \*(aq\*(haXH\*(aq=beginning\-of\-line bind \*(aq\*(haX4\*(TI\*(aq=end\-of\-line bind \*(aq\*(haX8\*(TI\*(aq=end\-of\-line bind \*(aq\*(haXF\*(aq=end\-of\-line bind \*(aq\*(haX3\*(TI\*(aq=delete\-char\-forward .Ed .Pp .It Ic break Op Ar level Exit the .Ar level Ns th inner-most .Ic for , .Ic select , .Ic until or .Ic while loop. .Ar level defaults to 1. .Pp .It Xo .Ic builtin .Op Fl \- .Ar command Op Ar arg ... .Xc Execute the built-in command .Ar command . .Pp .It Xo .Ic \ebuiltin .Ar command Op Ar arg ... .Xc Same as .Ic builtin . Additionally acts as declaration utility forwarder, i.e. this is a declaration utility (see .Sx Tilde expansion ) .No iff Ar command is a declaration utility. .Pp .It Xo .Ic cat .Op Fl u .Op Ar .Xc Read files sequentially, in command line order, and write them 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 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 The string .Ar new is substituted for .Ar old in the current directory, and the shell attempts to change to the new directory. .Pp .It Xo .Ic command .Op Fl pVv .Ar cmd .Op Ar arg ... .Xc If neither the .Fl v nor .Fl V option is given, .Ar cmd is executed exactly as if .Ic command had not been specified, with two exceptions: firstly, .Ar cmd cannot be a shell function; and secondly, special built-in commands lose their specialness (i.e. redirection and utility errors do not cause the shell to exit, and command assignments are not permanent). The declaration utility property is not reset. .Pp If the .Fl p option is given, a default search path is used instead of the current value of .Ev PATH , the actual value of which is system dependent. .Pp If the .Fl v option is given, instead of executing .Ar cmd , information about what would be executed is given (and the same is done for .Ar arg ... ) . For 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, except it is more verbose. .Pp .It Ic continue Op Ar level Jumps to the beginning of the .Ar level Ns th inner-most .Ic for , .Ic select , .Ic until or .Ic while loop. .Ar level defaults to 1. .Pp .It Xo .Ic echo .Op Fl Een .Op Ar arg ... .Xc .Em Warning: this utility is not portable; use the Korn shell builtin .Ic print instead. .Pp Prints its arguments (separated by spaces) followed by a newline, to the standard output. The newline is suppressed if any of the arguments contain the backslash sequence .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 n option suppresses the trailing newline, .Fl e enables backslash interpretation (a no-op, since this is normally done), and .Fl E suppresses backslash interpretation. .Pp If the .Ic posix or .Ic sh option is set or this is a direct builtin call 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 Ic eval Ar command ... The arguments are concatenated (with spaces between them) to form a single string which the shell then parses and executes in the current environment. .Pp .It Xo .Ic exec .Op Fl a Ar argv0 .Op Fl c .Op Ar command Op Ar arg ... .Xc The command is executed without forking, replacing the shell process. This is currently 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 _ variable 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 The shell or subshell exits with the specified exit status. If .Ar status is not specified, the exit status is the current value of the .Ic \&$? parameter. .Pp .It Xo .Ic export .Op Fl p .Op Ar parameter Ns Op = Ns Ar value .Xc Sets the export attribute of the named parameters. Exported parameters are passed in the environment to executed commands. If values are specified, the named parameters are also assigned. 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 .Fl p , .Ic export commands suitable for re-entry. .Pp .It Ic false A command that exits with a non-zero status. .Pp .It Xo .Ic fc .Oo Fl e Ar editor \*(Ba .Fl l Op Fl n Oc .Op Fl r .Op Ar first Op Ar last .Xc .Ar first and .Ar last select commands from the history. Commands can be selected by history number (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 then executed by the shell. .Pp .It Xo .Ic fc .Cm \-e \- \*(Ba Fl s .Op Fl g .Op Ar old Ns = Ns Ar new .Op Ar prefix .Xc Re-execute the selected command (the previous command by default) after performing the optional substitution of .Ar old with .Ar new . If .Fl g is specified, all occurrences of .Ar old are replaced with .Ar new . The meaning of .Cm \-e \- and .Fl s is identical: re-execute the selected command without invoking an editor. This command is usually accessed with the predefined: .Ic alias r=\*(aqfc \-e \-\*(aq .Pp .It Ic fg Op Ar job ... Resume the specified job(s) in the foreground. If no jobs are specified, .Ic %+ is assumed. See .Sx Job control below for more information. .Pp .It Xo .Ic getopts .Ar optstring name .Op Ar arg ... .Xc Used by shell procedures to parse the specified arguments (or positional parameters, if no arguments are given) and to check for legal options. .Ar optstring contains the option letters that .Ic getopts is to recognise. If a letter is followed by a colon, the option is expected to have an argument. Options that do not take arguments may be grouped in a single argument. If an option takes an argument and the option character is not the last character of the argument it is found in, the remainder of the argument is taken to be the option's argument; otherwise, the next argument is the option's argument. .Pp Each time .Ic getopts is invoked, it places the next option in the shell parameter .Ar name and the index of the argument to be processed by the next call to .Ic getopts in the shell parameter .Ev OPTIND . If the option was introduced with a .Ql + , the option placed in .Ar name is prefixed with a .Ql + . When an option requires an argument, .Ic getopts places it in the shell parameter .Ev OPTARG . .Pp When an illegal option or a missing option argument is encountered, a question mark or a colon is placed in .Ar name (indicating an illegal option or missing argument, respectively) and .Ev OPTARG is set to the option character that caused the problem. Furthermore, if .Ar optstring does not begin with a colon, a question mark is placed in .Ar name , .Ev OPTARG is unset, and an error message is printed to standard error. .Pp When the end of the options is encountered, .Ic getopts exits with a non-zero exit status. Options end at the first (non-option argument) argument that does not start with a .Ql \- , or when a .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 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 global .Op Ic +\-aglpnrtUux .Oo Fl L Ns Op Ar n .No \*(Ba Fl R Ns Op Ar n .No \*(Ba Fl Z Ns Op Ar n Oc .Op Fl i Ns Op Ar n .Oo Ar name .Op Ns = Ns Ar value .Ar ... Oc .Xc See .Ic typeset Fl g . .No Deprecated , Em will be removed from a future version of .Nm . .Pp .It Xo .Ic hash .Op Fl r .Op Ar name ... .Xc Without arguments, any hashed executable command pathnames are listed. The .Fl r option causes all hashed commands to be removed from the hash table. Each .Ar name is searched as if it were a command name and added to the hash table if it is an executable command. .Pp .It Xo .Ic jobs .Op Fl lnp .Op Ar job ... .Xc Display information about the specified job(s); if no jobs are specified, all jobs are displayed. The .Fl n option causes information to be displayed only for jobs that have changed state since the last notification. If the .Fl l option is used, the process ID of each process in a job is also listed. The .Fl p option causes only the process group of each job to be printed. See .Sx Job control below for the format of .Ar job and the displayed job. .Pp .It Xo .Ic kill .Oo Fl s Ar signame \*(Ba .No \- Ns Ar signum \*(Ba .No \- Ns Ar signame Oc .No { Ar job \*(Ba pid \*(Ba pgrp No } .Ar ... .Xc Send the specified signal to the specified jobs, process IDs or process groups. If no signal is specified, the .Dv TERM signal is sent. If a job is specified, the signal is sent to the job's process group. See .Sx Job control below for the format of .Ar job . .Pp .It Xo .Ic kill .Fl l .Op Ar exit-status ... .Xc Print the signal name corresponding to .Ar exit-status . If no arguments are specified, a list of all the signals with their numbers and a short description of each are printed. .Pp .It Ic let Op Ar expression ... Each expression is evaluated (see .Sx Arithmetic expressions above). If all expressions are successfully evaluated, the exit status is 0 (1) if the last expression evaluated to non-zero (zero). If an error occurs during the parsing or evaluation of an expression, the exit status is greater than 1. Since expressions may need to be quoted, .No \&(( Ar expr No )) is syntactic sugar for: .Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns "\*(aq; }" .Pp .It Xo .Ic mknod .Op Fl m Ar mode .Ar name .Cm b\*(Bac .Ar major minor .Xc .It Xo .Ic mknod .Op Fl m Ar mode .Ar name .Cm p .Xc Create a device special file. The file type may be .Cm b (block type device), .Cm c (character type device) or .Cm p .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 print .Oo Fl AcelNnprsu Ns Oo Ar n Oc \*(Ba .Fl R Op Fl n Oc .Op Ar argument ... .Xc Print the specified argument(s) on the standard output, separated by spaces, terminated with a newline. The 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 Ds .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 pwd Op Fl LP Print the present working directory. If the .Fl L option is used or if the .Ic physical option isn't set (see the .Ic set command below), the logical path is printed (i.e. the path used to .Ic cd to the current directory). If the .Fl P option (physical path) is used or if the .Ic physical option is set, the path determined from the filesystem (by following .Dq Li .. directories to the root directory) is printed. .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 Reads a line of input, separates the input into fields using the .Ev IFS parameter (see .Sx Substitution above), and assigns each field to the specified parameters .Ar p . If no parameters are specified, the .Ev REPLY parameter is used to store the result. With the .Fl A and .Fl a options, only no or one parameter is accepted. If there are more parameters than fields, the extra parameters are set to the empty string or 0; if there are more fields than parameters, the last parameter is assigned the remaining fields (including the word separators). .Pp The options are as follows: .Bl -tag -width XuXnX .It Fl A Store the result into the parameter .Ar p (or .Ev REPLY ) as array of words. .It Fl a Store the result without word splitting into the parameter .Ar p (or .Ev REPLY ) as array of characters (wide characters if the .Ic utf8\-mode option is enacted, octets otherwise); the codepoints are encoded as decimal numbers by default. .It Fl d Ar x Use the first byte of .Ar x , .Dv NUL if empty, instead of the ASCII newline character as input line delimiter. .It Fl N Ar z Instead of reading till end-of-line, read exactly .Ar z bytes. 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 on this. .It Fl u Ns Op Ar n Read from the file descriptor .Ar n (defaults to 0, i.e.\& standard input). The argument must immediately follow the option character. .It Fl t Ar n Interrupt reading after .Ar n seconds (specified as positive decimal value with an optional fractional part). 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, the ASCII backslash character escapes the special meaning of the following character and is stripped from the input; .Ic read does not stop when encountering a backslash-newline sequence and does not store that newline in the result. This option enables raw mode, in which backslashes are not processed. .It Fl s The input line is saved to the history. .El .Pp If the input is a terminal, both the .Fl N and .Fl n options set it into raw mode; they read an entire file if \-1 is passed as .Ar z argument. .Pp The first parameter may have a question mark and a string appended to it, in which case the string is used as a prompt (printed to standard error before any input is read) if the input is a .Xr tty 4 (e.g.\& .Ic read nfoo?\*(aqnumber of foos: \*(aq ) . .Pp If no input is read or a timeout occurred, .Ic read exits with a non-zero status. .Pp .It Xo .Ic readonly .Op Fl p .Oo Ar parameter .Op Ns = Ns Ar value .Ar ... Oc .Xc Sets the read-only attribute of the named parameters. This is a declaration utility. If values are given, parameters are set to them before setting the attribute. Once a parameter is made read-only, it cannot be unset and its value cannot be changed. .Pp If no parameters are specified, the names of all parameters with the read-only attribute are printed one per line, unless the .Fl p option is used, in which case .Ic readonly commands defining all read-only parameters, including their values, are printed. .Pp .It Xo .Ic realpath .Op Fl \- .Ar name .Xc Prints the resolved absolute pathname corresponding to .Ar name . If .Ar name ends with a slash .Pq Ql / , it's also checked for existence and whether it is a directory; otherwise, .Ic realpath returns 0 if the pathname either exists or can be created immediately, i.e. all but the last component exist and are directories. For calls from the shell, if any options are given, an external .Xr realpath 1 utility is preferred over the builtin. .Pp .It Xo .Ic rename .Op Fl \- .Ar from to .Xc Renames the file .Ar from to .Ar to . Both must be complete pathnames and on the same device. An external utility is preferred over this builtin, which is intended for emergency situations .Pq where Pa /bin/mv No becomes unusable and directly calls .Xr rename 2 . .Pp .It Ic return Op Ar status Returns from a function or .Ic \&. script, with exit status .Ar status . If no .Ar status is given, the exit status of the last executed command is used. If used outside of a function or .Ic \&. script, it has the same effect as .Ic exit . Note that .Nm treats both profile and .Ev ENV files as .Ic \&. scripts, while the original Korn shell only treats profiles as .Ic \&. scripts. .Pp .It Xo .Ic set Op Ic +\-abCefhiklmnprsUuvXx .Op Ic +\-o Ar option .Op Ic +\-A Ar name .Op Fl \- .Op Ar arg ... .Xc The .Ic set command can be used to set .Pq Ic \- or clear .Pq Ic + shell options, set the positional parameters, or set an array parameter. Options can be changed using the .Cm +\-o Ar option syntax, where .Ar option is the long name of an option, or using the .Cm +\- Ns Ar letter syntax, where .Ar letter is the option's single letter name (not all options have a single letter name). The following table lists both option letters (if they exist) and long names along with a description of what the option does: .Bl -tag -width 3n .It Fl A Ar name Sets the elements of the array parameter .Ar name to .Ar arg ... If .Fl A is used, the array is reset (i.e. emptied) first; if .Ic +A is used, the first N elements are set (where N is the number of arguments); the rest are left untouched. .Pp An alternative syntax for the command .Ic set \-A foo \-\- a b c which is compatible to .Tn GNU .Nm bash and also supported by .At .Nm ksh93 is: .Ic foo=(a b c); foo+=(d e) .It Fl a \*(Ba Fl o Ic allexport All new parameters are created with the export attribute. .It Fl b \*(Ba Fl o Ic notify Print job notification messages asynchronously, instead of just before the prompt. Only used if job control is enabled .Pq Fl m . .It Fl C \*(Ba Fl o Ic noclobber Prevent \*(Gt redirection from overwriting existing files. Instead, \*(Gt\*(Ba must be used to force an overwrite. Note that 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 if , .Ic until , .Ic while or .Ic \&! statements. For .Ic && or .Ic \*(Ba\*(Ba , 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 a description of what this means. .It Fl k \*(Ba Fl o Ic keyword Parameter assignments are recognised anywhere in a command. .It Fl l \*(Ba Fl o Ic login The shell is a login shell. This option can only be used when the shell is invoked. See above for a description of what this means. .It Fl m \*(Ba Fl o Ic monitor Enable job control (default for interactive shells). .It Fl n \*(Ba Fl o Ic noexec Do not execute any commands. Useful for checking the syntax of scripts (ignored if interactive). .It Fl p \*(Ba Fl o Ic privileged The shell is a privileged shell. It is set automatically if, when the shell starts, the real UID or GID does not match the effective UID (EUID) or GID (EGID), respectively. See above for a description of what this means. .It Fl r \*(Ba Fl o Ic restricted The shell is a restricted shell. This option can only be used when the shell is invoked. See above for a description of what this means. .It Fl s \*(Ba Fl o Ic stdin If used when the shell is invoked, commands are read from standard input. Set automatically if the shell is invoked with no arguments. .Pp When .Fl s is used with the .Ic set command it causes the specified arguments to be sorted before assigning them to the positional parameters (or to array .Ar name , if .Fl A is used). .It Fl U \*(Ba Fl o Ic utf8\-mode Enable UTF-8 support in the .Sx Emacs editing mode and internal string handling functions. This flag is disabled by default, but can be enabled by setting it on the shell command line; is enabled automatically for interactive shells if requested at compile time, your system supports .Fn setlocale LC_CTYPE \&"" and optionally .Fn nl_langinfo CODESET , or the .Ev LC_ALL , .Ev LC_CTYPE or .Ev LANG environment variables, and at least one of these returns something that matches .Dq UTF\-8 or .Dq utf8 case-insensitively; for direct builtin calls depending on the aforementioned environment variables; or for stdin or scripts, if the input begins with a UTF-8 Byte Order Mark. .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 file name generation. .It Fl x \*(Ba Fl o Ic xtrace Print command trees when they are executed, preceded by the value of .Ev PS4 . .It Fl o Ic bgnice Background jobs are run with lower priority. .It Fl o Ic braceexpand Enable brace expansion (a.k.a. alternation). This is enabled by default. .It Fl o Ic emacs Enable BRL emacs-like command-line editing (interactive shells only); see .Sx Emacs editing mode . .It Fl o Ic gmacs Enable gmacs-like command-line editing (interactive shells only). Currently identical to emacs editing except that transpose\-chars (\*(haT) acts slightly differently. .It Fl o Ic ignoreeof The shell will not (easily) exit when end-of-file is read; .Ic exit must be used. To avoid infinite loops, the shell will exit if .Dv EOF is read 13 times in a row. .It Fl o Ic inherit\-xtrace Do not reset .Fl o Ic xtrace upon entering functions. This is enabled by 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 prevents function definitions from being stored in the history file. .It Fl o Ic physical Causes the .Ic cd and .Ic pwd commands to use .Dq physical (i.e. the filesystem's) .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 the .Ic cd and .Ic pwd commands above for more details. .It Fl o Ic pipefail Make the exit status of a pipeline (before logically complementing) 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 basename of the shell invocation begins with .Dq sh and this autodetection feature is compiled in .Pq not in MirBSD . As a side effect, setting this flag turns off the .Ic braceexpand and .Ic utf8\-mode flags, which can be turned back on manually, and .Ic sh mode (unless both are enabled at the same time). .It Fl o Ic sh Enable .Pa /bin/sh .Pq kludge mode (see .Sx SH mode ) . Automatically enabled if the basename of the shell invocation begins with .Dq sh and this autodetection feature is compiled in .Pq not in MirBSD . As a side effect, setting this flag turns off .Ic braceexpand mode, which can be turned back on manually, and .Ic posix mode (unless both are enabled at the same time). .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 escape (\*(ha[) is entered in command mode. .It Fl o Ic vi\-tabcomplete In vi command-line editing, do command and file name completion when tab (\*(haI) is entered in insert mode. This is the default. .It Fl o Ic viraw No effect. In the original Korn shell, unless .Ic viraw was set, the vi command-line mode would let the .Xr tty 4 driver do the work until ESC (\*(ha[) was entered. .Nm is always in viraw mode. .El .Pp These options can also be used upon invocation of the shell. The current set of options (with single letter names) can be found in the parameter .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 will print the long names of all options that are currently on. In a future version, .Ic set +o will behave .Tn POSIX compliant and print commands to restore the current options instead. .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. If no options or arguments are given, the values of all names are printed. For unknown historical reasons, a lone .Dq Li \- option is treated specially \*(en it clears both the .Fl v and .Fl x options. .Pp .It Ic shift Op Ar number The positional parameters .Ar number Ns +1 , .Ar number Ns +2 , etc. are renamed to 1, 2, etc. .Ar number defaults to 1. .Pp .It Ic sleep Ar seconds Suspends execution for a minimum of the .Ar seconds specified as positive decimal value with an optional fractional part. Signal delivery may continue execution earlier. .Pp .It Ic source Ar file Op Ar arg ... Like .Ic \&. Po Do dot Dc Pc , except that the current working directory is appended to the search path (GNU .Nm bash extension). .Pp .It Ic suspend Stops the shell as if it had received the suspend character from the terminal. 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 \&] .Ic test evaluates the .Ar expression and returns zero status if true, 1 if false, or greater than 1 if there was an error. It is normally used as the condition command of .Ic if and .Ic while statements. Symbolic links are followed for all .Ar file expressions except .Fl h and .Fl L . .Pp The following basic expressions are available: .Bl -tag -width 17n .It Fl a Ar file .Ar file exists. .It Fl b Ar file .Ar file is a block special device. .It Fl c Ar file .Ar file is a character special device. .It Fl d Ar file .Ar file is a directory. .It Fl e Ar file .Ar file exists. .It Fl f Ar file .Ar file is a regular file. .It Fl G Ar file .Ar file Ns 's group is the shell's effective group ID. .It Fl g Ar file .Ar file Ns 's mode has the setgid bit set. .It Fl H Ar file .Ar file is a context dependent directory (only useful on HP-UX). .It Fl h Ar file .Ar file is a symbolic link. .It Fl k Ar file .Ar file Ns 's mode has the .Xr sticky 8 bit set. .It Fl L Ar file .Ar file is a symbolic link. .It Fl O Ar file .Ar file Ns 's owner is the shell's effective user ID. .It Fl 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 led by either .Ql \- or .Ql + .Pq no logical negation , for example .Dq Li \-x or .Dq Li +x instead of .Dq Li xtrace . .It Ar string No = Ar string Strings are equal. .It Ar string No == Ar string Strings are equal. .It Ar string No \*(Gt Ar string First string operand is greater than second string operand. .It Ar string No \*(Lt Ar string First string operand is less than second string operand. .It Ar string No != Ar string Strings are not equal. .It Ar number Fl eq Ar number Numbers compare equal. .It Ar number Fl ne Ar number Numbers compare not equal. .It Ar number Fl ge Ar number Numbers compare greater than or equal. .It Ar number Fl gt Ar number Numbers compare greater than. .It Ar number Fl le Ar number Numbers compare less than or equal. .It Ar number Fl \< 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 . .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 .Dq Li if \&[[ $foo = bar \&]] or, to avoid pattern matching (see .Ic \&[[ above): .Dq Li if \&[[ $foo = \&"$bar" \&]] .Pp 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 If a .Ar pipeline is given, the times used to execute the pipeline are reported. If no pipeline is given, then the user and system time used by the shell itself, and all the commands it has run since it was started, are reported. The times reported are the real time (elapsed time from start to finish), the user CPU time (time spent running in user mode), and the system CPU time (time spent running in kernel mode). Times are reported to standard error; the format of the output is: .Pp .Dl "0m0.00s real 0m0.00s user 0m0.00s system" .Pp If the .Fl p option is given the output is slightly longer: .Bd -literal -offset indent real 0.00 user 0.00 sys 0.00 .Ed .Pp It is an error to specify the .Fl p option unless .Ar pipeline is a simple command. .Pp Simple redirections of standard error do not affect the output of the .Ic time command: .Pp .Dl $ time sleep 1 2\*(Gtafile .Dl $ { time sleep 1; } 2\*(Gtafile .Pp Times for the first command do not go to .Dq afile , but those of the second command do. .Pp .It Ic times Print the accumulated user and system times used both by the shell and by processes that the shell started which have exited. The format of the output is: .Bd -literal -offset indent 0m0.00s 0m0.00s 0m0.00s 0m0.00s .Ed .Pp .It Ic trap Ar n Op Ar signal ... 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 .Pq Ar n Op Ar signal ... , all of which are treated as signals. .Pp .It Ic trap Op Ar handler signal ... 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 containing 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 of a signal .Pq e.g.\& Dv PIPE or Dv ALRM 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. .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 piped to another process (an artifact of the fact that traps are cleared when subprocesses are created). .Pp The original Korn shell's .Dv DEBUG trap and the handling of .Dv ERR and .Dv EXIT traps in functions are not yet implemented. .Pp .It Ic true A command that exits with a zero value. .Pp .It Xo .Ic typeset .Op Ic +\-aglpnrtUux .Oo Fl L Ns Op Ar n .No \*(Ba Fl R Ns Op Ar n .No \*(Ba Fl Z Ns Op Ar n Oc .Op Fl i Ns Op Ar n .Oo Ar name .Op Ns = Ns Ar value .Ar ... Oc .Xc .It Xo .Ic typeset .Fl f Op Fl tux .Op Ar name ... .Xc Display or set parameter attributes. This is a declaration utility. With no .Ar name arguments, parameter attributes are displayed; if no options are used, the 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 + , parameter values are not printed. .Pp If .Ar name arguments are given, the attributes of the named parameters are set .Pq Ic \&\- or cleared .Pq Ic \&+ ; inside a function, this will cause the parameters to be created (with no value) in the local scope (but see .Fl g ) . Values for parameters may optionally be specified. For .Ar name Ns \&[*] , the change affects 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 function names are reported. .Bl -tag -width Ds .It Fl a Indexed array attribute. .It Fl f Function mode. Display or set functions and their attributes, instead of parameters. .It Fl g Do not cause named parameters to be created in the local scope when called inside a function. .It Fl i Ns Op Ar n Integer attribute. .Ar n specifies the base to use when displaying the integer (if not specified, the base given in the first assignment is used). Parameters with this attribute may be assigned values containing arithmetic expressions. .It Fl L Ns Op Ar n Left justify attribute. .Ar n specifies the field width. If .Ar n is not specified, the current width of a parameter (or the width of its first assigned value) is used. Leading whitespace (and zeros, if used with the .Fl Z option) is stripped. If necessary, values are either truncated or space padded to fit the field width. .It Fl l Lower case attribute. All upper case 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 using .Ic eval . .It Fl p Print complete .Ic typeset commands that can be used to re-create the attributes and values of parameters. .It Fl R Ns Op Ar n Right justify attribute. .Ar n specifies the field width. If .Ar n is not specified, the current width of a parameter (or the width of its first assigned value) is used. Trailing whitespace is stripped. If necessary, values are either stripped of leading characters or space padded to make them fit the field width. .It Fl r Read-only attribute. Parameters with this attribute may not be assigned to or unset. Once this attribute is set, it cannot be turned off. .It Fl t Tag attribute. Has no meaning to the shell; provided for application use. .Pp For functions, .Fl t is the trace attribute. When functions with the trace attribute are executed, the .Ic xtrace .Pq Fl x shell option is temporarily turned on. .It Fl U Unsigned integer attribute. Integers are printed as unsigned values (combine with the .Fl i option). This option is not in the original Korn shell. .It Fl u Upper case attribute. All lower case 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 the .Fl U option.) .Pp For functions, .Fl u is the undefined attribute. See .Sx Functions above for the implications of this. .It Fl x Export attribute. Parameters (or functions) are placed in the environment of any executed commands. Exported functions are not yet implemented. .It Fl Z Ns Op Ar n Zero fill attribute. If not combined with .Fl L , this is the same as .Fl R , except zero padding is used instead of space padding. For integers, the number instead of the base is padded. .El .Pp If any of the .\" long integer , .Fl i , .Fl L , .Fl l , .Fl R , .Fl U , .Fl u or .Fl Z options are changed, all others from this set are cleared, unless they are also given on the same command line. .Pp .It Xo .Ic ulimit .Op Fl aBCcdefHilMmnOPpqrSsTtVvw .Op Ar value .Xc Display or set process limits. If no options are used, the file size limit .Pq Fl f is assumed. .Ar value , if specified, may be either an arithmetic expression or the word .Dq unlimited . The limits affect the shell and any processes created by the shell after a limit is imposed. Note that some systems may not allow limits to be increased once they are set. Also note that the types of limits available are system dependent \*(en some systems have only the .Fl f limit, or not even that, or can set only the soft limits .Bl -tag -width 5n .It Fl a Display all limits; unless .Fl H is used, soft limits are displayed. .It Fl B Ar n Set the socket buffer size to .Ar n kibibytes. .It Fl C Ar n Set the number of cached threads to .Ar n . .It Fl c Ar n Impose a size limit of .Ar n blocks on the size of core dumps. .It Fl d Ar n Impose a size limit of .Ar n kibibytes on the size of the data area. .It Fl e Ar n Set the maximum niceness to .Ar n . .It Fl f Ar n Impose a size limit of .Ar n blocks on files written by the shell and its child processes (files of any size may be read). .It Fl H Set the hard limit only (the default is to set both hard and soft limits). .It Fl i Ar n Set the number of pending signals to .Ar n . .It Fl l Ar n Impose a limit of .Ar n kibibytes on the amount of locked (wired) physical memory. .It Fl M Ar n Set the AIO locked memory to .Ar n kibibytes. .It Fl m Ar n Impose a limit of .Ar n kibibytes on the amount of physical memory used. .It Fl n Ar n Impose a limit of .Ar n file descriptors that can be open at once. .It Fl O Ar n Set the number of AIO operations to .Ar n . .It Fl P Ar n Limit the number of threads per process to .Ar n . .It Fl p Ar n Impose a limit of .Ar n processes that can be run by the user at any one time. .It Fl q Ar n Limit the size of .Tn POSIX message queues to .Ar n bytes. .It Fl r Ar n Set the maximum real-time priority to .Ar n . .It Fl S Set the soft limit only (the default is to set both hard and soft limits). .It Fl s Ar n Impose a size limit of .Ar n kibibytes on the size of the stack area. .It Fl T Ar n Impose a time limit of .Ar n real seconds to be used by each process. .It Fl t Ar n Impose a time limit of .Ar n CPU seconds spent in user mode to be used by each process. .It Fl V Ar n Set the number of vnode monitors on Haiku to .Ar n . .It Fl v Ar n Impose a limit of .Ar n kibibytes on the amount of virtual memory (address space) used. .It Fl w Ar n Impose a limit of .Ar n kibibytes on the amount of swap space used. .El .Pp As far as .Ic ulimit is concerned, a block is 512 bytes. .Pp .It Xo .Ic umask .Op Fl S .Op Ar mask .Xc Display or set the file permission creation mask or umask (see .Xr umask 2 ) . If the .Fl S option is used, the mask displayed or set is symbolic; otherwise, it is an octal number. .Pp Symbolic masks are like those used by .Xr chmod 1 . When used, they describe what permissions may be made available (as opposed to octal masks in which a set bit means the corresponding bit is to be cleared). For example, .Dq 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 The aliases for the given names are removed. If the .Fl a option is used, all aliases are removed. If the .Fl t or .Fl d options are used, the indicated operations are carried out on tracked or directory aliases, respectively. .Pp .It Xo .Ic unset .Op Fl fv .Ar parameter ... .Xc Unset the named parameters .Po .Fl v , the default .Pc or functions .Pq Fl f . With .Ar parameter Ns \&[*] , attributes are kept, only values are unset. .Pp The exit status is non-zero if any of the parameters have the read-only attribute set, zero otherwise. .Pp .It Ic wait Op Ar job ... Wait for the specified job(s) to finish. The exit status of .Ic wait is that of the last specified job; if the last job is killed by a signal, the exit status is 128 + the number of the signal (see .Ic kill Fl l Ar exit-status above); if the last specified job can't be found (because it never existed or had already finished), the exit status of .Ic wait is 127. See .Sx Job control below for the format of .Ar job . .Ic wait will return if a signal for which a trap has been set is received or if a .Dv SIGHUP , .Dv SIGINT or .Dv SIGQUIT signal is received. .Pp If no jobs are specified, .Ic wait waits for all currently running jobs (if any) to finish and exits with a zero status. If job monitoring is enabled, the completion status of jobs is printed (this is not the case when jobs are explicitly specified). .Pp .It Xo .Ic whence .Op Fl pv .Op Ar name ... .Xc Without the .Fl v option, it is the same as .Ic command Fl v , except aliases are not printed as alias command. With the .Fl v option, it is exactly the same as .Ic command Fl V . In either case, the .Fl p option differs: the search path is not affected in .Ic whence , but the search is restricted to the path. .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 .Ic fg and .Ic bg commands, and the state of the terminal is saved or restored when a foreground job is stopped or restarted, respectively. .Pp Note that only commands that create processes (e.g. asynchronous commands, subshell commands and non-built-in, non-function commands) can be stopped; commands like .Ic read cannot be. .Pp When a job is created, it is assigned a job number. For interactive shells, this number is printed inside .Dq 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 \*(en the process could be blocked waiting for some event). .It Stopped Op Ar signal The job was stopped by the indicated .Ar signal (if no signal is given, the job was stopped by .Dv SIGTSTP ) . .It Ar signal-description Op Dq core dumped The job was killed by a signal (e.g. memory fault, hangup); use .Ic kill 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 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 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}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}" .Ed .Ss Vi editing mode .Em Note: The vi command-line editing mode is orphaned, yet still functional. 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) \*(en the big-word is replaced with the resulting words. If the current big-word is the first on the line or follows one of the characters .Ql \&; , .Ql \*(Ba , .Ql & , .Ql \&( or .Ql \&) and does not contain a slash .Pq 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 .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 backwards history search 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 (for embedded systems); AOSP Android builds use .Pa /system/etc/mkshrc . .It Pa \*(TI/.profile User profile (non-privileged login shells); see .Sx Startup files near the top of this manual. .It Pa /etc/profile System profile (login shells); see .Sx Startup files. .It Pa /etc/shells Shell database. .It Pa /etc/suid_profile Suid profile (privileged shells); see .Sx Startup files. .El .Pp Note: On Android, .Pa /system/etc/ contains the system and suid profile. .Sh SEE ALSO .Xr awk 1 , .Xr cat 1 , .Xr ed 1 , .Xr getopt 1 , .Xr 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 .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 \*(en Portable Operating System Interface (POSIX)" .%V "Part 2: Shell and Utilities" .%D 1993 .%I "IEEE Press" .%P "xvii\ +\ 1195 pages" .%O "ISBN 978\-1\-55937\-255\-8 (1\-55937\-255\-9)" .Re .Rs .%A Bill Rosenblatt .%B "Learning the Korn Shell" .%D 1993 .%I "O'Reilly" .%P "360 pages" .%O "ISBN 978\-1\-56592\-054\-5 (1\-56592\-054\-6)" .Re .Rs .%A Bill Rosenblatt .%A Arnold Robbins .%B "Learning the Korn Shell, Second Edition" .%D 2002 .%I "O'Reilly" .%P "432 pages" .%O "ISBN 978\-0\-596\-00195\-7 (0\-596\-00195\-9)" .Re .Rs .%A Barry Rosenberg .%B "KornShell Programming Tutorial" .%D 1991 .%I "Addison-Wesley Professional" .%P "xxi\ +\ 324 pages" .%O "ISBN 978\-0\-201\-56324\-5 (0\-201\-56324\-X)" .Re .Sh AUTHORS .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 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 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 cause the shell to behave more conformant. .Pp For the purpose of .Tn POSIX , .Nm mksh supports only the .Dq C locale. .Nm mksh Ns 's .Ic utf8\-mode .Em must be disabled in POSIX mode, and it only supports the Unicode BMP (Basic Multilingual Plane) and maps raw octets into the U+EF80..U+EFFF wide character range; compare .Sx Arithmetic expressions . The following .Tn POSIX .Nm sh Ns -compatible code toggles the .Ic utf8\-mode option dependent on the current .Tn POSIX locale for mksh to allow using the UTF-8 mode, within the constraints outlined above, in code portable across various shell implementations: .Bd -literal -offset indent case ${KSH_VERSION:\-} in *MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*) case ${LC_ALL:\-${LC_CTYPE:\-${LANG:\-}}} in *[Uu][Tt][Ff]8*\*(Ba*[Uu][Tt][Ff]\-8*) set \-U ;; *) set +U ;; esac ;; esac .Ed In near future, (Unicode) locale tracking will be implemented though. .Pp See also the FAQ below. .Sh BUGS Suspending (using \*(haZ) pipelines like the one below will only suspend the currently running part of the pipeline; in this example, .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\ R56 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 .Aq Mt miros\-mksh@mirbsd.org mailing list 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 .Sh FREQUENTLY ASKED QUESTIONS This FAQ attempts to document some of the questions users of .Nm or readers of this manual page may encounter. .Ss I'm an Android user, so what's mksh? .Nm mksh is a .Ux shell / command interpreter, similar to .Nm COMMAND.COM or .Nm CMD.EXE , which has been included with .Tn Android Open Source Project for a while now. Basically, it's a program that runs in a terminal (console window), takes user input and runs commands or scripts, which it can also be asked to do by other programs, even in the background. Any privilege pop-ups you might be encountering are thus not .Nm mksh issues but questions by some other program utilising it. .Ss "I'm an OS/2 user, what do I need to know?" Unlike the native command prompt, the current working directory is, for security reasons common on Unix systems which the shell is designed for, not in the search path at all; if you really need this, run the command .Li PATH=.$PATHSEP$PATH or add that to a suitable initialisation file. .Pp There are two different newline modes for mksh-os2: standard (Unix) mode, in which only LF (0A hex) is supported as line separator, and "textmode", which also accepts ASCII newlines (CR+LF), like most other tools on OS/2, but creating an incompatibility with standard .Nm . If you compiled mksh from source, you will get the standard Unix mode unless .Fl T is added during compilation; you will most likely have gotten this shell through komh's port on Hobbes, or from his OS/2 Factory on eComStation Korea, which uses "textmode", though. Most OS/2 users will want to use "textmode" unless they need absolute compatibility with Unix .Nm . .Ss "How do I start mksh on a specific terminal?" Normally: .Dl mksh \-T/dev/tty2 .Pp However, if you want for it to return (e.g. for an embedded system rescue shell), use this on your real console device instead: .Dl mksh \-T!/dev/ttyACM0 .Pp .Nm can also daemonise (send to the background): .Dl mksh \-T\- \-c \*(aqexec cdio lock\*(aq .Ss "POSIX says..." Run the shell in POSIX mode (and possibly .Nm lksh instead of .Nm mksh ) : .Dl set \-o posix .Ss "My prompt from does not work!" Contact us on the mailing list or on IRC, we'll convert it for you. .Ss "Something is going wrong with my while...read loop" Most likely, you've encountered the problem in which the shell runs all parts of a pipeline as subshell. The inner loop will be executed in a subshell and variable changes cannot be propagated if run in a pipeline: .Bd -literal -offset indent bar \*(Ba baz \*(Ba while read foo; do ...; done .Ed .Pp Note that .Ic exit in the inner loop will only exit the subshell and not the original shell. Likewise, if the code is inside a function, .Ic return in the inner loop will only exit the subshell and won't terminate the function. .Pp Use co-processes instead: .Bd -literal -offset indent bar \*(Ba baz \*(Ba& while read \-p foo; do ...; done exec 3\*(Gt&p; exec 3\*(Gt&\- .Ed .Pp If .Ic read is run in a loop such as .Ic while read foo; do ...; done then leading whitespace will be removed (IFS) and backslashes processed. You might want to use .Ic while IFS= read \-r foo; do ...; done for pristine I/O. Similarly, when using the .Fl a option, use of the .Fl r option might be prudent .Pq Dq Li read \-raN\-1 arr \*(Ltfile ; the same applies for NUL-terminated lines: .Bd -literal -offset indent find . \-type f \-print0 \*(Ba& \e while IFS= read \-d \*(aq\*(aq \-pr filename; do print \-r \-\- "found \*(Lt${filename#./}\*(Gt" done .Ed .Pp .Ss "What differences in function-local scopes are there?" .Nm has a different scope model from .At .Nm ksh , which leads to subtle differences in semantics for identical builtins. This can cause issues with a .Ic nameref to suddenly point to a local variable by accident. .Pp .Tn GNU .Nm bash allows unsetting local variables; in .Nm , doing so in a function allows back access to the global variable (actually the one in the next scope up) with the same name. The following code, when run before the function definitions, changes the behaviour of .Ic unset to behave like other shells (the alias can be removed after the definitions): .Bd -literal -offset indent case ${KSH_VERSION:\-} in *MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*) function unset_compat { \e\ebuiltin typeset unset_compat_x for unset_compat_x in "$@"; do eval "\e\e\e\ebuiltin unset $unset_compat_x[*]" done } \e\ebuiltin alias unset=unset_compat ;; esac .Ed .Pp When a local variable is created (e.g. using .Ic local , .Ic typeset , .Ic integer , .Ic \e\ebuiltin typeset ) it does not, like in other shells, inherit the value from the global (next scope up) variable with the same name; it is rather created without any value (unset but defined). .Ss "I get an error in this regex comparison" Use extglobs instead of regexes: .Dl "[[ foo =~ (foo\*(Babar).*baz ]] # becomes" .Dl "[[ foo = *@(foo\*(Babar)*baz* ]] # instead" .Ss "Are there any extensions to avoid?" .Tn GNU .Nm bash supports .Dq Li &\*(Gt .Pq and Dq Li \*(Ba& to redirect both stdout and stderr in one go, but this breaks POSIX and Korn Shell syntax; use POSIX redirections instead: .Dl "foo \*(Ba& bar \*(Ba& baz &\*(Gtlog # GNU bash" .Dl "foo 2\*(Gt&1 \*(Ba bar 2\*(Gt&1 \*(Ba baz \*(Gtlog 2\*(Gt&1 # POSIX" .Ss "\*(haL (Ctrl-L) does not clear the screen" Use \*(ha[\*(haL (Escape+Ctrl-L) or rebind it: .Dl bind \*(aq\*(haL=clear-screen\*(aq .Ss "\*(haU (Ctrl-U) clears the entire line" If it should only delete the line up to the cursor, use: .Dl bind \-m \*(haU=\*(aq\*(ha[0\*(haK\*(aq .Ss "Cursor Up behaves differently from zsh" Some shells make Cursor Up search in the history only for commands starting with what was already entered. .Nm separates the shortcuts: Cursor Up goes up one command and PgUp searches the history as described above. .Ss "My question is not answered here!" Check .Pa http://www.mirbsd.org/mksh\-faq.htm which contains a collection of frequently asked questions about .Nm in general, for packagers, etc. while these above are in user scope. mksh/mksh.ico010064400000000000000000000335261217424775200104310ustar00(fh   00@@(.!( !!$!#wdcIOY`:rD3T<*@1&1*& #)'$$$ȯϨʋiHeUUkzixrfk_YNm`c[x]Ue_^Y?q<mi]YWTXUWTΕС^]feVVQQFtFyy&)&"$"!#!$%$GH;<SUبNR4p7O|Qhjy|hjdfFP4:HP:W<FQ?G6s<RXx}GRU\OUOuS:GBO>yF?vGciMoR1K5FeKx|9O9K>N>xIQ[`i8R==RA9N/f>7jE-T8*@0(:-)8-&4*#.&>MB1yF-Y:,K5'B/)D15V?#7)4A8,O7%A.%@.3O<\j%5*&5+'6,%1)'3+&/)'0*'A0&4+'5,+Y<Us`#.'&.)&R7'9.$%2*&3+"'$$)&%*'"0(%,(&-)'0+!.'#.(&4-!-'".()$&0+'1,%-)#)&$*'"&$%)'%'&*#$1+$/*$-)"'%# %-*!$#&)(!(&")'+(#'&$('$&&$%%##$$$%%%%$$$9~768qmlij|raŘhXZN_`WI[ QRbB=-,45 P3{D"#( FcA!uK%zHMg$;Ów+@1V}C0Up& Yy'<?:>/T.)*2J Snte^Ox svELfG9~768qmlij|raŘhXZN_`WI[ QRbB=-,45 P3{D"#( FcA!uK%zHMg$;Ów+@1V}C0Up& Yy'<?:>/T.)*2J Snte^Ox svELfG kd \]  o ( @#!" eaGMܰ7wE,F5'8-$)&6ZE%0*Tvhs$$$˽#332mݹ"2f>۸؟;fn>32,qx&-'FX7C2b:Vy\5CDQ.X6.R5DpL+A/jr8K.u=1x@/l<0g<8`@8O="0%ai"4}D9tF&I.,J3EmN,E21H5K4yE1[<*I2N[%1(4J3yG2qC2kB#C,*G2);.r+6.1|G0f@-R8&D/)F2*E2%<,)A0(<.*>0(9-/xF1|I/^>.Z<+K5-M7+I5-#&5+'6,%.(%(&&)'*_=/dB&L3-V;+N7%B/5&%?.(9.'5,!:*(C2&9-'2+&.)'/* 4(8VDt4>8%*'*[>"0(&-)%7-$-('0+r|'%0*'2,Pg[!1)!/(!-'%/*'1,$! &#!'$#)&%+("&$%'&-C:296EeX>KF-\KJpbm+62%-*f}AYQHZT7D@vudjUtlt?LIAOLZ}Kfa);8{!'&Qlh")(7>=Kkg&;9Z{xy8CB0XUBSR+GFE[Z.RQpedLgg4_`ukz9fk%%%$$$ iiƢȫʶ  ׿ϭ; ڸ̫k jV:<<<ǀo<Vp999ĭ<ŧwjVVVpwZhawjxiZak  uUèhUDKmUgZZM^vSpAANNA7"EE&0FX{LAVL7",,,B%2@RKeYI2M©q6,(,('.Cd<~Q'jöN"E(&!3X|~P!%ξ_)7/.CltC?+`@m}Y#.Ql{e\&4L TY|~<_"5*RVV`A氣*H< 8n111$ #*$ Mqgo1c<G-F=0\r=$))$ ]Jb^J[b4sRHb)0ss sf/IIW< >O VX>r9  eJ{ vOJ| {y zW  {(0`#!%##$!$#!#$#$#"#$!#$"#$!!" ! !! %%$!# iHt&)%xf̎_PfWn_~qwlOBWKnd܊PHRKԇYSת?=c`^\jhEE}}ؗ - "&*&%(%"$"&'&%&%DF<>:<WYOPnpĉʏ˔BGIK`c9@7<PUBPCJ>ENUtz=uCnuBU.$:,(;/(8."-&'2+ (#"*%&.)'/*(B2"1(+#Tk]%/)&+(*F6'5-'3,&-)&F4$8-1O@-A7%5-%1+&0+'1,%'&#6-y%M;$2,".)*1.EdXs$1,Vrgat,94%/+*XHy/iX_|K}oh*52dE[VKic5C@Tvp?TP2d\*B>W|vt8HF(0/kPpm.UR+JH.<;d\(..$%%;km~%%%$$$   ;     ͪ< ij: 7 =   ۭ8 8 mŮ;  = ӫ  :洳 ס7: ޞ n:: ==== סܷ=zvհ:999999iZzgzh\zzci9=bĦgzzZhħͷhi=͈aTĹĊoxwRw iv[gT5EPeYRcgxwvv͹ahbhfRtIRF+//+63BJKs=ZbxTCDMM-516ED..)3(?@js:iZ5Z6+6+*+61(32dulfʹL6nvI+))*.)(?KsO*c˺`-/.++.G?@j:?Mm˽Z&./.*,0Jjw:?'Y̸gA2++ ,'G0JjxJ00nʼIQS2'0JJJ^~dJ^J!nia3tƼvK0?d~k&1i3Xf~~:X*"%wyyY+)a%t i=;w+6fʣY%i m+e։ʬu!d(5Y2YfW@$%2,Hɴ€!'e$GG `tWV 1HH2S_>q w>Grr,1Osr]A %% "t%,VW=GG~] Hw~}p]WW]{A^%,prq ^VxqG1Ś~O,p~  xk͹~V#Pwx'pw=G4Gt$#W~=NUsV>4]jUq ~UN] }||Uq w 9|} (@##$!$$"$$#$"!"&%&$!#" &#$$ ! $""$##&#"$$#&(#()'tWz~j`JuhPEnbI=PHh`GBZSd_zuZWώߡHGEE>=2`2rqmm֛'/'&+&&*& " !%'%&'&NPHIVW_aKOln|>tD;J5?.Z4ait{&5(!)"/u;-c69{C,K1 ,">S6F4A.k98lADxNlv>U7J$\0.^87$ %!7L-\8+U5@lJ%>+%.'(1*-E3wE'T2:dE,L4':,&2)6M5J3|H1qC1mB0d?-U8'G0&D.!/%Zd'6+&1)-4/3tF(W6%M1.[<,M6*I3*G3*E2&=-)=/(5,0hB/_?-X;&J2-R9,P8+K5!9)(A0$:+)B1 2&)?0"4((;.&5+'6,(7-%1)&2*&,(%(&&)' ?+(M50W>,H6M{]&7,-$'8-&4+'5,,U;\m(8.&1*&.)'/*D-'9.JkW$3*(7.,;2!+%%/)%*'&+(#=.}&-),M;#;."5+3B:'2,*5/'7/&0+'1,'-*"&$%'&"1*%2,&K;;iU%>3$6.>XN$1,;PH5HA"-)#+(-52'QB=p^,bRq&3/Sme%3/g+;7-B=G`ZZwpV,75LxqXy#A=+HD&42{-RNc(10&.-rJki:nlf/]^@YZ%%'%%%$$$787  666|ͺ66j   5556 R 5dd   Rj d  W7 5 6 ܐ{Q2  ̽ʀ j7dR5 cd |ܔqj 6 5 ٢  6k d  d ݕ7' ݑqr46d{ 777տ8śݿR77777777hjjzq7444Ŀo7g]733333333տqj̼kO^|3Phq^}Rok8^}R^O7jOR|Pj7gD^WػP]j47j]@3Vffb^Dzn?|j3o@=1=Mmv=]hhyzz24̡hP|OD^PnDPO)&!1=<>BMZv̼xP|=GCP\o}CBMZmvT0=VO*!) &&!&!*$-:ASwxq~[/1Nسb?&*!!/1*!"AFZvxy74S$&Kc]&&*!& "AFMZw3Z9!?|C''!-FMvx4M"*Nj/ &"-FZvxp2Z-@/M;&!"""--FZwj7ZA-- ?ɠD0bTC AFFFFYuwBBFF 0Š\%U{SA-AYvxxypuumZ0Gri0;gJYtwqk̊xS=>b%U4x[)!:{~GwkN1(k~D [22j4747c='$nɭ~~DSjdD'%aŋ~CYJ!Gʼn\zJ:.Krg;;z[m4VCz$EE,EwrA+E``LCA>m`L9NU#EEYJ+Emue#;utseL9999Ll9SU#Lee`yIExwsEfwuuutstu`9#ettuueLyteI[xvu #etvx4sp4jqwsIL3~y:Lt7s,,H~qLEsw7e,He77ZH,,Xm44XHLy y_H,Xx27f__tXHXxesX_y3wkserm̡p 4mksh/os2.c010064400000000000000000000303301321723274100076170ustar00/*- * Copyright (c) 2015, 2017 * KO Myung-Hun * Copyright (c) 2017 * mirabilos * * Provided that these terms and disclaimer and all copyright notices * are retained or reproduced in an accompanying document, permission * is granted to deal in this work without restriction, including un- * limited rights to use, publicly perform, distribute, sell, modify, * merge, give away, or sublicence. * * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to * the utmost extent permitted by applicable law, neither express nor * implied; without malicious intent or gross negligence. In no event * may a licensor, author or contributor be held liable for indirect, * direct, other damage, loss, or other issues arising in any way out * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out * of said person's immediate fault when using the work as intended. */ #define INCL_DOS #include #include "sh.h" #include #include #include #include #include __RCSID("$MirOS: src/bin/mksh/os2.c,v 1.8 2017/12/22 16:41:42 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) { 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); 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() version */ int stat_ex(const char *name, struct stat *buffer) { return (access_stat_ex(stat, name, buffer)); } static int test_exec_exist(const char *name, char *real_name) { struct stat sb; if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode)) return (-1); /* safe due to calculations in real_exec_name() */ memcpy(real_name, name, strlen(name) + 1); return (0); } const char * real_exec_name(const char *name) { char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1]; const char *real_name = name; if (access_stat_ex(test_exec_exist, real_name, x_name) != -1) /*XXX memory leak */ strdupx(real_name, x_name, ATEMP); return (real_name); } /* 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); } mksh/rlimits.opt010064400000000000000000000050761263310636600111740ustar00/*- * Copyright (c) 2013, 2015 * mirabilos * * 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.3 2015/12/12 21:08:44 tg Exp $"); @RLIMITS_ITEMS #define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid), @@ /* generic options for the ulimit builtin */ 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 >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 |RLIMITS_OPTCS mksh/sh.h010064400000000000000000002540671322653332300075520ustar00/* $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 * mirabilos * * 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 #endif #include #if HAVE_BOTH_TIME_H #include #include #elif HAVE_SYS_TIME_H #include #elif HAVE_TIME_H #include #endif #include #if HAVE_SYS_SYSMACROS_H #include #endif #if HAVE_SYS_MKDEV_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #if HAVE_SYS_RESOURCE_H #include #endif #include #include #include #include #include #if HAVE_IO_H #include #endif #if HAVE_LIBGEN_H #include #endif #if HAVE_LIBUTIL_H #include #endif #include #if HAVE_PATHS_H #include #endif #include #include #include #include #include #if HAVE_STDINT_H #include #endif #include #include #include #if HAVE_STRINGS_H #include #endif #if HAVE_TERMIOS_H #include #else /* shudder… */ #include #endif #ifdef _ISC_UNIX /* XXX imake style */ #include #endif #if HAVE_ULIMIT_H #include #endif #include #if HAVE_VALUES_H #include #endif #ifdef MIRBSD_BOOTFLOPPY #include #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 */ #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.858 2018/01/14 01:47:36 tg Exp $"); #endif #define MKSH_VERSION "R56 2018/01/14" /* 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 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 /* 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 /* 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 #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 != 563) #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 #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(FUNICODE) /* * 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 */ /* 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 LLEAVE 4 /* untrappable exit/error */ #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 */ /* sort of shell global state */ EXTERN pid_t procpid; /* PID of executing process */ EXTERN int exstat; /* exit status */ EXTERN int subst_exstat; /* exit status of last $(..)/`..` */ EXTERN struct tbl *vp_pipest; /* global PIPESTATUS array */ EXTERN short trap_exstat; /* exit status before running a trap */ EXTERN uint8_t trap_nested; /* running nested traps */ EXTERN uint8_t shell_flags[FNFLAGS]; EXTERN const char *kshname; /* $0 */ EXTERN struct { uid_t kshuid_v; /* real UID of shell */ uid_t ksheuid_v; /* effective UID of shell */ gid_t kshgid_v; /* real GID of shell */ gid_t kshegid_v; /* effective GID of shell */ pid_t kshpgrp_v; /* process group of shell */ pid_t kshppid_v; /* PID of parent of shell */ pid_t kshpid_v; /* $$, shell PID */ } rndsetupstate; #define kshpid rndsetupstate.kshpid_v #define kshpgrp rndsetupstate.kshpgrp_v #define kshuid rndsetupstate.kshuid_v #define ksheuid rndsetupstate.ksheuid_v #define kshgid rndsetupstate.kshgid_v #define kshegid rndsetupstate.kshegid_v #define kshppid rndsetupstate.kshppid_v /* option processing */ #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 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) #define TPATH (TFPATH + 1) #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 Treq_arg[] E_INIT("requires an argument"); EXTERN const char Tselect[] E_INIT("select"); EXTERN const char Tsgset[] E_INIT("*=set"); #define Tset (Tf_parm + 18) #define Tsh (Tmksh + 2) #define TSHELL (TEXECSHELL + 4) #define Tshell (Ttoo_many_files + 23) EXTERN const char Tshf_read[] E_INIT("shf_read"); EXTERN const char Tshf_write[] E_INIT("shf_write"); EXTERN const char Tgsource[] E_INIT("=source"); #define Tsource (Tgsource + 1) EXTERN const char Tj_suspend[] E_INIT("j_suspend"); #define Tsuspend (Tj_suspend + 2) EXTERN const char Tsynerr[] E_INIT("syntax error"); EXTERN const char Ttime[] E_INIT("time"); EXTERN const char Ttoo_many_args[] E_INIT("too many arguments"); EXTERN const char Ttoo_many_files[] E_INIT("too many open files in shell"); EXTERN const char Ttrue[] E_INIT("true"); EXTERN const char Ttty_fd_dupof[] E_INIT("dup of tty fd"); #define Ttty_fd (Ttty_fd_dupof + 7) EXTERN const char 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) EXTERN const char Tf__ss[] E_INIT(" %s%s"); #define Tf__sN (Tf_s_s_sN + 5) EXTERN const char Tf_sSs[] E_INIT("%s/%s"); #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_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"); #define Tf_ss (Tf_sss + 2) EXTERN const char Tf_sss[] E_INIT("%s%s%s"); EXTERN const char Tf_sD_s_sD_s[] E_INIT("%s: %s %s: %s"); EXTERN const char Tf_toomany[] E_INIT("too many %ss"); 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 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 TPATH "PATH" #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 Treq_arg "requires an argument" #define Tselect "select" #define Tsgset "*=set" #define Tset "set" #define Tsh "sh" #define TSHELL "SHELL" #define Tshell "shell" #define Tshf_read "shf_read" #define Tshf_write "shf_write" #define Tgsource "=source" #define Tsource "source" #define Tj_suspend "j_suspend" #define Tsuspend "suspend" #define Tsynerr "syntax error" #define Ttime "time" #define Ttoo_many_args "too many arguments" #define Ttoo_many_files "too many open files in shell" #define Ttrue "true" #define Ttty_fd_dupof "dup of tty fd" #define Ttty_fd "tty fd" #define 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__sN " %s\n" #define Tf_sSs "%s/%s" #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_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_ss "%s%s" #define Tf_sss "%s%s%s" #define Tf_sD_s_sD_s "%s: %s %s: %s" #define Tf_toomany "too many %ss" #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 | 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) /* 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 */ /* * 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 */ /* * 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 IONAMEXP BIT(8) /* name has been expanded */ #define IOBASH BIT(9) /* &> etc. */ #define IOHERESTR BIT(10) /* <<< (here string) */ #define IONDELIM BIT(11) /* null delimiter (<<) */ /* execute/exchild flags */ #define XEXEC BIT(0) /* execute without forking */ #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 X_EXTRA 20 /* this many extra bytes in X string */ 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 */ /* edit.c */ #ifndef MKSH_NO_CMDLINE_EDITING #ifndef MKSH_SMALL int x_bind(const char *, const char *, bool, bool); #else int x_bind(const char *, const char *, bool); #endif 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); #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(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 *); #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) 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 *, int); 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; #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 */ mksh/sh_flags.opt010064400000000000000000000114571305173120600112670ustar00/*- * Copyright (c) 2013, 2014, 2015, 2017 * mirabilos * * 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.5 2017/02/18 02:33:15 tg Exp $"); @SHFLAGS_ENUMS #define FN(sname,cname,flags,ochar) cname, #define F0(sname,cname,flags,ochar) cname = 0, @SHFLAGS_ITEMS #define FN(sname,cname,flags,ochar) ((const char *)(&shoptione_ ## cname)) + 2, @@ /* special cases */ 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", FUNICODE, 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 */ /* ./. direct builtin call (divined from argv[0] multi-call binary) */ >| FN("", FAS_BUILTIN, OF_INTERNAL /* ./. (internal) initial shell was interactive */ >| FN("", FTALKING_I, OF_INTERNAL |SHFLAGS_OPTCS mksh/shf.c010064400000000000000000000753461322653111300077070ustar00/* $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 * mirabilos * Copyright (c) 2015 * Daniel Richard G. * * 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.97 2018/01/14 01:28:16 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 (end && buf > orig_buf + 1 && buf[-2] == '\r') { buf--; bsize++; buf[-1] = '\n'; } #endif } while (!end && bsize); #ifdef MKSH_WITH_TEXTMODE if (!bsize && buf[-1] == '\r') { int c = shf_getc(shf); if (c == '\n') buf[-1] = '\n'; else if (c != -1) shf_ungetc(c, shf); } #endif *buf = '\0'; return (buf); } /* 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); } #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) 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 /* * 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 Unicode, 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 mksh/strlcpy.c010064400000000000000000000031431262663013000106130ustar00/*- * Copyright (c) 2006, 2008, 2009, 2013 * mirabilos * Copyright (c) 1998 Todd C. Miller * * 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)); } mksh/syn.c010064400000000000000000000642371322652134100077370ustar00/* $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 * mirabilos * * 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.127 2018/01/14 00:22:30 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; /* 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; while (/* CONSTCOND */ 1) { bool check_decl_utility; 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; } break; 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); break; 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); } else { /* * Check for "> foo (echo hi)" * which AT&T ksh allows (not * POSIX, but not disallowed) */ afree(t, ATEMP); 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); } goto Leave; default: goto Leave; } } Leave: 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; } mksh/tree.c010064400000000000000000000605561322651712400100710ustar00/* $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 * * 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.95 2018/01/14 00:03:05 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=<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("", 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 Unicode: 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, int 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 mksh/var.c010064400000000000000000001404151322651617600077210ustar00/* $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 * mirabilos * * 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 #endif __RCSID("$MirOS: src/bin/mksh/var.c,v 1.223 2018/01/13 23:55:15 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 *); 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 *); static int getint(struct tbl *, mksh_ari_u *, bool); static const char *array_index_calc(const char *, bool *, uint32_t *); /* * 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); } 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! */ cp = shf_smprintf(Tf_ss, 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; /* calculate the value of the subscript */ *arrayp = true; strndupx(tmp, p + 1, len - 2, ATEMP); sub = substitute(tmp, 0); afree(tmp, ATEMP); strndupx(n, n, p - n, ATEMP); evaluate(sub, &rval, KSH_UNWIND_ERROR, true); *valp = (uint32_t)rval; afree(sub, ATEMP); } 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 (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) { char *salloc = NULL; 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 */ if ((vq->flag&ALLOC)) { #ifndef MKSH_SMALL /* debugging */ if (s >= vq->val.s && s <= strnul(vq->val.s)) { internal_errorf( "setstr: %s=%s: assigning to self", vq->name, s); } #endif afree(vq->val.s, vq->areap); } vq->flag &= ~(ISSET|ALLOC); vq->type = 0; if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST))) s = salloc = formatstr(vq, s); if ((vq->flag&EXPORT)) exportprep(vq, s); else { strdupx(vq->val.s, s, vq->areap); vq->flag |= ALLOC; } } else { /* integer dest */ if (!v_evaluate(vq, s, error_ok, true)) return (0); } vq->flag |= ISSET; if ((vq->flag&SPECIAL)) setspec(vq); afree(salloc, ATEMP); 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, nlen + 1, "%-*.*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) { char *xp; char *op = (vp->flag&ALLOC) ? vp->val.s : NULL; size_t namelen, vallen; namelen = strlen(vp->name); vallen = strlen(val) + 1; vp->flag |= ALLOC; /* since name+val are both in memory this can go unchecked */ xp = alloc(namelen + 1 + vallen, vp->areap); memcpy(vp->val.s = xp, vp->name, namelen); xp += namelen; *xp++ = '='; /* offset to value */ vp->type = xp - vp->val.s; memcpy(xp, val, vallen); afree(op, vp->areap); } /* * lookup variable (according to (set&LOCAL)), set its attributes * (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, LCASEV, * UCASEV_AL), and optionally set its value if an assignment. */ struct tbl * typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) { struct tbl *vp; struct tbl *vpbase, *t; char *tvar; const char *val; size_t len; bool vappend = false; enum namerefflag new_refflag = SRF_NOP; 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) errorf(Tf_sD_s, var, "reference variable can't be an array"); len = array_ref_len(val); if (len == 0) return (NULL); /* * IMPORT is only used when the shell starts up and is * 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('=')) { strndupx(tvar, var, val - var, ATEMP); ++val; } else if (set & IMPORT) { /* environment invalid variable name or no assignment */ return (NULL); } else if (ord(val[0]) == ORD('+') && ord(val[1]) == ORD('=')) { strndupx(tvar, var, val - var, ATEMP); 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 */ strdupx(tvar, var, ATEMP); val = NULL; /* handle foo[*] => foo (whole array) mapping for R39b */ len = strlen(tvar); 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) errorf("appending not allowed for nameref"); /* 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: errorf(Tf_sD_s, var, "empty nameref target"); } 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 '[' */ errorf(Tf_sD_s, qval, "nameref target not a valid parameter name"); } nameref_rhs_checked: /* prevent nameref loops */ while (qval) { if (!strcmp(qval, tvar)) errorf(Tf_sD_s, qval, "expression recurses on parameter"); 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, "ENV") == 0 || strcmp(tvar, TSHELL) == 0)) errorf(Tf_sD_s, tvar, "restricted"); 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)))) /* XXX check calls - is error here ok by POSIX? */ errorfx(2, Tf_ro, tvar); 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) errorfz(); } if (val != NULL) { char *tval; if (vappend) { tval = shf_smprintf(Tf_ss, str_val(vp), val); val = tval; } else tval = NULL; if (vp->flag&INTEGER) { /* do not zero base before assignment */ setstr(vp, val, KSH_UNWIND_ERROR | 0x4); /* done after assignment to override default */ if (base > 0) vp->type = base; } else /* setstr can't fail (readonly check already done) */ setstr(vp, val, KSH_RETURN_ERROR | 0x4); afree(tval, ATEMP); } /* only x[0] is ever exported, so use vpbase */ if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) && vpbase->type == 0) exportprep(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null); 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); } /* * 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) { /* * 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); break; case V_PATH: afree(path, APERM); strdupx(path, def_path, APERM); /* clear tracked aliases */ flushcom(true); break; #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; } break; case V_LINENO: case V_RANDOM: case V_SECONDS: case V_TMOUT: /* AT&T ksh leaves previous value in place */ unspecial(vp->name); break; #ifdef MKSH_EARLY_LOCALE_TRACKING case V_LANG: case V_LC_ALL: case V_LC_CTYPE: recheck_ctype(); return; #endif } } /* * 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); } /* * 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, global(deprecated), export, and readonly */ int c_typeset(const char **wp) { struct tbl *vp, **p; uint32_t fset = 0, fclr = 0, flag; int thing = 0, field = 0, base = 0, i; struct block *l; const char *opts; const char *fieldstr = NULL, *basestr = NULL; bool localv = false, func = false, pflag = false, istset = true; enum namerefflag new_refflag = SRF_NOP; switch (**wp) { /* export */ case 'e': fset |= EXPORT; istset = false; break; /* readonly */ case 'r': fset |= RDONLY; istset = false; break; /* set */ case 's': /* called with 'typeset -' */ break; /* typeset */ case 't': localv = true; break; } /* see comment below regarding possible opions */ opts = istset ? "L#R#UZ#afgi#lnprtux" : "p"; builtin_opt.flags |= GF_PLUSOPT; /* * AT&T ksh seems to have 0-9 as options which are multiplied * to get a number that is used with -L, -R, -Z or -i (eg, -1R2 * sets right justify in a field of 12). This allows options * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and * does not allow the number to be specified as a separate argument * Here, the number must follow the RLZi option, but is optional * (see the # kludge in ksh_getopt()). */ while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) { flag = 0; switch (i) { case 'L': flag = LJUST; fieldstr = builtin_opt.optarg; break; case 'R': flag = RJUST; fieldstr = builtin_opt.optarg; break; case 'U': /* * AT&T ksh uses u, but this conflicts with * upper/lower case. If this option is changed, * need to change the -U below as well */ flag = INT_U; break; case 'Z': flag = ZEROFIL; fieldstr = builtin_opt.optarg; break; case 'a': /* * this is supposed to set (-a) or unset (+a) the * indexed array attribute; it does nothing on an * existing regular string or indexed array though */ break; case 'f': func = true; break; case 'g': localv = (builtin_opt.info & GI_PLUS) ? true : false; break; case 'i': flag = INTEGER; basestr = builtin_opt.optarg; break; case 'l': flag = LCASEV; break; case 'n': new_refflag = (builtin_opt.info & GI_PLUS) ? SRF_DISABLE : SRF_ENABLE; break; /* export, readonly: POSIX -p flag */ case 'p': /* typeset: show values as well */ pflag = true; if (istset) continue; break; case 'r': flag = RDONLY; break; case 't': flag = TRACE; break; case 'u': /* upper case / autoload */ flag = UCASEV_AL; break; case 'x': flag = EXPORT; break; case '?': return (1); } if (builtin_opt.info & GI_PLUS) { fclr |= flag; fset &= ~flag; thing = '+'; } else { fset |= flag; fclr &= ~flag; thing = '-'; } } if (fieldstr && !getn(fieldstr, &field)) { bi_errorf(Tf_sD_s, Tbadnum, fieldstr); return (1); } if (basestr) { if (!getn(basestr, &base)) { bi_errorf(Tf_sD_s, "bad integer base", basestr); return (1); } if (base < 1 || base > 36) base = 10; } if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] && (wp[builtin_opt.optind][0] == '-' || wp[builtin_opt.optind][0] == '+') && wp[builtin_opt.optind][1] == '\0') { thing = wp[builtin_opt.optind][0]; builtin_opt.optind++; } if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || new_refflag != SRF_NOP)) { bi_errorf("only -t, -u and -x options may be used with -f"); return (1); } if (wp[builtin_opt.optind]) { /* * Take care of exclusions. * At this point, flags in fset are cleared in fclr and vice * versa. This property should be preserved. */ if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ fset &= ~UCASEV_AL; if (fset & LJUST) /* LJUST has priority over RJUST */ fset &= ~RJUST; if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */ fset |= RJUST; fclr &= ~RJUST; } /* * Setting these attributes clears the others, unless they * are also set in this command */ if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | INTEGER | INT_U | INT_L)) || new_refflag != SRF_NOP) fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | INTEGER | INT_U | INT_L); } if (new_refflag != SRF_NOP) { fclr &= ~(ARRAY | ASSOC); fset &= ~(ARRAY | ASSOC); fclr |= EXPORT; fset |= ASSOC; if (new_refflag == SRF_DISABLE) fclr |= ASSOC; } /* set variables and attributes */ if (wp[builtin_opt.optind] && /* not "typeset -p varname" */ !(!func && pflag && !(fset | fclr))) { int rv = 0; struct tbl *f; if (localv && !func) fset |= LOCAL; for (i = builtin_opt.optind; wp[i]; i++) { if (func) { f = findfunc(wp[i], hash(wp[i]), tobool(fset & UCASEV_AL)); if (!f) { /* AT&T ksh does ++rv: bogus */ rv = 1; continue; } if (fset | fclr) { f->flag |= fset; f->flag &= ~fclr; } else { fpFUNCTf(shl_stdout, 0, tobool(f->flag & FKSH), wp[i], f->val.t); shf_putc('\n', shl_stdout); } } else if (!typeset(wp[i], fset, fclr, field, base)) { bi_errorf(Tf_sD_s, wp[i], Tnot_ident); return (1); } } return (rv); } /* list variables and attributes */ /* no difference at this point.. */ flag = fset | fclr; if (func) { for (l = e->loc; l; l = l->next) { for (p = ktsort(&l->funs); (vp = *p++); ) { if (flag && (vp->flag & flag) == 0) continue; if (thing == '-') fpFUNCTf(shl_stdout, 0, tobool(vp->flag & FKSH), vp->name, vp->val.t); else shf_puts(vp->name, shl_stdout); shf_putc('\n', shl_stdout); } } } else if (wp[builtin_opt.optind]) { for (i = builtin_opt.optind; wp[i]; i++) { vp = isglobal(wp[i], false); c_typeset_vardump(vp, flag, thing, last_lookup_was_array ? 4 : 0, pflag, istset); } } else c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset); return (0); } static void c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing, bool pflag, bool istset) { struct tbl **blockvars, *vp; if (l->next) c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset); blockvars = ktsort(&l->vars); while ((vp = *blockvars++)) c_typeset_vardump(vp, flag, thing, 0, pflag, istset); /*XXX doesn’t this leak? */ } static void c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, int any_set, bool pflag, bool istset) { struct tbl *tvp; char *s; if (!vp) return; /* * See if the parameter is set (for arrays, if any * element is set). */ for (tvp = vp; tvp; tvp = tvp->u.array) if (tvp->flag & ISSET) { any_set |= 1; break; } /* * Check attributes - note that all array elements * have (should have?) the same attributes, so checking * the first is sufficient. * * Report an unset param only if the user has * explicitly given it some attribute (like export); * otherwise, after "echo $FOO", we would report FOO... */ if (!any_set && !(vp->flag & USERATTRIB)) return; if (flag && (vp->flag & flag) == 0) return; if (!(vp->flag & ARRAY)) /* optimise later conditionals */ any_set = 0; do { /* * Ignore array elements that aren't set unless there * are no set elements, in which case the first is * reported on */ if (any_set && !(vp->flag & ISSET)) continue; /* no arguments */ if (!thing && !flag) { if (any_set == 1) { shprintf(Tf_s_s_sN, Tset, "-A", vp->name); any_set = 2; } /* * AT&T ksh prints things like export, integer, * leftadj, zerofill, etc., but POSIX says must * be suitable for re-entry... */ shprintf(Tf_s_s, Ttypeset, ""); if (((vp->flag & (ARRAY | ASSOC)) == ASSOC)) shprintf(Tf__c_, 'n'); if ((vp->flag & INTEGER)) shprintf(Tf__c_, 'i'); if ((vp->flag & EXPORT)) shprintf(Tf__c_, 'x'); if ((vp->flag & RDONLY)) shprintf(Tf__c_, 'r'); if ((vp->flag & TRACE)) shprintf(Tf__c_, 't'); if ((vp->flag & LJUST)) shprintf("-L%d ", vp->u2.field); if ((vp->flag & RJUST)) shprintf("-R%d ", vp->u2.field); if ((vp->flag & ZEROFIL)) shprintf(Tf__c_, 'Z'); if ((vp->flag & LCASEV)) shprintf(Tf__c_, 'l'); if ((vp->flag & UCASEV_AL)) shprintf(Tf__c_, 'u'); if ((vp->flag & INT_U)) shprintf(Tf__c_, 'U'); } else if (pflag) { shprintf(Tf_s_s, istset ? Ttypeset : (flag & EXPORT) ? Texport : Treadonly, ""); } if (any_set) shprintf("%s[%lu]", vp->name, arrayindex(vp)); else shf_puts(vp->name, shl_stdout); if ((!thing && !flag && pflag) || (thing == '-' && (vp->flag & ISSET))) { s = str_val(vp); shf_putc('=', shl_stdout); /* AT&T ksh can't have justified integers... */ if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER) shf_puts(s, shl_stdout); else print_value_quoted(shl_stdout, s); } shf_putc('\n', shl_stdout); /* * Only report first 'element' of an array with * no set elements. */ if (!any_set) return; } while (!(any_set & 4) && (vp = vp->u.array)); } mksh/var_spec.h010064400000000000000000000037021322647613500107350ustar00/*- * Copyright (c) 2009, 2011, 2012, 2016, 2018 * mirabilos * * 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