mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-24 23:14:14 +00:00
The fixargs() function is invoked when ksh needs to run a script without a #!/hashbang/path. Instead of letting the kernel invoke a shell, ksh exfile()s the script itself from sh_main(). In the forked child, it calls fixargs() to set the argument list in the environment to the args of the new script, so that 'ps' and /proc/PID/cmdline show the expected output. But fixargs() is broken because, on systems other than HP-UX (on which ksh uses pstat(2)), ksh simply inserts a terminating zero. The arguments list is not a zero-terminated C string. Unix systems expect the entire arguments buffer to be zeroed out, otherwise 'ps' and /proc/*/cmdline will have fragments of previous command lines in the output. The Red Hat patch for this bug is: https://src.fedoraproject.org/rpms/ksh/blob/642af4d6/f/ksh-20120801-argvfix.patch However, that fix is incomplete because 'command_len' was also hardcoded to be limited to 64 characters (!), which still gave invalid 'ps' output if the erased command line was longer. src/cmd/ksh93/sh/main.c: fixargs(): - Remove CMD_LENGTH macro which was defined as 64. - Remove code that limited the erasure of the arguments buffer to CMD_LENGTH characters. That code also had quite a dodgy strdup() call -- it copies arguments to the heap, but they are never freed (or even used), so it's a memory leak. Also, none of this is ever done if the length is calculated using pstat(2) on HP-UX, which is a clear indication that it's unnecessary. (I think this code block must have been some experiment they forgot to remove. One reason why I think so is that a 64 byte arguments limit never made sense, even in the 1980s when they wrote ksh on 80-column CRT displays. Another indication of this is that fixing it didn't require adding anything; the code to do the right thing was already there, it was just being overridden.) - Zero out the full arguments length as in the Red Hat patch. src/cmd/ksh93/tests/basic.sh: - Add test. It's sort of involved because 'ps' is one of the least portable commands in practice, in spite of standardisation.
720 lines
22 KiB
Bash
Executable file
720 lines
22 KiB
Bash
Executable file
########################################################################
|
|
# #
|
|
# This software is part of the ast package #
|
|
# Copyright (c) 1982-2012 AT&T Intellectual Property #
|
|
# and is licensed under the #
|
|
# Eclipse Public License, Version 1.0 #
|
|
# by AT&T Intellectual Property #
|
|
# #
|
|
# A copy of the License is available at #
|
|
# http://www.eclipse.org/org/documents/epl-v10.html #
|
|
# (with md5 checksum b35adb5213ca9657e911e9befb180842) #
|
|
# #
|
|
# Information and Software Systems Research #
|
|
# AT&T Research #
|
|
# Florham Park NJ #
|
|
# #
|
|
# David Korn <dgk@research.att.com> #
|
|
# #
|
|
########################################################################
|
|
function err_exit
|
|
{
|
|
print -u2 -n "\t"
|
|
print -u2 -r ${Command}[$1]: "${@:2}"
|
|
let Errors+=1
|
|
}
|
|
alias err_exit='err_exit $LINENO'
|
|
|
|
Command=${0##*/}
|
|
integer Errors=0
|
|
|
|
[[ -d $tmp && -w $tmp && $tmp == "$PWD" ]] || { err\_exit "$LINENO" '$tmp not set; run this from shtests. Aborting.'; exit 1; }
|
|
|
|
bincat=$(whence -p cat)
|
|
binecho=$(whence -p echo)
|
|
binfalse=$(whence -p false)
|
|
# make an external 'sleep' command that supports fractional seconds
|
|
binsleep=$tmp/.sleep.sh # hide to exclude from simple wildcard expansion
|
|
cat >"$binsleep" <<EOF
|
|
#!$SHELL
|
|
sleep "\$@"
|
|
EOF
|
|
chmod +x "$binsleep"
|
|
|
|
# test basic file operations like redirection, pipes, file expansion
|
|
set -- \
|
|
go+r 0000 \
|
|
go-r 0044 \
|
|
ug=r 0330 \
|
|
go+w 0000 \
|
|
go-w 0022 \
|
|
ug=w 0550 \
|
|
go+x 0000 \
|
|
go-x 0011 \
|
|
ug=x 0660 \
|
|
go-rx 0055 \
|
|
uo-wx 0303 \
|
|
ug-rw 0660 \
|
|
o= 0007
|
|
while (( $# >= 2 ))
|
|
do umask 0
|
|
umask $1
|
|
g=$(umask)
|
|
[[ $g == $2 ]] || err_exit "umask 0; umask $1 failed -- expected $2, got $g"
|
|
shift 2
|
|
done
|
|
umask u=rwx,go=rx || err_exit "umask u=rws,go=rx failed"
|
|
if [[ $(umask -S) != u=rwx,g=rx,o=rx ]]
|
|
then err_exit 'umask -S incorrect'
|
|
fi
|
|
um=$(umask -S)
|
|
( umask 0777; > foobar )
|
|
rm -f foobar
|
|
> foobar
|
|
[[ -r foobar ]] || err_exit 'umask not being restored after subshell'
|
|
umask "$um"
|
|
rm -f foobar
|
|
# optimizer bug test
|
|
> foobar
|
|
for i in 1 2
|
|
do print foobar*
|
|
rm -f foobar
|
|
done > out
|
|
if [[ "$(<out)" != "foobar"$'\n'"foobar*" ]]
|
|
then print -u2 "optimizer bug with file expansion"
|
|
fi
|
|
rm -f out foobar
|
|
mkdir dir
|
|
if [[ $(print */) != dir/ ]]
|
|
then err_exit 'file expansion with trailing / not working'
|
|
fi
|
|
if [[ $(print *) != dir ]]
|
|
then err_exit 'file expansion with single file not working'
|
|
fi
|
|
print hi > .foo
|
|
if [[ $(print *) != dir ]]
|
|
then err_exit 'file expansion leading . not working'
|
|
fi
|
|
date > dat1 || err_exit "date > dat1 failed"
|
|
test -r dat1 || err_exit "dat1 is not readable"
|
|
x=dat1
|
|
cat <$x > dat2 || err_exit "cat < $x > dat2 failed"
|
|
cat dat1 dat2 | cat | cat | cat > dat3 || err_exit "cat pipe failed"
|
|
cat > dat4 <<!
|
|
$(date)
|
|
!
|
|
cat dat1 dat2 | cat | cat | cat > dat5 &
|
|
wait $!
|
|
set -- dat*
|
|
if (( $# != 5 ))
|
|
then err_exit "dat* matches only $# files"
|
|
fi
|
|
if (command > foo\\abc) 2> /dev/null
|
|
then set -- foo*
|
|
if [[ $1 != 'foo\abc' ]]
|
|
then err_exit 'foo* does not match foo\abc'
|
|
fi
|
|
fi
|
|
if ( : > TT* && : > TTfoo ) 2>/dev/null
|
|
then set -- TT*
|
|
if (( $# < 2 ))
|
|
then err_exit 'TT* not expanding when file TT* exists'
|
|
fi
|
|
fi
|
|
cd ~- || err_exit "cd back failed"
|
|
cat > $tmp/script <<- !
|
|
#! $SHELL
|
|
print -r -- \$0
|
|
!
|
|
chmod 755 $tmp/script
|
|
if [[ $($tmp/script) != "$tmp/script" ]]
|
|
then err_exit '$0 not correct for #! script'
|
|
fi
|
|
bar=foo
|
|
eval foo=\$bar
|
|
if [[ $foo != foo ]]
|
|
then err_exit 'eval foo=\$bar not working'
|
|
fi
|
|
bar='foo=foo\ bar'
|
|
eval $bar
|
|
if [[ $foo != 'foo bar' ]]
|
|
then err_exit 'eval foo=\$bar, with bar="foo\ bar" not working'
|
|
fi
|
|
cd /tmp
|
|
cd ../../tmp || err_exit "cd ../../tmp failed"
|
|
if [[ $PWD != /tmp ]]
|
|
then err_exit 'cd ../../tmp is not /tmp'
|
|
fi
|
|
( sleep .2; cat <<!
|
|
foobar
|
|
!
|
|
) | cat > $tmp/foobar &
|
|
wait $!
|
|
foobar=$( < $tmp/foobar)
|
|
if [[ $foobar != foobar ]]
|
|
then err_exit "$foobar is not foobar"
|
|
fi
|
|
{
|
|
print foo
|
|
"$binecho" bar
|
|
print bam
|
|
} > $tmp/foobar
|
|
if [[ $( < $tmp/foobar) != $'foo\nbar\nbam' ]]
|
|
then err_exit "output file pointer not shared correctly"
|
|
fi
|
|
cat > $tmp/foobar <<\!
|
|
print foo
|
|
"$binecho" bar
|
|
print bam
|
|
!
|
|
chmod +x $tmp/foobar
|
|
if [[ $(export binecho; $tmp/foobar) != $'foo\nbar\nbam' ]]
|
|
then err_exit "script not working"
|
|
fi
|
|
if [[ $(export binecho; $tmp/foobar | "$bincat") != $'foo\nbar\nbam' ]]
|
|
then err_exit "script | cat not working"
|
|
fi
|
|
if [[ $(export binecho; $tmp/foobar) != $'foo\nbar\nbam' ]]
|
|
then err_exit "output file pointer not shared correctly"
|
|
fi
|
|
rm -f $tmp/foobar
|
|
x=$( (print foo) ; (print bar) )
|
|
if [[ $x != $'foo\nbar' ]]
|
|
then err_exit " ( (print foo);(print bar ) failed"
|
|
fi
|
|
x=$( ("$binecho" foo) ; (print bar) )
|
|
if [[ $x != $'foo\nbar' ]]
|
|
then err_exit " ( ("$binecho");(print bar ) failed"
|
|
fi
|
|
x=$( ("$binecho" foo) ; ("$binecho" bar) )
|
|
if [[ $x != $'foo\nbar' ]]
|
|
then err_exit " ( ("$binecho");("$binecho" bar ) failed"
|
|
fi
|
|
cat > $tmp/script <<\!
|
|
if [[ -p /dev/fd/0 ]]
|
|
then builtin cat
|
|
cat - > /dev/null
|
|
[[ -p /dev/fd/0 ]] && print ok
|
|
else print no
|
|
fi
|
|
!
|
|
chmod +x $tmp/script
|
|
case $( (print) | $tmp/script;:) in
|
|
ok) ;;
|
|
no) err_exit "[[ -p /dev/fd/0 ]] fails for standard input pipe" ;;
|
|
*) err_exit "builtin replaces standard input pipe" ;;
|
|
esac
|
|
print 'print $0' > $tmp/script
|
|
print ". $tmp/script" > $tmp/scriptx
|
|
chmod +x $tmp/scriptx
|
|
if [[ $($tmp/scriptx) != $tmp/scriptx ]]
|
|
then err_exit '$0 not correct for . script'
|
|
fi
|
|
cd $tmp || { err_exit "cd $tmp failed"; exit 1; }
|
|
print ./b > ./a; print ./c > b; print ./d > c; print ./e > d; print "echo \"hello there\"" > e
|
|
chmod 755 a b c d e
|
|
x=$(./a)
|
|
if [[ $x != "hello there" ]]
|
|
then err_exit "nested scripts failed"
|
|
fi
|
|
x=$( (./a) | cat)
|
|
if [[ $x != "hello there" ]]
|
|
then err_exit "scripts in subshells fail"
|
|
fi
|
|
cd ~- || err_exit "cd back failed"
|
|
x=$( ("$binecho" foo) 2> /dev/null )
|
|
if [[ $x != foo ]]
|
|
then err_exit "subshell in command substitution fails"
|
|
fi
|
|
exec 9>& 1
|
|
exec 1>&-
|
|
x=$(print hello)
|
|
if [[ $x != hello ]]
|
|
then err_exit "command substitution with stdout closed failed"
|
|
fi
|
|
exec >& 9
|
|
x=$(export binecho bincat; cat <<\! | $SHELL
|
|
"$binecho" | "$bincat"
|
|
"$binecho" hello
|
|
!
|
|
)
|
|
if [[ $x != $'\n'hello ]]
|
|
then err_exit "$SHELL not working when standard input is a pipe"
|
|
fi
|
|
x=$( ("$binecho" hello) 2> /dev/null )
|
|
if [[ $x != hello ]]
|
|
then err_exit "subshell in command substitution with 1 closed fails"
|
|
fi
|
|
cat > $tmp/script <<- \!
|
|
read line 2> /dev/null
|
|
print done
|
|
!
|
|
if [[ $($SHELL $tmp/script <&-) != done ]]
|
|
then err_exit "executing script with 0 closed fails"
|
|
fi
|
|
trap '' INT
|
|
cat > $tmp/script <<- \!
|
|
trap 'print bad' INT
|
|
kill -s INT $$
|
|
print good
|
|
!
|
|
chmod +x $tmp/script
|
|
if [[ $($SHELL $tmp/script) != good ]]
|
|
then err_exit "traps ignored by parent not ignored"
|
|
fi
|
|
trap - INT
|
|
cat > $tmp/script <<- \!
|
|
read line
|
|
"$bincat"
|
|
!
|
|
if [[ $(export bincat; $SHELL $tmp/script <<!
|
|
one
|
|
two
|
|
!
|
|
) != two ]]
|
|
then err_exit "standard input not positioned correctly"
|
|
fi
|
|
word=$(print $'foo\nbar' | { read line; "$bincat";})
|
|
if [[ $word != bar ]]
|
|
then err_exit "pipe to { read line; $bincat;} not working"
|
|
fi
|
|
word=$(print $'foo\nbar' | ( read line; "$bincat") )
|
|
if [[ $word != bar ]]
|
|
then err_exit "pipe to ( read line; $bincat) not working"
|
|
fi
|
|
if [[ $(print x{a,b}y) != 'xay xby' ]]
|
|
then err_exit 'brace expansion not working'
|
|
fi
|
|
if [[ $(for i in foo bar
|
|
do ( tgz=$(print $i)
|
|
print $tgz)
|
|
done) != $'foo\nbar' ]]
|
|
then err_exit 'for loop subshell optimizer bug'
|
|
fi
|
|
unset a1
|
|
function optbug
|
|
{
|
|
set -A a1 foo bar bam
|
|
integer i
|
|
for ((i=0; i < 3; i++))
|
|
do
|
|
(( ${#a1[@]} < 2 )) && return 0
|
|
set -- "${a1[@]}"
|
|
shift
|
|
set -A a1 -- "$@"
|
|
done
|
|
return 1
|
|
}
|
|
optbug || err_exit 'array size optimization bug'
|
|
wait # not running --pipefail which would interfere with subsequent tests
|
|
: $(jobs -p) # required to clear jobs for next jobs -p (interactive side effect)
|
|
sleep 2 &
|
|
pids=$!
|
|
if [[ $(jobs -p) != $! ]]
|
|
then err_exit 'jobs -p not reporting a background job'
|
|
fi
|
|
sleep 2 &
|
|
pids="$pids $!"
|
|
foo()
|
|
{
|
|
set -- $(jobs -p)
|
|
(( $# == 2 )) || err_exit "$# jobs not reported -- 2 expected"
|
|
}
|
|
foo
|
|
kill $pids
|
|
|
|
[[ $( (trap 'print alarm' ALRM; sleep .4) & sleep .2; kill -ALRM $!; sleep .2; wait) == alarm ]] || err_exit 'ALRM signal not working'
|
|
[[ $($SHELL -c 'trap "" HUP; $SHELL -c "(sleep .2;kill -HUP $$)& sleep .4;print done"') != done ]] && err_exit 'ignored traps not being ignored'
|
|
[[ $($SHELL -c 'o=foobar; for x in foo bar; do (o=save);print $o;done' 2> /dev/null ) == $'foobar\nfoobar' ]] || err_exit 'for loop optimization subshell bug'
|
|
command exec 3<> /dev/null
|
|
if cat /dev/fd/3 >/dev/null 2>&1 || whence mkfifo > /dev/null
|
|
then [[ $($SHELL -c 'cat <(print foo)' 2> /dev/null) == foo ]] || err_exit 'process substitution not working'
|
|
[[ $($SHELL -c $'tee >(grep \'1$\' > '$tmp/scriptx$') > /dev/null <<- \!!!
|
|
line0
|
|
line1
|
|
line2
|
|
!!!
|
|
wait
|
|
cat '$tmp/scriptx 2> /dev/null) == line1 ]] || err_exit '>() process substitution fails'
|
|
> $tmp/scriptx
|
|
[[ $($SHELL -c $'
|
|
for i in 1
|
|
do tee >(grep \'1$\' > '$tmp/scriptx$') > /dev/null <<- \!!!
|
|
line0
|
|
line1
|
|
line2
|
|
!!!
|
|
done
|
|
wait
|
|
cat '$tmp/scriptx 2>> /dev/null) == line1 ]] || err_exit '>() process substitution fails in for loop'
|
|
[[ $({ $SHELL -c 'cat <(for i in x y z; do print $i; done)';} 2> /dev/null) == $'x\ny\nz' ]] ||
|
|
err_exit 'process substitution of compound commands not working'
|
|
fi
|
|
[[ $($SHELL -r 'command -p :' 2>&1) == *restricted* ]] || err_exit 'command -p not restricted'
|
|
print cat > $tmp/scriptx
|
|
chmod +x $tmp/scriptx
|
|
[[ $($SHELL -c "print foo | $tmp/scriptx ;:" 2> /dev/null ) == foo ]] || err_exit 'piping into script fails'
|
|
[[ $($SHELL -c 'X=1;print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 1 ]] || err_exit 'x=1;${x:=$(..."...")} failure'
|
|
[[ $($SHELL -c 'print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 0 ]] || err_exit '${x:=$(..."...")} failure'
|
|
if cat /dev/fd/3 >/dev/null 2>&1 || whence mkfifo > /dev/null
|
|
then [[ $(cat <(print hello) ) == hello ]] || err_exit "process substitution not working outside for or while loop"
|
|
$SHELL -c '[[ $(for i in 1;do cat <(print hello);done ) == hello ]]' 2> /dev/null|| err_exit "process substitution not working in for or while loop"
|
|
fi
|
|
exec 3> /dev/null
|
|
print 'print foo "$@"' > $tmp/scriptx
|
|
[[ $( print "($tmp/scriptx bar)" | $SHELL 2>/dev/null) == 'foo bar' ]] || err_exit 'script pipe to shell fails'
|
|
print "#! $SHELL" > $tmp/scriptx
|
|
print 'print -- $0' >> $tmp/scriptx
|
|
chmod +x $tmp/scriptx
|
|
[[ $($tmp/scriptx) == $tmp/scriptx ]] || err_exit "\$0 is $0 instead of $tmp/scriptx"
|
|
cat > $tmp/scriptx <<- \EOF
|
|
myfilter() { x=$(print ok | cat); print -r -- $SECONDS;}
|
|
set -o pipefail
|
|
sleep .3 | myfilter
|
|
EOF
|
|
(( $($SHELL $tmp/scriptx) > .2 )) && err_exit 'command substitution causes pipefail option to hang'
|
|
exec 3<&-
|
|
( typeset -r foo=bar) 2> /dev/null || err_exit 'readonly variables set in a subshell cannot unset'
|
|
$SHELL -c 'x=${ print hello;}; [[ $x == hello ]]' 2> /dev/null || err_exit '${ command;} not supported'
|
|
$SHELL 2> /dev/null <<- \EOF || err_exit 'multiline ${...} command substitution not supported'
|
|
x=${
|
|
print hello
|
|
}
|
|
[[ $x == hello ]]
|
|
EOF
|
|
$SHELL 2> /dev/null <<- \EOF || err_exit '${...} command substitution with side effects not supported '
|
|
y=bye
|
|
x=${
|
|
y=hello
|
|
print hello
|
|
}
|
|
[[ $y == $x ]]
|
|
EOF
|
|
$SHELL 2> /dev/null <<- \EOF || err_exit 'nested ${...} command substitution not supported'
|
|
x=${
|
|
print ${ print hello;} $(print world)
|
|
}
|
|
[[ $x == 'hello world' ]]
|
|
EOF
|
|
$SHELL 2> /dev/null <<- \EOF || err_exit 'terminating } is not a reserved word with ${ command }'
|
|
x=${ { print -n } ; print -n hello ; } ; print ' world' }
|
|
[[ $x == '}hello world' ]]
|
|
EOF
|
|
$SHELL 2> /dev/null <<- \EOF || err_exit '${ command;}xxx not working'
|
|
f()
|
|
{
|
|
print foo
|
|
}
|
|
[[ ${ f;}bar == foobar ]]
|
|
EOF
|
|
|
|
unset foo
|
|
[[ ! ${foo[@]} ]] || err_exit '${foo[@]} is not empty when foo is unset'
|
|
[[ ! ${foo[3]} ]] || err_exit '${foo[3]} is not empty when foo is unset'
|
|
[[ $(print "[${ print foo }]") == '[foo]' ]] || err_exit '${...} not working when } is followed by ]'
|
|
[[ $(print "${ print "[${ print foo }]" }") == '[foo]' ]] || err_exit 'nested ${...} not working when } is followed by ]'
|
|
unset foo
|
|
foo=$(false) > /dev/null && err_exit 'failed command substitution with redirection not returning false'
|
|
expected=foreback
|
|
got=$(print -n fore; (sleep .2;print back)&)
|
|
[[ $got == $expected ]] || err_exit "command substitution background process output error -- got '$got', expected '$expected'"
|
|
|
|
for false in false $binfalse
|
|
do x=$($false) && err_exit "x=\$($false) should fail"
|
|
$($false) && err_exit "\$($false) should fail"
|
|
$($false) > /dev/null && err_exit "\$($false) > /dev/null should fail"
|
|
done
|
|
if env x-a=y >/dev/null 2>&1
|
|
then [[ $(env 'x-a=y' $SHELL -c 'env | grep x-a') == *x-a=y* ]] || err_exit 'invalid environment variables not preserved'
|
|
fi
|
|
|
|
foo() { return; }
|
|
false
|
|
foo && err_exit "'return' within function does not preserve exit status"
|
|
false
|
|
foo & wait "$!" && err_exit "'return' within function does not preserve exit status (bg job)"
|
|
|
|
foo() { false; return || :; }
|
|
foo && err_exit "'return ||' does not preserve exit status"
|
|
|
|
foo() { false; return && :; }
|
|
foo && err_exit "'return &&' does not preserve exit status"
|
|
|
|
foo() { false; while return; do true; done; }
|
|
foo && err_exit "'while return' does not preserve exit status"
|
|
|
|
foo() { false; while return; do true; done 2>&1; }
|
|
foo && err_exit "'while return' with redirection does not preserve exit status"
|
|
|
|
foo() { false; until return; do true; done; }
|
|
foo && err_exit "'until return' does not preserve exit status"
|
|
|
|
foo() { false; until return; do true; done 2>&1; }
|
|
foo && err_exit "'until return' with redirection does not preserve exit status"
|
|
|
|
foo() { false; for i in 1; do return; done; }
|
|
foo && err_exit "'return' within 'for' does not preserve exit status"
|
|
|
|
foo() { false; for i in 1; do return; done 2>&1; }
|
|
foo && err_exit "'return' within 'for' with redirection does not preserve exit status"
|
|
|
|
foo() { false; { return; } 2>&1; }
|
|
foo && err_exit "'return' within { block; } with redirection does not preserve exit status"
|
|
|
|
foo() ( exit )
|
|
false
|
|
foo && err_exit "'exit' within function does not preserve exit status"
|
|
false
|
|
foo & wait "$!" && err_exit "'exit' within function does not preserve exit status (bg job)"
|
|
|
|
foo() ( false; exit || : )
|
|
foo && err_exit "'exit ||' does not preserve exit status"
|
|
|
|
foo() ( false; exit && : )
|
|
foo && err_exit "'exit &&' does not preserve exit status"
|
|
|
|
foo() ( false; while exit; do true; done )
|
|
foo && err_exit "'while exit' does not preserve exit status"
|
|
|
|
foo() ( false; while exit; do true; done 2>&1 )
|
|
foo && err_exit "'while exit' with redirection does not preserve exit status"
|
|
|
|
foo() ( false; until exit; do true; done )
|
|
foo && err_exit "'until exit' does not preserve exit status"
|
|
|
|
foo() ( false; until exit; do true; done 2>&1 )
|
|
foo && err_exit "'until exit' with redirection does not preserve exit status"
|
|
|
|
foo() ( false; for i in 1; do exit; done )
|
|
foo && err_exit "'exit' within 'for' does not preserve exit status"
|
|
|
|
foo() ( false; for i in 1; do exit; done 2>&1 )
|
|
foo && err_exit "'exit' within 'for' with redirection does not preserve exit status"
|
|
|
|
foo() ( false; { exit; } 2>&1 )
|
|
foo && err_exit "'exit' within { block; } with redirection does not preserve exit status"
|
|
|
|
set --
|
|
false
|
|
for i in "$@"; do :; done || err_exit 'empty loop does not reset exit status ("$@")'
|
|
false
|
|
for i do :; done || err_exit 'empty loop does not reset exit status ("$@" shorthand)'
|
|
false
|
|
for i in; do :; done || err_exit "empty loop does not reset exit status (empty 'in' list)"
|
|
|
|
false
|
|
case 1 in 1) ;; esac || err_exit "empty case does not reset exit status"
|
|
false
|
|
case 1 in 2) echo whoa ;; esac || err_exit "non-matching case does not reset exit status"
|
|
|
|
false
|
|
2>&1 || err_exit "lone redirection does not reset exit status"
|
|
|
|
float s=SECONDS
|
|
for i in .1 .2
|
|
do print $i
|
|
done | while read sec; do ( "$binsleep" "$sec"; "$binsleep" "$sec") done
|
|
(( (SECONDS-s) < .4)) && err_exit '"command | while read...done" finishing too fast'
|
|
s=SECONDS
|
|
set -o pipefail
|
|
for ((i=0; i < 30; i++))
|
|
do print hello
|
|
sleep .01
|
|
done | "$binsleep" .1
|
|
(( (SECONDS-s) < .2 )) || err_exit 'early termination not causing broken pipe'
|
|
|
|
[[ $({ trap 'print trap' 0; print -n | $(whence -p cat); } & wait $!) == trap ]] || err_exit 'trap on exit not getting triggered'
|
|
var=$({ trap 'print trap' ERR; print -n | $binfalse; } & wait $!)
|
|
[[ $var == trap ]] || err_exit 'trap on ERR not getting triggered'
|
|
|
|
exp=
|
|
got=$(
|
|
function fun
|
|
{
|
|
$binfalse && echo FAILED
|
|
}
|
|
: works if this line deleted : |
|
|
fun
|
|
: works if this line deleted :
|
|
)
|
|
[[ $got == $exp ]] || err_exit "pipe to function with conditional fails -- expected '$exp', got '$got'"
|
|
got=$(
|
|
: works if this line deleted : |
|
|
{ $binfalse && echo FAILED; }
|
|
: works if this line deleted :
|
|
)
|
|
[[ $got == $exp ]] || err_exit "pipe to { ... } with conditional fails -- expected '$exp', got '$got'"
|
|
|
|
got=$(
|
|
: works if this line deleted : |
|
|
( $binfalse && echo FAILED )
|
|
: works if this line deleted :
|
|
)
|
|
[[ $got == $exp ]] || err_exit "pipe to ( ... ) with conditional fails -- expected '$exp', got '$got'"
|
|
|
|
( $SHELL -c 'trap : DEBUG; x=( $foo); exit 0') 2> /dev/null || err_exit 'trap DEBUG fails'
|
|
|
|
bintrue=$(whence -p true)
|
|
set -o pipefail
|
|
float start=$SECONDS end
|
|
for ((i=0; i < 2; i++))
|
|
do print foo
|
|
sleep .15
|
|
done | { read; $bintrue; end=$SECONDS ;}
|
|
(( (SECONDS-start) < .1 )) && err_exit "pipefail not waiting for pipe to finish"
|
|
set +o pipefail
|
|
(( (SECONDS-end) > .2 )) && err_exit "pipefail causing $bintrue to wait for other end of pipe"
|
|
|
|
|
|
{ env A__z=C+SHLVL $SHELL -c : ;} 2> /dev/null || err_exit "SHLVL with wrong attribute fails"
|
|
|
|
if [[ $bintrue ]]
|
|
then float t0=SECONDS
|
|
{ time sleep .15 | $bintrue ;} 2> /dev/null
|
|
(( (SECONDS-t0) < .1 )) && err_exit 'time not waiting for pipeline to complete'
|
|
fi
|
|
|
|
cat > $tmp/foo.sh <<- \EOF
|
|
eval "cat > /dev/null < /dev/null"
|
|
sleep .1
|
|
EOF
|
|
float sec=SECONDS
|
|
. $tmp/foo.sh | cat > /dev/null
|
|
(( (SECONDS-sec) < .07 )) && err_exit '. script does not restore output redirection with eval'
|
|
|
|
file=$tmp/foobar
|
|
builtin cat
|
|
for ((n=0; n < 1000; n++))
|
|
do
|
|
> $file
|
|
{ sleep .001;echo $? >$file;} | cat > /dev/null
|
|
if [[ ! -s $file ]]
|
|
then err_exit 'output from pipe is lost with pipe to builtin'
|
|
break;
|
|
fi
|
|
done
|
|
|
|
$SHELL -c 'kill -0 123456789123456789123456789' 2> /dev/null && err_exit 'kill not catching process id overflows'
|
|
|
|
[[ $($SHELL -c '{ cd..; print ok;}' 2> /dev/null) == ok ]] || err_exit 'command name ending in .. causes shell to abort'
|
|
|
|
$SHELL -xc '$(LD_LIBRARY_PATH=$LD_LIBRARY_PATH exec $SHELL -c :)' > /dev/null 2>&1 || err_exit "ksh -xc '(name=value exec ksh)' fails with err=$?"
|
|
|
|
$SHELL 2> /dev/null -c $'for i;\ndo :;done' || err_exit 'for i ; <newline> not vaid'
|
|
|
|
# ======
|
|
# Crash on syntax error when dotting/sourcing multiple files
|
|
# Ref.: https://www.mail-archive.com/ast-developers@lists.research.att.com/msg01943.html
|
|
(
|
|
mkdir "$tmp/dotcrash" || exit
|
|
cd "$tmp/dotcrash" || exit
|
|
cat >functions.ksh <<-EOF
|
|
function f1
|
|
{
|
|
echo "f1"
|
|
}
|
|
function f2
|
|
{
|
|
if [[ $1 -eq 1 ]]: # deliberate syntax error
|
|
then echo "f2"
|
|
fi
|
|
}
|
|
EOF
|
|
cat >sub1.ksh <<-EOF
|
|
. ./functions.ksh
|
|
echo "sub1" >tmp.out
|
|
EOF
|
|
cat >main.ksh <<-EOF
|
|
. ./sub1.ksh
|
|
EOF
|
|
"$SHELL" main.ksh 2>/dev/null
|
|
) || err_exit "crash when sourcing multiple files (exit status $?)"
|
|
|
|
# ======
|
|
# The time keyword
|
|
|
|
# A single '%' after a format specifier should not be a syntax
|
|
# error (it should be treated as a literal '%').
|
|
expect='0%'
|
|
actual=$(
|
|
TIMEFORMAT=$'%0S%'
|
|
redirect 2>&1
|
|
set +x
|
|
time :
|
|
)
|
|
[[ $actual == "$expect" ]] || err_exit "'%' is not treated literally when placed after a format specifier" \
|
|
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
|
|
|
|
# The locale's radix point shouldn't be ignored
|
|
us=$(
|
|
LC_ALL='C.UTF-8' # radix point '.'
|
|
TIMEFORMAT='%1U' # catch -1.99 bug as well by getting user time
|
|
redirect 2>&1
|
|
set +x
|
|
time sleep 0
|
|
)
|
|
eu=$(
|
|
LC_ALL='C_EU.UTF-8' # radix point ','
|
|
TIMEFORMAT='%1U'
|
|
redirect 2>&1
|
|
set +x
|
|
time sleep 0
|
|
)
|
|
[[ ${us:1:1} == ${eu:1:1} ]] && err_exit "The time keyword ignores the locale's radix point (both are ${eu:1:1})"
|
|
|
|
# ======
|
|
# Test for bug in ksh binaries that use posix_spawn() while job control is active.
|
|
# See discussion at: https://github.com/ksh93/ksh/issues/79
|
|
if test -t 1 2>/dev/null 1>/dev/tty # this test only works if we have a tty
|
|
then actual=$(
|
|
"$SHELL" -i <<-\EOF 2>/dev/tty
|
|
printf '%s\n' 1 2 3 4 5 | while read
|
|
do ls /dev/null
|
|
done 2>&1
|
|
exit # suppress extra newline
|
|
EOF
|
|
)
|
|
expect=$'/dev/null\n/dev/null\n/dev/null\n/dev/null\n/dev/null'
|
|
[[ $actual == "$expect" ]] || err_exit 'Race condition while launching external commands' \
|
|
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
|
|
fi
|
|
|
|
# ======
|
|
# Expansion of multibyte characters after expansion of single-character names $1..$9, $?, $!, $-, etc.
|
|
function exptest
|
|
{
|
|
print -r "$1テスト"
|
|
print -r "$?テスト"
|
|
print -r "$#テスト"
|
|
}
|
|
expect=$'fooテスト\n0テスト\n1テスト'
|
|
actual=$(exptest foo)
|
|
[[ $actual == "$expect" ]] || err_exit 'Corruption of multibyte char following expansion of single-char name' \
|
|
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
|
|
|
|
# ======
|
|
# ksh didn't rewrite argv correctly (rhbz#1047506)
|
|
# When running a script without a #! hashbang path, ksh attempts to replace argv with the arguments
|
|
# of the script. However, fixargs() didn't wipe out the rest of previous arguments after the last
|
|
# \0. This caused an erroneous record in /proc/<PID>/cmdline and the output of the ps command.
|
|
if actual=$(UNIX95=1 ps -o args= -p "$$" 2>&1) # UNIX95=1 makes this work on HP-UX
|
|
# Some 'ps' implementations add leading and/or trailing whitespace. Remove.
|
|
while [[ $actual == [[:space:]]* ]]; do actual=${actual#?}; done
|
|
while [[ $actual == *[[:space:]] ]]; do actual=${actual%?}; done
|
|
[[ $actual == "$SHELL $0" ]] # this is how shtests invokes this script
|
|
then expect='./atest 1 2'
|
|
echo 'sleep 10; exit 0' >atest
|
|
chmod 755 atest
|
|
./atest 1 2 &
|
|
actual=$(UNIX95=1 ps -o args= -p "$!")
|
|
kill "$!"
|
|
while [[ $actual == [[:space:]]* ]]; do actual=${actual#?}; done
|
|
while [[ $actual == *[[:space:]] ]]; do actual=${actual%?}; done
|
|
[[ $actual == "$expect" ]] || err_exit "ksh didn't rewrite argv correctly" \
|
|
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
|
|
else err_exit "warning: skipping argv rewrite test due to noncompliant 'ps' utility (got $(printf %q "$actual"))"
|
|
let Errors--
|
|
fi
|
|
|
|
# ======
|
|
exit $((Errors<125?Errors:125))
|