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