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