1 | /* Test capturing output from a subprocess. |
2 | Copyright (C) 2017-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <stdbool.h> |
20 | #include <stdio.h> |
21 | #include <stdlib.h> |
22 | #include <string.h> |
23 | #include <support/capture_subprocess.h> |
24 | #include <support/check.h> |
25 | #include <support/support.h> |
26 | #include <support/temp_file.h> |
27 | #include <sys/wait.h> |
28 | #include <unistd.h> |
29 | #include <paths.h> |
30 | #include <getopt.h> |
31 | #include <limits.h> |
32 | #include <errno.h> |
33 | #include <array_length.h> |
34 | |
35 | /* Nonzero if the program gets called via 'exec'. */ |
36 | static int restart; |
37 | |
38 | /* Hold the four initial argument used to respawn the process. */ |
39 | static char *initial_argv[5]; |
40 | |
41 | /* Write one byte at *P to FD and advance *P. Do nothing if *P is |
42 | '\0'. */ |
43 | static void |
44 | transfer (const unsigned char **p, int fd) |
45 | { |
46 | if (**p != '\0') |
47 | { |
48 | TEST_VERIFY (write (fd, *p, 1) == 1); |
49 | ++*p; |
50 | } |
51 | } |
52 | |
53 | /* Determine the order in which stdout and stderr are written. */ |
54 | enum write_mode { out_first, err_first, interleave, |
55 | write_mode_last = interleave }; |
56 | |
57 | static const char * |
58 | write_mode_to_str (enum write_mode mode) |
59 | { |
60 | switch (mode) |
61 | { |
62 | case out_first: return "out_first" ; |
63 | case err_first: return "err_first" ; |
64 | case interleave: return "interleave" ; |
65 | default: return "write_mode_last" ; |
66 | } |
67 | } |
68 | |
69 | static enum write_mode |
70 | str_to_write_mode (const char *mode) |
71 | { |
72 | if (strcmp (s1: mode, s2: "out_first" ) == 0) |
73 | return out_first; |
74 | else if (strcmp (s1: mode, s2: "err_first" ) == 0) |
75 | return err_first; |
76 | else if (strcmp (s1: mode, s2: "interleave" ) == 0) |
77 | return interleave; |
78 | return write_mode_last; |
79 | } |
80 | |
81 | /* Describe what to write in the subprocess. */ |
82 | struct test |
83 | { |
84 | char *out; |
85 | char *err; |
86 | enum write_mode write_mode; |
87 | int signal; |
88 | int status; |
89 | }; |
90 | |
91 | _Noreturn static void |
92 | test_common (const struct test *test) |
93 | { |
94 | bool mode_ok = false; |
95 | switch (test->write_mode) |
96 | { |
97 | case out_first: |
98 | TEST_VERIFY (fputs (test->out, stdout) >= 0); |
99 | TEST_VERIFY (fflush (stdout) == 0); |
100 | TEST_VERIFY (fputs (test->err, stderr) >= 0); |
101 | TEST_VERIFY (fflush (stderr) == 0); |
102 | mode_ok = true; |
103 | break; |
104 | case err_first: |
105 | TEST_VERIFY (fputs (test->err, stderr) >= 0); |
106 | TEST_VERIFY (fflush (stderr) == 0); |
107 | TEST_VERIFY (fputs (test->out, stdout) >= 0); |
108 | TEST_VERIFY (fflush (stdout) == 0); |
109 | mode_ok = true; |
110 | break; |
111 | case interleave: |
112 | { |
113 | const unsigned char *pout = (const unsigned char *) test->out; |
114 | const unsigned char *perr = (const unsigned char *) test->err; |
115 | do |
116 | { |
117 | transfer (p: &pout, STDOUT_FILENO); |
118 | transfer (p: &perr, STDERR_FILENO); |
119 | } |
120 | while (*pout != '\0' || *perr != '\0'); |
121 | } |
122 | mode_ok = true; |
123 | break; |
124 | } |
125 | TEST_VERIFY (mode_ok); |
126 | |
127 | if (test->signal != 0) |
128 | raise (sig: test->signal); |
129 | exit (status: test->status); |
130 | } |
131 | |
132 | static int |
133 | parse_int (const char *str) |
134 | { |
135 | char *endptr; |
136 | long int ret; |
137 | errno = 0; |
138 | ret = strtol (str, &endptr, 10); |
139 | TEST_COMPARE (errno, 0); |
140 | TEST_VERIFY (ret >= 0 && ret <= INT_MAX); |
141 | return ret; |
142 | } |
143 | |
144 | /* For use with support_capture_subprogram. */ |
145 | _Noreturn static void |
146 | handle_restart (char *out, char *err, const char *write_mode, |
147 | const char *signal, const char *status) |
148 | { |
149 | struct test test = |
150 | { |
151 | out, |
152 | err, |
153 | str_to_write_mode (mode: write_mode), |
154 | parse_int (str: signal), |
155 | parse_int (str: status) |
156 | }; |
157 | test_common (test: &test); |
158 | } |
159 | |
160 | /* For use with support_capture_subprocess. */ |
161 | _Noreturn static void |
162 | callback (void *closure) |
163 | { |
164 | const struct test *test = closure; |
165 | test_common (test); |
166 | } |
167 | |
168 | /* Create a heap-allocated random string of letters. */ |
169 | static char * |
170 | random_string (size_t length) |
171 | { |
172 | char *result = xmalloc (n: length + 1); |
173 | for (size_t i = 0; i < length; ++i) |
174 | result[i] = 'a' + (rand () % 26); |
175 | result[length] = '\0'; |
176 | return result; |
177 | } |
178 | |
179 | /* Check that the specific stream from the captured subprocess matches |
180 | expectations. */ |
181 | static void |
182 | check_stream (const char *what, const struct xmemstream *stream, |
183 | const char *expected) |
184 | { |
185 | if (strcmp (s1: stream->buffer, s2: expected) != 0) |
186 | { |
187 | support_record_failure (); |
188 | printf (format: "error: captured %s data incorrect\n" |
189 | " expected: %s\n" |
190 | " actual: %s\n" , |
191 | what, expected, stream->buffer); |
192 | } |
193 | if (stream->length != strlen (s: expected)) |
194 | { |
195 | support_record_failure (); |
196 | printf (format: "error: captured %s data length incorrect\n" |
197 | " expected: %zu\n" |
198 | " actual: %zu\n" , |
199 | what, strlen (s: expected), stream->length); |
200 | } |
201 | } |
202 | |
203 | static struct support_capture_subprocess |
204 | do_subprocess (struct test *test) |
205 | { |
206 | return support_capture_subprocess (callback, closure: test); |
207 | } |
208 | |
209 | static struct support_capture_subprocess |
210 | do_subprogram (const struct test *test) |
211 | { |
212 | /* Three digits per byte plus null terminator. */ |
213 | char signalstr[3 * sizeof(int) + 1]; |
214 | snprintf (s: signalstr, maxlen: sizeof (signalstr), format: "%d" , test->signal); |
215 | char statusstr[3 * sizeof(int) + 1]; |
216 | snprintf (s: statusstr, maxlen: sizeof (statusstr), format: "%d" , test->status); |
217 | |
218 | int argc = 0; |
219 | enum { |
220 | /* 4 elements from initial_argv (path to ld.so, '--library-path', the |
221 | path', and application name'), 2 for restart argument ('--direct', |
222 | '--restart'), 5 arguments plus NULL. */ |
223 | argv_size = 12 |
224 | }; |
225 | char *args[argv_size]; |
226 | |
227 | for (char **arg = initial_argv; *arg != NULL; arg++) |
228 | args[argc++] = *arg; |
229 | |
230 | args[argc++] = (char*) "--direct" ; |
231 | args[argc++] = (char*) "--restart" ; |
232 | |
233 | args[argc++] = test->out; |
234 | args[argc++] = test->err; |
235 | args[argc++] = (char*) write_mode_to_str (mode: test->write_mode); |
236 | args[argc++] = signalstr; |
237 | args[argc++] = statusstr; |
238 | args[argc] = NULL; |
239 | TEST_VERIFY (argc < argv_size); |
240 | |
241 | return support_capture_subprogram (file: args[0], argv: args); |
242 | } |
243 | |
244 | enum test_type |
245 | { |
246 | subprocess, |
247 | subprogram, |
248 | }; |
249 | |
250 | static int |
251 | do_multiple_tests (enum test_type type) |
252 | { |
253 | const int lengths[] = {0, 1, 17, 512, 20000, -1}; |
254 | |
255 | /* Test multiple combinations of support_capture_sub{process,program}. |
256 | |
257 | length_idx_stdout: Index into the lengths array above, |
258 | controls how many bytes are written by the subprocess to |
259 | standard output. |
260 | length_idx_stderr: Same for standard error. |
261 | write_mode: How standard output and standard error writes are |
262 | ordered. |
263 | signal: Exit with no signal if zero, with SIGTERM if one. |
264 | status: Process exit status: 0 if zero, 3 if one. */ |
265 | for (int length_idx_stdout = 0; lengths[length_idx_stdout] >= 0; |
266 | ++length_idx_stdout) |
267 | for (int length_idx_stderr = 0; lengths[length_idx_stderr] >= 0; |
268 | ++length_idx_stderr) |
269 | for (int write_mode = 0; write_mode < write_mode_last; ++write_mode) |
270 | for (int signal = 0; signal < 2; ++signal) |
271 | for (int status = 0; status < 2; ++status) |
272 | { |
273 | struct test test = |
274 | { |
275 | .out = random_string (length: lengths[length_idx_stdout]), |
276 | .err = random_string (length: lengths[length_idx_stderr]), |
277 | .write_mode = write_mode, |
278 | .signal = signal * SIGTERM, /* 0 or SIGTERM. */ |
279 | .status = status * 3, /* 0 or 3. */ |
280 | }; |
281 | TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]); |
282 | TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]); |
283 | |
284 | struct support_capture_subprocess result |
285 | = type == subprocess ? do_subprocess (test: &test) |
286 | : do_subprogram (test: &test); |
287 | |
288 | check_stream (what: "stdout" , stream: &result.out, expected: test.out); |
289 | check_stream (what: "stderr" , stream: &result.err, expected: test.err); |
290 | |
291 | /* Allowed output for support_capture_subprocess_check. */ |
292 | int check_allow = 0; |
293 | if (lengths[length_idx_stdout] > 0) |
294 | check_allow |= sc_allow_stdout; |
295 | if (lengths[length_idx_stderr] > 0) |
296 | check_allow |= sc_allow_stderr; |
297 | if (check_allow == 0) |
298 | check_allow = sc_allow_none; |
299 | |
300 | if (test.signal != 0) |
301 | { |
302 | TEST_VERIFY (WIFSIGNALED (result.status)); |
303 | TEST_VERIFY (WTERMSIG (result.status) == test.signal); |
304 | support_capture_subprocess_check (&result, context: "signal" , |
305 | status_or_signal: -SIGTERM, allowed: check_allow); |
306 | } |
307 | else |
308 | { |
309 | TEST_VERIFY (WIFEXITED (result.status)); |
310 | TEST_VERIFY (WEXITSTATUS (result.status) == test.status); |
311 | support_capture_subprocess_check (&result, context: "exit" , |
312 | status_or_signal: test.status, allowed: check_allow); |
313 | } |
314 | support_capture_subprocess_free (&result); |
315 | free (ptr: test.out); |
316 | free (ptr: test.err); |
317 | } |
318 | return 0; |
319 | } |
320 | |
321 | static int |
322 | do_test (int argc, char *argv[]) |
323 | { |
324 | /* We must have either: |
325 | |
326 | - one or four parameters if called initially: |
327 | + argv[1]: path for ld.so optional |
328 | + argv[2]: "--library-path" optional |
329 | + argv[3]: the library path optional |
330 | + argv[4]: the application name |
331 | |
332 | - six parameters left if called through re-execution: |
333 | + argv[1]: the application name |
334 | + argv[2]: the stdout to print |
335 | + argv[3]: the stderr to print |
336 | + argv[4]: the write mode to use |
337 | + argv[5]: the signal to issue |
338 | + argv[6]: the exit status code to use |
339 | |
340 | * When built with --enable-hardcoded-path-in-tests or issued without |
341 | using the loader directly. |
342 | */ |
343 | |
344 | if (argc != (restart ? 6 : 5) && argc != (restart ? 6 : 2)) |
345 | FAIL_EXIT1 ("wrong number of arguments (%d)" , argc); |
346 | |
347 | if (restart) |
348 | { |
349 | handle_restart (out: argv[1], /* stdout */ |
350 | err: argv[2], /* stderr */ |
351 | write_mode: argv[3], /* write_mode */ |
352 | signal: argv[4], /* signal */ |
353 | status: argv[5]); /* status */ |
354 | } |
355 | |
356 | initial_argv[0] = argv[1]; /* path for ld.so */ |
357 | initial_argv[1] = argv[2]; /* "--library-path" */ |
358 | initial_argv[2] = argv[3]; /* the library path */ |
359 | initial_argv[3] = argv[4]; /* the application name */ |
360 | initial_argv[4] = NULL; |
361 | |
362 | do_multiple_tests (type: subprocess); |
363 | do_multiple_tests (type: subprogram); |
364 | |
365 | return 0; |
366 | } |
367 | |
368 | #define CMDLINE_OPTIONS \ |
369 | { "restart", no_argument, &restart, 1 }, |
370 | #define TEST_FUNCTION_ARGV do_test |
371 | #include <support/test-driver.c> |
372 | |