t_fileactions.c revision 1.8 1 /* $NetBSD: t_fileactions.c,v 1.8 2025/03/16 15:35:36 riastradh 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.8 2025/03/16 15:35:36 riastradh 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_empty_fileactions);
312 ATF_TC_HEAD(t_spawn_empty_fileactions, tc)
313 {
314 atf_tc_set_md_var(tc, "descr",
315 "posix_spawn with empty fileactions (PR kern/46038)");
316 atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
317 }
318 ATF_TC_BODY(t_spawn_empty_fileactions, tc)
319 {
320 int status;
321 pid_t pid;
322 char * const args[2] = { __UNCONST("cat"), NULL };
323 posix_spawn_file_actions_t fa;
324 size_t insize, outsize;
325
326 /*
327 * try a "cat < testfile > checkfile", but set up stdin/stdout
328 * already in the parent and pass empty file actions to the child.
329 */
330 make_testfile(TESTFILE);
331
332 REQUIRE_LIBC(freopen(TESTFILE, "r", stdin), NULL);
333 REQUIRE_LIBC(freopen(CHECKFILE, "w", stdout), NULL);
334
335 RZ(posix_spawn_file_actions_init(&fa));
336 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL));
337 RZ(posix_spawn_file_actions_destroy(&fa));
338
339 /* ok, wait for the child to finish */
340 RL(waitpid(pid, &status, 0));
341 ATF_REQUIRE_MSG((WIFEXITED(status) &&
342 WEXITSTATUS(status) == EXIT_SUCCESS),
343 "status=0x%x", status);
344
345 /* now check that input and output have the same size */
346 insize = filesize(TESTFILE);
347 outsize = filesize(CHECKFILE);
348 ATF_CHECK_MSG(insize == strlen(TESTCONTENT),
349 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT));
350 ATF_CHECK_MSG(insize == outsize,
351 "insize=%zu outsize=%zu", insize, outsize);
352 }
353
354 ATF_TP_ADD_TCS(tp)
355 {
356
357 ATF_TP_ADD_TC(tp, t_spawn_fileactions);
358 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent);
359 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag);
360 ATF_TP_ADD_TC(tp, t_spawn_reopen);
361 ATF_TP_ADD_TC(tp, t_spawn_openmode);
362 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions);
363
364 return atf_no_error();
365 }
366