1/* $NetBSD: t_fileactions.c,v 1.6 2017/01/10 22:36:29 christos 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@NetBSD.org> and
9 * Martin Husemann <martin@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
34#include <atf-c.h>
35
36#include <sys/wait.h>
37#include <sys/stat.h>
38
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <errno.h>
43#include <fcntl.h>
44#include <spawn.h>
45#include <unistd.h>
46
47
48ATF_TC(t_spawn_openmode);
49
50ATF_TC_HEAD(t_spawn_openmode, tc)
51{
52	atf_tc_set_md_var(tc, "descr",
53	    "Test the proper handling of 'mode' for 'open' fileactions");
54	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
55}
56
57static off_t
58filesize(const char * restrict fname)
59{
60	struct stat st;
61	int err;
62
63	err = stat(fname, &st);
64	ATF_REQUIRE(err == 0);
65	return st.st_size;
66}
67
68#define TESTFILE	"./the_input_data"
69#define CHECKFILE	"./the_output_data"
70#define TESTCONTENT	"marry has a little lamb"
71
72static void
73make_testfile(const char *restrict file)
74{
75	FILE *f;
76	size_t written;
77
78	f = fopen(file, "w");
79	ATF_REQUIRE(f != NULL);
80	written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f);
81	fclose(f);
82	ATF_REQUIRE(written == strlen(TESTCONTENT));
83}
84
85static void
86empty_outfile(const char *restrict filename)
87{
88	FILE *f;
89
90	f = fopen(filename, "w");
91	ATF_REQUIRE(f != NULL);
92	fclose(f);
93}
94
95ATF_TC_BODY(t_spawn_openmode, tc)
96{
97	int status, err;
98	pid_t pid;
99	size_t insize, outsize;
100	char * const args[2] = { __UNCONST("cat"), NULL };
101	posix_spawn_file_actions_t fa;
102
103	/*
104	 * try a "cat < testfile > checkfile"
105	 */
106	make_testfile(TESTFILE);
107	unlink(CHECKFILE);
108
109	posix_spawn_file_actions_init(&fa);
110	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
111	    TESTFILE, O_RDONLY, 0);
112	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
113	    CHECKFILE, O_WRONLY|O_CREAT, 0600);
114	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
115	posix_spawn_file_actions_destroy(&fa);
116
117	ATF_REQUIRE(err == 0);
118
119	/* ok, wait for the child to finish */
120	waitpid(pid, &status, 0);
121	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
122
123	/* now check that input and output have the same size */
124	insize = filesize(TESTFILE);
125	outsize = filesize(CHECKFILE);
126	ATF_REQUIRE(insize == strlen(TESTCONTENT));
127	ATF_REQUIRE(insize == outsize);
128
129	/*
130	 * try a "cat < testfile >> checkfile"
131	 */
132	make_testfile(TESTFILE);
133	make_testfile(CHECKFILE);
134
135	posix_spawn_file_actions_init(&fa);
136	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
137	    TESTFILE, O_RDONLY, 0);
138	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
139	    CHECKFILE, O_WRONLY|O_APPEND, 0);
140	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
141	posix_spawn_file_actions_destroy(&fa);
142
143	ATF_REQUIRE(err == 0);
144
145	/* ok, wait for the child to finish */
146	waitpid(pid, &status, 0);
147	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
148
149	/* now check that output is twice as long as input */
150	insize = filesize(TESTFILE);
151	outsize = filesize(CHECKFILE);
152	ATF_REQUIRE(insize == strlen(TESTCONTENT));
153	ATF_REQUIRE(insize*2 == outsize);
154
155	/*
156	 * try a "cat < testfile  > checkfile" with input and output swapped
157	 */
158	make_testfile(TESTFILE);
159	empty_outfile(CHECKFILE);
160
161	posix_spawn_file_actions_init(&fa);
162	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
163	    TESTFILE, O_RDONLY, 0);
164	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
165	    CHECKFILE, O_WRONLY, 0);
166	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
167	posix_spawn_file_actions_destroy(&fa);
168
169	ATF_REQUIRE(err == 0);
170
171	/* ok, wait for the child to finish */
172	waitpid(pid, &status, 0);
173	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE);
174
175	/* now check that input and output are still the same size */
176	insize = filesize(TESTFILE);
177	outsize = filesize(CHECKFILE);
178	ATF_REQUIRE(insize == strlen(TESTCONTENT));
179	ATF_REQUIRE(outsize == 0);
180}
181
182ATF_TC(t_spawn_reopen);
183
184ATF_TC_HEAD(t_spawn_reopen, tc)
185{
186	atf_tc_set_md_var(tc, "descr",
187	    "an open filehandle can be replaced by a 'open' fileaction");
188	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
189}
190
191ATF_TC_BODY(t_spawn_reopen, tc)
192{
193	int status, err;
194	pid_t pid;
195	char * const args[2] = { __UNCONST("cat"), NULL };
196	posix_spawn_file_actions_t fa;
197
198	/*
199	 * make sure stdin is open in the parent
200	 */
201	freopen("/dev/zero", "r", stdin);
202	/*
203	 * now request an open for this fd again in the child
204	 */
205	posix_spawn_file_actions_init(&fa);
206	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
207	    "/dev/null", O_RDONLY, 0);
208	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
209	posix_spawn_file_actions_destroy(&fa);
210
211	ATF_REQUIRE(err == 0);
212
213	waitpid(pid, &status, 0);
214	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
215}
216
217ATF_TC(t_spawn_open_nonexistent);
218
219ATF_TC_HEAD(t_spawn_open_nonexistent, tc)
220{
221	atf_tc_set_md_var(tc, "descr",
222	    "posix_spawn fails when a file to open does not exist");
223	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
224}
225
226ATF_TC_BODY(t_spawn_open_nonexistent, tc)
227{
228	int err, status;
229	pid_t pid;
230	char * const args[2] = { __UNCONST("cat"), NULL };
231	posix_spawn_file_actions_t fa;
232
233	posix_spawn_file_actions_init(&fa);
234	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
235	    "./non/ex/ist/ent", O_RDONLY, 0);
236	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
237	if (err == 0) {
238		/*
239		 * The child has been created - it should fail and
240		 * return exit code 127
241		 */
242		waitpid(pid, &status, 0);
243		ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127);
244	} else {
245		/*
246		 * The error has been noticed early enough, no child has
247		 * been run
248		 */
249		ATF_REQUIRE(err == ENOENT);
250	}
251	posix_spawn_file_actions_destroy(&fa);
252}
253
254#ifdef __NetBSD__
255ATF_TC(t_spawn_open_nonexistent_diag);
256
257ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc)
258{
259	atf_tc_set_md_var(tc, "descr",
260	    "posix_spawn fails when a file to open does not exist "
261	    "and delivers proper diagnostic");
262	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
263}
264
265ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc)
266{
267	int err;
268	pid_t pid;
269	char * const args[2] = { __UNCONST("cat"), NULL };
270	posix_spawnattr_t attr;
271	posix_spawn_file_actions_t fa;
272
273	posix_spawnattr_init(&attr);
274	/*
275	 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
276	 * will cause a "proper" return value from posix_spawn(2)
277	 * instead of a (potential) success there and a 127 exit
278	 * status from the child process (c.f. the non-diag variant
279	 * of this test).
280	 */
281	posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR);
282	posix_spawn_file_actions_init(&fa);
283	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
284	    "./non/ex/ist/ent", O_RDONLY, 0);
285	err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL);
286	ATF_REQUIRE(err == ENOENT);
287	posix_spawn_file_actions_destroy(&fa);
288	posix_spawnattr_destroy(&attr);
289}
290#endif
291
292ATF_TC(t_spawn_fileactions);
293
294ATF_TC_HEAD(t_spawn_fileactions, tc)
295{
296	atf_tc_set_md_var(tc, "descr",
297	    "Tests various complex fileactions");
298}
299
300ATF_TC_BODY(t_spawn_fileactions, tc)
301{
302	int fd1, fd2, fd3, status, err;
303	pid_t pid;
304	char * const args[2] = { __UNCONST("h_fileactions"), NULL };
305	char helper[FILENAME_MAX];
306	posix_spawn_file_actions_t fa;
307
308	posix_spawn_file_actions_init(&fa);
309
310	closefrom(fileno(stderr)+1);
311
312	fd1 = open("/dev/null", O_RDONLY);
313	ATF_REQUIRE(fd1 == 3);
314
315	fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC);
316	ATF_REQUIRE(fd2 == 4);
317
318	fd3 = open("/dev/null", O_WRONLY);
319	ATF_REQUIRE(fd3 == 5);
320
321	posix_spawn_file_actions_addclose(&fa, fd1);
322	posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0);
323	posix_spawn_file_actions_adddup2(&fa, 1, 7);
324
325	snprintf(helper, sizeof helper, "%s/h_fileactions",
326	    atf_tc_get_config_var(tc, "srcdir"));
327	err = posix_spawn(&pid, helper, &fa, NULL, args, NULL);
328	posix_spawn_file_actions_destroy(&fa);
329
330	ATF_REQUIRE(err == 0);
331
332	waitpid(pid, &status, 0);
333	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
334}
335
336ATF_TC(t_spawn_empty_fileactions);
337
338ATF_TC_HEAD(t_spawn_empty_fileactions, tc)
339{
340	atf_tc_set_md_var(tc, "descr",
341	    "posix_spawn with empty fileactions (PR kern/46038)");
342	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
343}
344
345ATF_TC_BODY(t_spawn_empty_fileactions, tc)
346{
347	int status, err;
348	pid_t pid;
349	char * const args[2] = { __UNCONST("cat"), NULL };
350	posix_spawn_file_actions_t fa;
351	size_t insize, outsize;
352
353	/*
354	 * try a "cat < testfile > checkfile", but set up stdin/stdout
355	 * already in the parent and pass empty file actions to the child.
356	 */
357	make_testfile(TESTFILE);
358	unlink(CHECKFILE);
359
360	freopen(TESTFILE, "r", stdin);
361	freopen(CHECKFILE, "w", stdout);
362
363	posix_spawn_file_actions_init(&fa);
364	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
365	posix_spawn_file_actions_destroy(&fa);
366
367	ATF_REQUIRE(err == 0);
368
369	/* ok, wait for the child to finish */
370	waitpid(pid, &status, 0);
371	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
372
373	/* now check that input and output have the same size */
374	insize = filesize(TESTFILE);
375	outsize = filesize(CHECKFILE);
376	ATF_REQUIRE(insize == strlen(TESTCONTENT));
377	ATF_REQUIRE(insize == outsize);
378}
379
380ATF_TP_ADD_TCS(tp)
381{
382	ATF_TP_ADD_TC(tp, t_spawn_fileactions);
383	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent);
384#ifdef __NetBSD__
385	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag);
386#endif
387	ATF_TP_ADD_TC(tp, t_spawn_reopen);
388	ATF_TP_ADD_TC(tp, t_spawn_openmode);
389	ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions);
390
391	return atf_no_error();
392}
393