t_fileactions.c revision 1.9 1 /* $NetBSD: t_fileactions.c,v 1.9 2025/07/09 11:40:43 martin Exp $ */
2
3 /*-
4 * Copyright (c) 2012 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Charles Zhang <charles (at) NetBSD.org> and
9 * Martin Husemann <martin (at) NetBSD.org>.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <sys/cdefs.h>
34 __RCSID("$NetBSD: t_fileactions.c,v 1.9 2025/07/09 11:40:43 martin Exp $");
35
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38
39 #include <atf-c.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <spawn.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47
48 #include "fa_spawn_utils.h"
49 #include "h_macros.h"
50
51 #define TESTFILE "./the_input_data"
52 #define CHECKFILE "./the_output_data"
53 #define TESTCONTENT "marry has a little lamb"
54
55 static void
56 make_testfile(const char *restrict file)
57 {
58 FILE *f;
59 ssize_t written;
60
61 REQUIRE_LIBC(f = fopen(file, "w"), NULL);
62 RL(written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f));
63 REQUIRE_LIBC(fclose(f), EOF);
64 ATF_REQUIRE((size_t)written == strlen(TESTCONTENT));
65 }
66
67 ATF_TC(t_spawn_openmode);
68 ATF_TC_HEAD(t_spawn_openmode, tc)
69 {
70 atf_tc_set_md_var(tc, "descr",
71 "Test the proper handling of 'mode' for 'open' fileactions");
72 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
73 }
74 ATF_TC_BODY(t_spawn_openmode, tc)
75 {
76 int status;
77 pid_t pid;
78 size_t insize, outsize;
79 char * const args[2] = { __UNCONST("cat"), NULL };
80 posix_spawn_file_actions_t fa;
81
82 /*
83 * try a "cat < testfile > checkfile"
84 */
85 make_testfile(TESTFILE);
86
87 RZ(posix_spawn_file_actions_init(&fa));
88 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin),
89 TESTFILE, O_RDONLY, 0));
90 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout),
91 CHECKFILE, O_WRONLY|O_CREAT, 0600));
92 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
93 RZ(posix_spawn_file_actions_destroy(&fa));
94
95 /* ok, wait for the child to finish */
96 RL(waitpid(pid, &status, 0));
97 ATF_REQUIRE_MSG((WIFEXITED(status) &&
98 WEXITSTATUS(status) == EXIT_SUCCESS),
99 "status=0x%x", status);
100
101 /* now check that input and output have the same size */
102 insize = filesize(TESTFILE);
103 outsize = filesize(CHECKFILE);
104 ATF_CHECK_MSG(insize == strlen(TESTCONTENT),
105 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT));
106 ATF_CHECK_MSG(insize == outsize,
107 "insize=%zu outsize=%zu", insize, outsize);
108
109 /*
110 * try a "cat < testfile >> checkfile"
111 */
112 make_testfile(TESTFILE);
113 make_testfile(CHECKFILE);
114
115 RZ(posix_spawn_file_actions_init(&fa));
116 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin),
117 TESTFILE, O_RDONLY, 0));
118 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout),
119 CHECKFILE, O_WRONLY|O_APPEND, 0));
120 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
121 RZ(posix_spawn_file_actions_destroy(&fa));
122
123 /* ok, wait for the child to finish */
124 RL(waitpid(pid, &status, 0));
125 ATF_REQUIRE_MSG((WIFEXITED(status) &&
126 WEXITSTATUS(status) == EXIT_SUCCESS),
127 "status=0x%x", status);
128
129 /* now check that output is twice as long as input */
130 insize = filesize(TESTFILE);
131 outsize = filesize(CHECKFILE);
132 ATF_CHECK_MSG(insize == strlen(TESTCONTENT),
133 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT));
134 ATF_CHECK_MSG(insize*2 == outsize,
135 "insize*2=%zu outsize=%zu", insize*2, outsize);
136
137 /*
138 * try a "cat < testfile > checkfile" with input and output swapped
139 */
140 make_testfile(TESTFILE);
141 empty_outfile(CHECKFILE);
142
143 RZ(posix_spawn_file_actions_init(&fa));
144 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout),
145 TESTFILE, O_RDONLY, 0));
146 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin),
147 CHECKFILE, O_WRONLY, 0));
148 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
149 RZ(posix_spawn_file_actions_destroy(&fa));
150
151 /* ok, wait for the child to finish */
152 RL(waitpid(pid, &status, 0));
153 ATF_REQUIRE_MSG((WIFEXITED(status) &&
154 WEXITSTATUS(status) == EXIT_FAILURE),
155 "status=0x%x", status);
156
157 /* now check that input and output are still the same size */
158 insize = filesize(TESTFILE);
159 outsize = filesize(CHECKFILE);
160 ATF_CHECK_MSG(insize == strlen(TESTCONTENT),
161 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT));
162 ATF_CHECK_MSG(outsize == 0,
163 "outsize=%zu", outsize);
164 }
165
166 ATF_TC(t_spawn_reopen);
167 ATF_TC_HEAD(t_spawn_reopen, tc)
168 {
169 atf_tc_set_md_var(tc, "descr",
170 "an open filehandle can be replaced by a 'open' fileaction");
171 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
172 }
173 ATF_TC_BODY(t_spawn_reopen, tc)
174 {
175 int status;
176 pid_t pid;
177 char * const args[2] = { __UNCONST("cat"), NULL };
178 posix_spawn_file_actions_t fa;
179
180 /*
181 * make sure stdin is open in the parent
182 */
183 REQUIRE_LIBC(freopen("/dev/zero", "r", stdin), NULL);
184 /*
185 * now request an open for this fd again in the child
186 */
187 RZ(posix_spawn_file_actions_init(&fa));
188 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin),
189 "/dev/null", O_RDONLY, 0));
190 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
191 RZ(posix_spawn_file_actions_destroy(&fa));
192
193 RL(waitpid(pid, &status, 0));
194 ATF_REQUIRE_MSG((WIFEXITED(status) &&
195 WEXITSTATUS(status) == EXIT_SUCCESS),
196 "status=0x%x", status);
197 }
198
199 ATF_TC(t_spawn_open_nonexistent);
200 ATF_TC_HEAD(t_spawn_open_nonexistent, tc)
201 {
202 atf_tc_set_md_var(tc, "descr",
203 "posix_spawn fails when a file to open does not exist");
204 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
205 }
206 ATF_TC_BODY(t_spawn_open_nonexistent, tc)
207 {
208 int err, status;
209 pid_t pid;
210 char * const args[2] = { __UNCONST("cat"), NULL };
211 posix_spawn_file_actions_t fa;
212
213 RZ(posix_spawn_file_actions_init(&fa));
214 RZ(posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
215 "./non/ex/ist/ent", O_RDONLY, 0));
216 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
217 if (err == 0) {
218 /*
219 * The child has been created - it should fail and
220 * return exit code 127
221 */
222 waitpid(pid, &status, 0);
223 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127);
224 } else {
225 /*
226 * The error has been noticed early enough, no child has
227 * been run
228 */
229 ATF_REQUIRE_MSG(err == ENOENT, "err=%d (%s)",
230 err, strerror(err));
231 }
232 RZ(posix_spawn_file_actions_destroy(&fa));
233 }
234
235 ATF_TC(t_spawn_open_nonexistent_diag);
236 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc)
237 {
238 atf_tc_set_md_var(tc, "descr",
239 "posix_spawn fails when a file to open does not exist "
240 "and delivers proper diagnostic");
241 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
242 }
243 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc)
244 {
245 int err;
246 pid_t pid;
247 char * const args[2] = { __UNCONST("cat"), NULL };
248 posix_spawnattr_t attr;
249 posix_spawn_file_actions_t fa;
250
251 RZ(posix_spawnattr_init(&attr));
252 /*
253 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
254 * will cause a "proper" return value from posix_spawn(2)
255 * instead of a (potential) success there and a 127 exit
256 * status from the child process (c.f. the non-diag variant
257 * of this test).
258 */
259 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR));
260 RZ(posix_spawn_file_actions_init(&fa));
261 RZ(posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
262 "./non/ex/ist/ent", O_RDONLY, 0));
263 err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL);
264 ATF_REQUIRE_MSG(err == ENOENT, "err=%d (%s)", err, strerror(err));
265 RZ(posix_spawn_file_actions_destroy(&fa));
266 RZ(posix_spawnattr_destroy(&attr));
267 }
268
269 ATF_TC(t_spawn_fileactions);
270 ATF_TC_HEAD(t_spawn_fileactions, tc)
271 {
272 atf_tc_set_md_var(tc, "descr",
273 "Tests various complex fileactions");
274 }
275 ATF_TC_BODY(t_spawn_fileactions, tc)
276 {
277 int fd1, fd2, fd3, status;
278 pid_t pid;
279 char * const args[2] = { __UNCONST("h_fileactions"), NULL };
280 char helper[FILENAME_MAX];
281 posix_spawn_file_actions_t fa;
282
283 RZ(posix_spawn_file_actions_init(&fa));
284
285 RL(closefrom(fileno(stderr) + 1));
286
287 RL(fd1 = open("/dev/null", O_RDONLY));
288 ATF_REQUIRE(fd1 == 3);
289
290 RL(fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC));
291 ATF_REQUIRE(fd2 == 4);
292
293 RL(fd3 = open("/dev/null", O_WRONLY));
294 ATF_REQUIRE(fd3 == 5);
295
296 RZ(posix_spawn_file_actions_addclose(&fa, fd1));
297 RZ(posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0));
298 RZ(posix_spawn_file_actions_adddup2(&fa, 1, 7));
299
300 snprintf(helper, sizeof helper, "%s/h_fileactions",
301 atf_tc_get_config_var(tc, "srcdir"));
302 RZ(posix_spawn(&pid, helper, &fa, NULL, args, NULL));
303 RZ(posix_spawn_file_actions_destroy(&fa));
304
305 RL(waitpid(pid, &status, 0));
306 ATF_REQUIRE_MSG((WIFEXITED(status) &&
307 WEXITSTATUS(status) == EXIT_SUCCESS),
308 "status=0x%x", status);
309 }
310
311 ATF_TC(t_spawn_close_already_closed);
312 ATF_TC_HEAD(t_spawn_close_already_closed, tc)
313 {
314 atf_tc_set_md_var(tc, "descr",
315 "file actions closing closed descriptors are allowed (PR 59523");
316 atf_tc_set_md_var(tc, "require.progs", "/bin/ls");
317 }
318
319 ATF_TC_BODY(t_spawn_close_already_closed, tc)
320 {
321 int status, fd;
322 pid_t pid;
323 char * const args[2] = { __UNCONST("ls"), NULL };
324 posix_spawn_file_actions_t fa;
325
326 /* get a free file descriptor number */
327 fd = open("/dev/null", O_RDONLY);
328 ATF_REQUIRE(fd >= 0);
329 close(fd);
330
331 RZ(posix_spawn_file_actions_init(&fa));
332 // redirect output to /dev/null to not garble atf test results
333 RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null",
334 O_WRONLY, 0));
335 // known closed fd
336 RZ(posix_spawn_file_actions_addclose(&fa, fd));
337 // a random fd we know nothing about (cross fingers!
338 RZ(posix_spawn_file_actions_addclose(&fa, fd+1));
339 // high fd probably not ever been allocated, likely to trigger
340 // a fd_getfile() failure in the kernel, which is another
341 // path that originaly caused the fallout in PR 59523
342 RZ(posix_spawn_file_actions_addclose(&fa, 560));
343 RZ(posix_spawn(&pid, "/bin/ls", &fa, NULL, args, NULL));
344 RZ(posix_spawn_file_actions_destroy(&fa));
345
346 /* ok, wait for the child to finish */
347 RL(waitpid(pid, &status, 0));
348 ATF_REQUIRE_MSG((WIFEXITED(status) &&
349 WEXITSTATUS(status) == EXIT_SUCCESS),
350 "status=0x%x", status);
351 }
352
353 ATF_TC(t_spawn_close_already_closed_wait);
354 ATF_TC_HEAD(t_spawn_close_already_closed_wait, tc)
355 {
356 atf_tc_set_md_var(tc, "descr",
357 "file actions closing closed descriptors are allowed, "
358 "with parent process waiting (PR 59523");
359 atf_tc_set_md_var(tc, "require.progs", "/bin/ls");
360 }
361
362 ATF_TC_BODY(t_spawn_close_already_closed_wait, tc)
363 {
364 int status, fd;
365 pid_t pid;
366 char * const args[2] = { __UNCONST("ls"), NULL };
367 posix_spawn_file_actions_t fa;
368 posix_spawnattr_t attr;
369
370 /* get a free file descriptor number */
371 fd = open("/dev/null", O_RDONLY);
372 ATF_REQUIRE(fd >= 0);
373 close(fd);
374 RZ(posix_spawn_file_actions_init(&fa));
375 // redirect output to /dev/null to not garble atf test results
376 RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null",
377 O_WRONLY, 0));
378 // known closed fd
379 RZ(posix_spawn_file_actions_addclose(&fa, fd));
380 // a random fd we know nothing about (cross fingers!
381 RZ(posix_spawn_file_actions_addclose(&fa, fd+1));
382 // high fd probably not ever been allocated, likely to trigger
383 // a fd_getfile() failure in the kernel, which is another
384 // path that originaly caused the fallout in PR 59523
385 RZ(posix_spawn_file_actions_addclose(&fa, 560));
386
387 RZ(posix_spawnattr_init(&attr));
388 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP));
389
390 RZ(posix_spawn(&pid, "/bin/ls", &fa, &attr, args, NULL));
391 RZ(posix_spawn_file_actions_destroy(&fa));
392
393 /* ok, wait for the child to finish */
394 RL(waitpid(pid, &status, 0));
395 ATF_REQUIRE_MSG((WIFEXITED(status) &&
396 WEXITSTATUS(status) == EXIT_SUCCESS),
397 "status=0x%x", status);
398 }
399
400 ATF_TC(t_spawn_empty_fileactions);
401 ATF_TC_HEAD(t_spawn_empty_fileactions, tc)
402 {
403 atf_tc_set_md_var(tc, "descr",
404 "posix_spawn with empty fileactions (PR kern/46038)");
405 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
406 }
407 ATF_TC_BODY(t_spawn_empty_fileactions, tc)
408 {
409 int status;
410 pid_t pid;
411 char * const args[2] = { __UNCONST("cat"), NULL };
412 posix_spawn_file_actions_t fa;
413 size_t insize, outsize;
414
415 /*
416 * try a "cat < testfile > checkfile", but set up stdin/stdout
417 * already in the parent and pass empty file actions to the child.
418 */
419 make_testfile(TESTFILE);
420
421 REQUIRE_LIBC(freopen(TESTFILE, "r", stdin), NULL);
422 REQUIRE_LIBC(freopen(CHECKFILE, "w", stdout), NULL);
423
424 RZ(posix_spawn_file_actions_init(&fa));
425 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
426 RZ(posix_spawn_file_actions_destroy(&fa));
427
428 /* ok, wait for the child to finish */
429 RL(waitpid(pid, &status, 0));
430 ATF_REQUIRE_MSG((WIFEXITED(status) &&
431 WEXITSTATUS(status) == EXIT_SUCCESS),
432 "status=0x%x", status);
433
434 /* now check that input and output have the same size */
435 insize = filesize(TESTFILE);
436 outsize = filesize(CHECKFILE);
437 ATF_CHECK_MSG(insize == strlen(TESTCONTENT),
438 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT));
439 ATF_CHECK_MSG(insize == outsize,
440 "insize=%zu outsize=%zu", insize, outsize);
441 }
442
443 ATF_TP_ADD_TCS(tp)
444 {
445
446 ATF_TP_ADD_TC(tp, t_spawn_fileactions);
447 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent);
448 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag);
449 ATF_TP_ADD_TC(tp, t_spawn_reopen);
450 ATF_TP_ADD_TC(tp, t_spawn_openmode);
451 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions);
452 ATF_TP_ADD_TC(tp, t_spawn_close_already_closed);
453 ATF_TP_ADD_TC(tp, t_spawn_close_already_closed_wait);
454
455 return atf_no_error();
456 }
457