1# 2# CDDL HEADER START 3# 4# The contents of this file are subject to the terms of the 5# Common Development and Distribution License (the "License"). 6# You may not use this file except in compliance with the License. 7# 8# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9# or http://www.opensolaris.org/os/licensing. 10# See the License for the specific language governing permissions 11# and limitations under the License. 12# 13# When distributing Covered Code, include this CDDL HEADER in each 14# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15# If applicable, add the following below this CDDL HEADER, with the 16# fields enclosed by brackets "[]" replaced with your own identifying 17# information: Portions Copyright [yyyy] [name of copyright owner] 18# 19# CDDL HEADER END 20# 21 22# 23# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 24# 25 26# 27# Written by Roland Mainz <roland.mainz@nrubsig.org> 28# 29 30# test setup 31function err_exit 32{ 33 print -u2 -n "\t" 34 print -u2 -r ${Command}[$1]: "${@:2}" 35 (( Errors < 127 && Errors++ )) 36} 37alias err_exit='err_exit $LINENO' 38 39set -o nounset 40Command=${0##*/} 41integer Errors=0 42 43 44function isvalidpid 45{ 46 kill -0 ${1} 2>/dev/null && return 0 47 return 1 48} 49integer testfilesize i maxwait 50typeset tmpfile 51integer testid 52 53 54######################################################################## 55#### test set 001: 56# run loop and check various temp filesizes 57# (Please keep this test syncted with sun_solaris_cr_6800929_large_command_substitution_hang.sh) 58 59# test 1: run loop and check various temp filesizes 60tmpfile="$(mktemp -t "ksh93_tests_command_substitution.${PPID}.$$.XXXXXX")" || err_exit "Cannot create temporary file." 61 62compound test1=( 63 compound -a testcases=( 64 # test 1a: Run test child for $(...) 65 # (note the pipe chain has to end in a builtin command, an external command may not trigger the bug) 66 ( name="test1a" cmd="builtin cat ; print -- \"\$(cat \"${tmpfile}\" | cat)\" ; true" ) 67 # test 1b: Same as test1a but uses ${... ; } instead if $(...) 68 ( name="test1b" cmd="builtin cat ; print -- \"\${ cat \"${tmpfile}\" | cat ; }\" ; true" ) 69 # test 1c: Same as test1a but does not use a pipe 70 ( name="test1c" cmd="builtin cat ; print -- \"\$(cat \"${tmpfile}\" ; true)\" ; true" ) 71 # test 1d: Same as test1a but does not use a pipe 72 ( name="test1d" cmd="builtin cat ; print -- \"\${ cat \"${tmpfile}\" ; true ; }\" ; true" ) 73 74 # test 1e: Same as test1a but uses an external "cat" command 75 ( name="test1e" cmd="builtin -d cat /bin/cat ; print -- \"\$(cat \"${tmpfile}\" | cat)\" ; true" ) 76 # test 1f: Same as test1a but uses an external "cat" command 77 ( name="test1f" cmd="builtin -d cat /bin/cat ; print -- \"\${ cat \"${tmpfile}\" | cat ; }\" ; true" ) 78 # test 1g: Same as test1a but uses an external "cat" command 79 ( name="test1g" cmd="builtin -d cat /bin/cat ; print -- \"\$(cat \"${tmpfile}\" ; true)\" ; true" ) 80 # test 1h: Same as test1a but uses an external "cat" command 81 ( name="test1h" cmd="builtin -d cat /bin/cat ; print -- \"\${ cat \"${tmpfile}\" ; true ; }\" ; true" ) 82 ) 83) 84 85for (( testfilesize=1*1024 ; testfilesize <= 1024*1024 ; testfilesize*=2 )) ; do 86 # Create temp file 87 { 88 for (( i=0 ; i < testfilesize ; i+=64 )) ; do 89 print "0123456789abcdef01234567890ABCDEF0123456789abcdef01234567890ABCDE" 90 done 91 } >"${tmpfile}" 92 93 # wait up to log2(i) seconds for the child to terminate 94 # (this is 10 seconds for 1KB and 19 seconds for 512KB) 95 (( maxwait=log2(testfilesize) )) 96 97 for testid in "${!test1.testcases[@]}" ; do 98 nameref currtst=test1.testcases[testid] 99 ${SHELL} -o errexit -c "${currtst.cmd}" >"${tmpfile}.out" & 100 (( childpid=$! )) 101 102 for (( i=0 ; i < maxwait ; i++ )) ; do 103 isvalidpid ${childpid} || break 104 sleep 0.25 105 done 106 107 if isvalidpid ${childpid} ; then 108 err_exit "${currtst.name}: child (pid=${childpid}) still busy, filesize=${testfilesize}." 109 kill -KILL ${childpid} 2>/dev/null 110 fi 111 wait || err_exit "${currtst.name}: Child returned non-zero exit code." # wait for child (and/or avoid zombies/slime) 112 113 # compare input/output 114 cmp -s "${tmpfile}" "${tmpfile}.out" || err_exit "${currtst.name}: ${tmpfile} and ${tmpfile}.out differ, filesize=${testfilesize}." 115 rm "${tmpfile}.out" 116 done 117 118 # Cleanup 119 rm "${tmpfile}" 120done 121 122 123######################################################################## 124#### test set 002: 125# If a command substitution calls a function and that function contains 126# a command substitution which contains a piped command, the original 127# command substitution calling the function will return 127 instead of 0. 128# This is causing problems in several VSC tests. 129# If we remove the piped command from the simple 130# case in the attached script, it returns 0. 131 132typeset str 133typeset testbody 134typeset testout 135 136testbody=$( 137# <CS> means command substitution start, <CE> means command substitution end 138cat <<EOF 139myfunc () 140{ 141 pipedcmd=<CS> printf "hi" | tr "h" "H" <CE> 142 echo \$pipedcmd 143 144 return 0 145} 146 147foo=<CS>myfunc<CE> 148retval=\$? 149 150if [ "\$foo"X != "HiX" ]; then 151 echo "myfunc returned '\${foo}'; expected 'Hi'" 152fi 153 154if [ \$retval -ne 0 ]; then 155 echo "command substitution calling myfunc returned \"\${retval}\"; expected 0" 156else 157 echo "command substitution calling myfunc successfully returned 0" 158fi 159EOF 160) 161 162 163# Test 002/a: Plain test 164testout=${ printf "%B\n" testbody | sed 's/<CS>/$(/g;s/<CE>/)/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 165[[ "${testout}" == "command substitution calling myfunc successfully returned 0" ]] || err_exit "Expected 'command substitution calling myfunc successfully returned 0', got ${testout}" 166 167# Test 002/b: Same as test002/a but replaces "$(" with "${" 168testout=${ printf "%B\n" testbody | sed 's/<CS>/${ /g;s/<CE>/ ; }/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 169[[ "${testout}" == "command substitution calling myfunc successfully returned 0" ]] || err_exit "Expected 'command substitution calling myfunc successfully returned 0', got ${testout}" 170 171# Test 002/c: Same as test002/a but forces |fork()| for a subshell via "ulimit -c 0" 172testout=${ printf "%B\n" testbody | sed 's/<CS>/$( ulimit -c 0 ; /g;s/<CE>/)/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 173[[ "${testout}" == "command substitution calling myfunc successfully returned 0" ]] || err_exit "Expected 'command substitution calling myfunc successfully returned 0', got ${testout}" 174 175# Test 002/d: Same as test002/a but uses extra subshell 176testout=${ printf "%B\n" testbody | sed 's/<CS>/$( ( /g;s/<CE>/) )/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 177[[ "${testout}" == "command substitution calling myfunc successfully returned 0" ]] || err_exit "Expected 'command substitution calling myfunc successfully returned 0', got ${testout}" 178 179# Test 002/e: Same as test002/b but uses extra subshell after "${ " 180testout=${ printf "%B\n" testbody | sed 's/<CS>/${ ( /g;s/<CE>/) ; }/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 181[[ "${testout}" == "command substitution calling myfunc successfully returned 0" ]] || err_exit "Expected 'command substitution calling myfunc successfully returned 0', got ${testout}" 182 183 184 185 186######################################################################## 187#### test set 003: 188# An expression within backticks which should return false, instead 189# returns true (0). 190 191typeset str 192typeset testbody 193typeset testout 194 195testbody=$( 196# <CS> means command substitution start, <CE> means command substitution end 197cat <<EOF 198if <CS>expr "NOMATCH" : ".*Z" > /dev/null<CE> ; then 199 echo "xerror" 200else 201 echo "xok" 202fi 203EOF 204) 205 206 207# Test 003/a: Plain test 208testout=${ printf "%B\n" testbody | sed 's/<CS>/$(/g;s/<CE>/)/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 209[[ "${testout}" == "xok" ]] || err_exit "Expected 'xok', got ${testout}" 210 211# Test 003/b: Same as test003/a but replaces "$(" with "${" 212testout=${ printf "%B\n" testbody | sed 's/<CS>/${ /g;s/<CE>/ ; }/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 213[[ "${testout}" == "xok" ]] || err_exit "Expected 'xok', got ${testout}" 214 215# Test 003/c: Same as test003/a but forces |fork()| for a subshell via "ulimit -c 0" 216testout=${ printf "%B\n" testbody | sed 's/<CS>/$( ulimit -c 0 ; /g;s/<CE>/)/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 217[[ "${testout}" == "xok" ]] || err_exit "Expected 'xok', got ${testout}" 218 219# Test 003/d: Same as test003/a but uses extra subshell 220testout=${ printf "%B\n" testbody | sed 's/<CS>/$( ( /g;s/<CE>/) )/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 221[[ "${testout}" == "xok" ]] || err_exit "Expected 'xok', got ${testout}" 222 223# Test 003/e: Same as test003/b but uses extra subshell after "${ " 224testout=${ printf "%B\n" testbody | sed 's/<CS>/${ ( /g;s/<CE>/) ; }/g' | ${SHELL} 2>&1 || err_exit "command returned exit code $?" } 225[[ "${testout}" == "xok" ]] || err_exit "Expected 'xok', got ${testout}" 226 227 228######################################################################## 229#### test set 004: 230# test pipe within ${... ; } command subtitution ending in a 231# non-builtin command (therefore we use "/bin/cat" instead of "cat" below 232# to force the use of the external "cat" command). ast-ksh.2009-01-20 233# had a bug which caused this test to fail. 234testout=$( ${SHELL} -c 'pipedcmd=${ printf "hi" | /bin/cat ; } ; print $pipedcmd' ) 235[[ "${testout}" == "hi" ]] || err_exit "test004: Expected 'hi', got '${testout}'" 236 237 238######################################################################## 239#### test set 005: 240# Test whether the shell may hang in a 241# 'exec 5>/dev/null; print $(eval ls -d . 2>&1 1>&5)' 242# Originally discovered with ast-ksh.2009-05-05 which hung in 243# the "configure" script of postgresql-8.3.7.tar.gz (e.g. 244# configure --enable-thread-safety --without-readline) 245compound test5=( 246 compound -a testcases=( 247 # gsf's reduced testcase 248 ( name="test5_a" cmd='exec 5>/dev/null; print $(eval ls -d . 2>&1 1>&5)done' ) 249 # gisburn's reduced testcase 250 ( name="test5_b" cmd='exec 5>/dev/null; print $(eval "/bin/printf hello\n" 2>&1 1>&5)done' ) 251 252 ## The following tests do not trigger the problem but are included here for completeness 253 ## and to make sure we don't get other incarnations of the same problem later... 254 255 # same as test5_a but uses ${ ... ; } instead of $(...) 256 ( name="test5_c" cmd='exec 5>/dev/null; print "${ eval ls -d . 2>&1 1>&5 ;}done"' ) 257 # same as test5_b but uses ${ ... ; } instead of $(...) 258 ( name="test5_d" cmd='exec 5>/dev/null; print "${ eval "/bin/printf hello\n" 2>&1 1>&5 ;}done"' ) 259 # same as test5_a but uses "ulimit -c 0" to force the shell to use a seperare process for $(...) 260 ( name="test5_e" cmd='exec 5>/dev/null; print $(ulimit -c 0 ; eval ls -d . 2>&1 1>&5)done' ) 261 # same as test5_b but uses "ulimit -c 0" to force the shell to use a seperare process for $(...) 262 ( name="test5_f" cmd='exec 5>/dev/null; print $(ulimit -c 0 ; eval "/bin/printf hello\n" 2>&1 1>&5)done' ) 263 ) 264) 265 266maxwait=5 267for testid in "${!test5.testcases[@]}" ; do 268 nameref currtst=test5.testcases[testid] 269 ${SHELL} -o errexit -c "${currtst.cmd}" >"${tmpfile}.out" & 270 (( childpid=$! )) 271 272 for (( i=0 ; i < maxwait ; i++ )) ; do 273 isvalidpid ${childpid} || break 274 sleep 0.25 275 done 276 277 if isvalidpid ${childpid} ; then 278 err_exit "${currtst.name}: child (pid=${childpid}) still busy." 279 kill -KILL ${childpid} 2>/dev/null 280 fi 281 wait || err_exit "${currtst.name}: Child returned non-zero exit code." # wait for child (and/or avoid zombies/slime) 282 283 testout="$( < "${tmpfile}.out")" 284 rm "${tmpfile}.out" || err_exit "File '${tmpfile}.out' could not be removed." 285 [[ "${testout}" == "done" ]] || err_exit "test '${currtst.name}' failed, expected 'done', got '${testout}'" 286done 287 288 289# tests done 290exit $((Errors)) 291