1#!/usr/bin/perl 2# Test 'env -S' feature 3 4# Copyright (C) 2018-2023 Free Software Foundation, Inc. 5 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <https://www.gnu.org/licenses/>. 18 19use strict; 20 21(my $program_name = $0) =~ s|.*/||; 22my $prog = 'env'; 23 24my $env = "$ENV{abs_top_builddir}/src/env"; 25# Ensure no whitespace or other problematic chars in path 26$env =~ m!^([-+\@\w./]+)$! 27 or CuSkip::skip "unusual absolute builddir name; skipping this test\n"; 28$env = $1; 29 30# Turn off localization of executable's output. 31@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3; 32 33# This envvar is somehow set at least on macOS 11.6, and would 34# otherwise cause failure of q*, t* and more tests below. Ignore it. 35my $cf = '__CF_USER_TEXT_ENCODING'; 36exists $ENV{$cf} and $env .= " -u$cf"; 37# Likewise for these Cygwin env vars 38my $cf = 'SYSTEMROOT'; 39exists $ENV{$cf} and $env .= " -u$cf"; 40my $cf = 'WINDIR'; 41exists $ENV{$cf} and $env .= " -u$cf"; 42 43my @Tests = 44 ( 45 # Test combination of -S and regular arguments 46 ['1', q[-ufoo A=B FOO=AR sh -c 'echo $A$FOO'], {OUT=>"BAR"}], 47 ['2', q[-ufoo -S'A=B FOO=AR sh -c "echo \\$A\\$FOO"'], {OUT=>"BAR"}], 48 ['3', q[-ufoo -S'A=B FOO=AR' sh -c 'echo $A$FOO'], {OUT=>"BAR"}], 49 ['4', q[-ufoo -S'A=B' FOO=AR sh -c 'echo $A$FOO'], {OUT=>"BAR"}], 50 ['5', q[-S'-ufoo A=B FOO=AR sh -c "echo \\$A\\$FOO"'], {OUT=>"BAR"}], 51 52 # Test quoting inside -S 53 ['q1', q[-S'-i A="B C" ]."$env'", {OUT=>"A=B C"}], 54 ['q2', q[-S"-i A='B C' ]."$env\"", {OUT=>"A=B C"}], 55 ['q3', q[-S"-i A=\"B C\" ]."$env\"", {OUT=>"A=B C"}], 56 # Test backslash-quoting inside quoting inside -S 57 ['q4', q[-S'-i A="B \" C" ]."$env'", {OUT=>'A=B " C'}], 58 ['q5', q[-S"-i A='B \\' C' ]."$env\"", {OUT=>"A=B ' C"}], 59 # Single-quotes in double-quotes and vice-versa 60 ['q6', q[-S'-i A="B'"'"'C" ]."$env'", {OUT=>"A=B'C"}], 61 ['q7', q[-S"-i A='B\\"C' ]."$env\"", {OUT=>'A=B"C'}], 62 63 # Test tab and space (note: tab here is expanded by perl 64 # and sent to the shell as ASCII 0x9 inside single-quotes). 65 ['t1', qq[-S'-i\tA="B \tC" $env'], {OUT=>"A=B \tC"}], 66 # Here '\\t' is not interpolated by perl/shell, passed as two characters 67 # (backslash, 't') to env, resulting in one argument ("A<tab>B"). 68 ['t2', qq[-S'printf x%sx\\n A\\tB'], {OUT=>"xA\tBx"}], 69 # Here '\t' is interpolated by perl, passed as literal tab (ASCII 0x9) 70 # to env, resulting in two arguments ("A" <whitespace> "B"). 71 ['t3', qq[-S'printf x%sx\\n A\tB'], {OUT=>"xAx\nxBx"}], 72 ['t4', qq[-S'printf x%sx\\n A \t B'], {OUT=>"xAx\nxBx"}], 73 # Ensure \v\f\r\n treated like other whitespace. 74 # From 8.30 - 8.32 these would introduce arguments to printf, 75 # and also crash ASAN builds with out of bounds access. 76 ['t5', qq[-S'printf x%sx\\n A \t B \013\f\r\n'], {OUT=>"xAx\nxBx"}], 77 78 79 # Test empty strings 80 ['m1', qq[-i -S"" A=B $env], {OUT=>"A=B"}], 81 ['m2', qq[-i -S" \t" A=B $env], {OUT=>"A=B"}], 82 83 # Test escape sequences. 84 # note: in the following, there is no interpolation by perl due 85 # to q[], and no interpolation by the shell due to single-quotes. 86 # env will receive the backslash character followed by t/f/r/n/v. 87 # Also: Perl does not recognize "\v", so use "\013" for vertical tab. 88 ['e1', q[-i -S'A="B\tC" ]."$env'", {OUT=>"A=B\tC"}], 89 ['e2', q[-i -S'A="B\fC" ]."$env'", {OUT=>"A=B\fC"}], 90 ['e3', q[-i -S'A="B\rC" ]."$env'", {OUT=>"A=B\rC"}], 91 ['e4', q[-i -S'A="B\nC" ]."$env'", {OUT=>"A=B\nC"}], 92 ['e5', q[-i -S'A="B\vC" ]."$env'", {OUT=>"A=B\013C"}], 93 ['e6', q[-i -S'A="B\$C" ]."$env'", {OUT=>'A=B$C'}], 94 ['e7', q[-i -S'A=B\$C ]."$env'", {OUT=>'A=B$C'}], 95 ['e8', q[-i -S'A="B\#C" ]."$env'", {OUT=>'A=B#C'}], 96 ['e9', q[-i -S'A="B\\\\C" ]."$env'", {OUT=>'A=B\\C'}], 97 ['e10',q[-i -S"A='B\\\\\\\\C' ]."$env\"", {OUT=>'A=B\\C'}], 98 99 # Escape in single-quoted string - passed as-is 100 # (the multiple pairs of backslashes are to survive two interpolations: 101 # by perl and then by the shell due to double-quotes). 102 ['e11',q[-i -S"A='B\\\\tC' ]."$env\"", {OUT=>'A=B\tC'}], 103 ['e12',q[-i -S"A='B\\\\#C' ]."$env\"", {OUT=>'A=B\#C'}], 104 ['e13',q[-i -S"A='B\\\\\\$C' ]."$env\"", {OUT=>'A=B\$C'}], 105 ['e14',q[-i -S"A='B\\\\\\"C' ]."$env\"", {OUT=>'A=B\"C'}], 106 107 # Special escape sequences: 108 # \_ in double-quotes is a space - result is just one envvar 'A' 109 ['e20', q[-i -S'A="B\_C=D" ]."$env'", {OUT=>'A=B C=D'}], 110 # \_ outside double-quotes is arg separator, the command to 111 # execute should be 'env env' 112 ['e21', q[-i -S'A=B]."\\_$env\\_$env'", {OUT=>"A=B"}], 113 114 # Test -C inside -S 115 ['c1', q["-S-C/ pwd"], {OUT=>"/"}], 116 ['c2', q["-S -C / pwd"], {OUT=>"/"}], 117 ['c3', q["-S --ch'dir='/ pwd"], {OUT=>"/"}], 118 119 # Test -u inside and outside -S 120 # u1,u2 - establish a baseline, without -S 121 ['u1', q[ sh -c 'echo =$FOO='], {ENV=>"FOO=BAR"}, {OUT=>"=BAR="}], 122 ['u2', q[-uFOO sh -c 'echo =$FOO='], {ENV=>"FOO=BAR"}, {OUT=>"=="}], 123 # u3,u4: ${FOO} expanded by env itself before executing sh. 124 # \\$FOO expanded by sh. 125 # ${FOO} should have value of the original environment 126 # and \\$FOO should be unset, regardless where -uFOO is used. 127 # 'u3' behavior differs from FreeBSD's but deemed preferable, in 128 # https://lists.gnu.org/r/coreutils/2018-04/msg00014.html 129 ['u3', q[-uFOO -S'sh -c "echo x${FOO}x =\\$FOO="'], 130 {ENV=>"FOO=BAR"}, {OUT=>"xBARx =="}], 131 ['u4', q[-S'-uFOO sh -c "echo x${FOO}x =\\$FOO="'], 132 {ENV=>"FOO=BAR"}, {OUT=>"xBARx =="}], 133 134 # Test ENVVAR expansion 135 ['v1', q[-i -S'A=${FOO} ]."$env'", {ENV=>"FOO=BAR"}, {OUT=>"A=BAR"}], 136 ['v2', q[-i -S'A=x${FOO}x ]."$env'", {ENV=>"FOO=BAR"}, {OUT=>"A=xBARx"}], 137 ['v3', q[-i -S'A=x${FOO}x ]."$env'", {ENV=>"FOO="}, {OUT=>"A=xx"}], 138 ['v4', q[-i -S'A=x${FOO}x ]."$env'", {OUT=>"A=xx"}], 139 ['v5', q[-i -S'A="x${FOO}x" ]."$env'", {ENV=>"FOO=BAR"}, {OUT=>"A=xBARx"}], 140 ['v6', q[-i -S'${FOO}=A ]."$env'", {ENV=>"FOO=BAR"}, {OUT=>"BAR=A"}], 141 # No expansion inside single-quotes 142 ['v7', q[-i -S"A='x\${FOO}x' ]."$env\"", {OUT=>'A=x${FOO}x'}], 143 ['v8', q[-i -S'A="${_FOO}" ]."$env'", {ENV=>"_FOO=BAR"}, {OUT=>"A=BAR"}], 144 ['v9', q[-i -S'A="${F_OO}" ]."$env'", {ENV=>"F_OO=BAR"}, {OUT=>"A=BAR"}], 145 ['v10', q[-i -S'A="${FOO1}" ]."$env'", {ENV=>"FOO1=BAR"}, {OUT=>"A=BAR"}], 146 147 # Test end-of-string '#" and '\c' 148 ['d1', q[-i -S'A=B #C=D' ]."$env", {OUT=>"A=B"}], 149 ['d2', q[-i -S'#A=B C=D' ]."$env", {OUT=>""}], 150 ['d3', q[-i -S'A=B#' ]."$env", {OUT=>"A=B#"}], 151 ['d4', q[-i -S'A=B #' ]."$env", {OUT=>"A=B"}], 152 153 ['d5', q[-i -S'A=B\cC=D' ]."$env", {OUT=>"A=B"}], 154 ['d6', q[-i -S'\cA=B C=D' ]."$env", {OUT=>""}], 155 ['d7', q[-i -S'A=B\c' ]."$env", {OUT=>"A=B"}], 156 ['d8', q[-i -S'A=B \c' ]."$env", {OUT=>"A=B"}], 157 158 ['d10', q[-S'echo FOO #BAR'], {OUT=>"FOO"}], 159 ['d11', q[-S'echo FOO \\#BAR'], {OUT=>"FOO #BAR"}], 160 ['d12', q[-S'echo FOO#BAR'], {OUT=>"FOO#BAR"}], 161 162 # Test underscore as space/separator in double/single/no quotes 163 ['s1', q[-S'printf x%sx\\n "A\\_B"'], {OUT=>"xA Bx"}], 164 ['s2', q[-S"printf x%sx\\n 'A\\_B'"], {OUT=>"xA\\_Bx"}], 165 ['s3', q[-S"printf x%sx\\n A\\_B"], {OUT=>"xAx\nxBx"}], 166 ['s4', q[-S"printf x%sx\\n A B"], {OUT=>"xAx\nxBx"}], 167 ['s5', q[-S"printf x%sx\\n A B"], {OUT=>"xAx\nxBx"}], 168 # test underscore/spaces variations - 169 # ensure they don't generate empty arguments. 170 ['s6', q[-S"\\_printf x%sx\\n FOO"], {OUT=>"xFOOx"}], 171 ['s7', q[-S"printf x%sx\\n FOO\\_"], {OUT=>"xFOOx"}], 172 ['s8', q[-S"\\_printf x%sx\\n FOO\\_"], {OUT=>"xFOOx"}], 173 ['s9', q[-S"\\_\\_printf x%sx\\n FOO\\_\\_"], {OUT=>"xFOOx"}], 174 ['s10', q[-S" printf x%sx\\n FOO"], {OUT=>"xFOOx"}], 175 ['s11', q[-S"printf x%sx\\n FOO "], {OUT=>"xFOOx"}], 176 ['s12', q[-S" printf x%sx\\n FOO "], {OUT=>"xFOOx"}], 177 ['s13', q[-S" printf x%sx\\n FOO "], {OUT=>"xFOOx"}], 178 ['s14', q[-S'printf\\_x%sx\\n\\_FOO'], {OUT=>"xFOOx"}], 179 ['s15', q[-S"printf x%sx\\n \\_ FOO"], {OUT=>"xFOOx"}], 180 ['s16', q[-S"printf x%sx\\n\\_ \\_FOO"], {OUT=>"xFOOx"}], 181 ['s17', q[-S"\\_ \\_ printf x%sx\\n FOO \\_ \\_"], {OUT=>"xFOOx"}], 182 183 # Check for empty quotes 184 ['eq1', q[-S'printf x%sx\\n A "" B'], {OUT=>"xAx\nxx\nxBx"}], 185 ['eq2', q[-S'printf x%sx\\n A"" B'], {OUT=>"xAx\nxBx"}], 186 ['eq3', q[-S'printf x%sx\\n A""B'], {OUT=>"xABx"}], 187 ['eq4', q[-S'printf x%sx\\n A ""B'], {OUT=>"xAx\nxBx"}], 188 ['eq5', q[-S'printf x%sx\\n ""'], {OUT=>"xx"}], 189 ['eq6', q[-S'printf x%sx\\n "" '], {OUT=>"xx"}], 190 ['eq10', q[-S"printf x%sx\\n A '' B"], {OUT=>"xAx\nxx\nxBx"}], 191 ['eq11', q[-S"printf x%sx\\n A'' B"], {OUT=>"xAx\nxBx"}], 192 ['eq12', q[-S"printf x%sx\\n A''B"], {OUT=>"xABx"}], 193 ['eq13', q[-S"printf x%sx\\n A ''B"], {OUT=>"xAx\nxBx"}], 194 ['eq14', q[-S'printf x%sx\\n ""'], {OUT=>"xx"}], 195 ['eq15', q[-S'printf x%sx\\n "" '], {OUT=>"xx"}], 196 197 # extreme example - such as could be found on a #! line. 198 ['p10', q[-S"\\_ \\_perl\_-w\_-T\_-e\_'print \"hello\n\";'\\_ \\_"], 199 {OUT=>"hello"}], 200 201 # Test Error Conditions 202 ['err1', q[-S'"\\c"'], {EXIT=>125}, 203 {ERR=>"$prog: '\\c' must not appear in double-quoted -S string\n"}], 204 ['err2', q[-S'A=B\\'], {EXIT=>125}, 205 {ERR=>"$prog: invalid backslash at end of string in -S\n"}], 206 ['err3', q[-S'"A=B\\"'], {EXIT=>125}, 207 {ERR=>"$prog: no terminating quote in -S string\n"}], 208 ['err4', q[-S"'A=B\\\\'"], {EXIT=>125}, 209 {ERR=>"$prog: no terminating quote in -S string\n"}], 210 ['err5', q[-S'A=B\\q'], {EXIT=>125}, 211 {ERR=>"$prog: invalid sequence '\\q' in -S\n"}], 212 ['err6', q[-S'A=$B'], {EXIT=>125}, 213 {ERR=>"$prog: only \${VARNAME} expansion is supported, error at: \$B\n"}], 214 ['err7', q[-S'A=${B'], {EXIT=>125}, 215 {ERR=>"$prog: only \${VARNAME} expansion is supported, " . 216 "error at: \${B\n"}], 217 ['err8', q[-S'A=${B%B}'], {EXIT=>125}, 218 {ERR=>"$prog: only \${VARNAME} expansion is supported, " . 219 "error at: \${B%B}\n"}], 220 ['err9', q[-S'A=${9B}'], {EXIT=>125}, 221 {ERR=>"$prog: only \${VARNAME} expansion is supported, " . 222 "error at: \${9B}\n"}], 223 224 # Test incorrect shebang usage (extraneous whitespace). 225 ['err_sp2', q['-v -S cat -n'], {EXIT=>125}, 226 {ERR=>"env: invalid option -- ' '\n" . 227 "env: use -[v]S to pass options in shebang lines\n" . 228 "Try 'env --help' for more information.\n"}], 229 ['err_sp3', q['-v -S cat -n'], {EXIT=>125}, # embedded tab after -v 230 {ERR=>"env: invalid option -- '\t'\n" . 231 "env: use -[v]S to pass options in shebang lines\n" . 232 "Try 'env --help' for more information.\n"}], 233 234 # Also diagnose incorrect shebang usage when failing to exec. 235 # This typically happens with: 236 # 237 # $ cat xxx 238 # #!env -v -S cat -n 239 # 240 # $ ./xxx 241 # 242 # in which case: 243 # argv[0] = env 244 # argv[1] = '-v -S cat -n' 245 # argv[2] = './xxx' 246 ['err_sp5', q['cat -n' ./xxx], {EXIT=>127}, 247 {ERR=>"env: 'cat -n': No such file or directory\n" . 248 "env: use -[v]S to pass options in shebang lines\n"}], 249 250 ['err_sp6', q['cat -n' ./xxx arg], {EXIT=>127}, 251 {ERR=>"env: 'cat -n': No such file or directory\n" . 252 "env: use -[v]S to pass options in shebang lines\n"}], 253 ); 254 255# Append a newline to end of each expected 'OUT' string. 256my $t; 257foreach $t (@Tests) 258 { 259 my $arg1 = $t->[1]; 260 my $e; 261 foreach $e (@$t) 262 { 263 $e->{OUT} .= "\n" 264 if ref $e eq 'HASH' and exists $e->{OUT} and length($e->{OUT})>0; 265 } 266 } 267 268# Repeat above tests with "--debug" option (but discard STDERR). 269my @new; 270foreach my $t (@Tests) 271{ 272 #skip tests that are expected to fail 273 next if $t->[0] =~ /^err/; 274 275 my @new_t = @$t; 276 my $test_name = shift @new_t; 277 my $args = shift @new_t; 278 push @new, ["$test_name-debug", 279 "--debug " . $args, 280 @new_t, 281 {ERR_SUBST => 's/.*//ms'}]; 282} 283push @Tests, @new; 284 285my $save_temps = $ENV{SAVE_TEMPS}; 286my $verbose = $ENV{VERBOSE}; 287 288my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose); 289exit $fail; 290