1313227Sngie/* $NetBSD: t_fileactions.c,v 1.6 2017/01/10 22:36:29 christos Exp $ */
2272343Sngie
3272343Sngie/*-
4272343Sngie * Copyright (c) 2012 The NetBSD Foundation, Inc.
5272343Sngie * All rights reserved.
6272343Sngie *
7272343Sngie * This code is derived from software contributed to The NetBSD Foundation
8272343Sngie * by Charles Zhang <charles@NetBSD.org> and
9272343Sngie * Martin Husemann <martin@NetBSD.org>.
10272343Sngie *
11272343Sngie * Redistribution and use in source and binary forms, with or without
12272343Sngie * modification, are permitted provided that the following conditions
13272343Sngie * are met:
14272343Sngie * 1. Redistributions of source code must retain the above copyright
15272343Sngie *    notice, this list of conditions and the following disclaimer.
16272343Sngie * 2. Redistributions in binary form must reproduce the above copyright
17272343Sngie *    notice, this list of conditions and the following disclaimer in the
18272343Sngie *    documentation and/or other materials provided with the distribution.
19272343Sngie *
20272343Sngie * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21272343Sngie * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22272343Sngie * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23272343Sngie * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24272343Sngie * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25272343Sngie * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26272343Sngie * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27272343Sngie * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28272343Sngie * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29272343Sngie * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30272343Sngie * POSSIBILITY OF SUCH DAMAGE.
31272343Sngie */
32272343Sngie
33272343Sngie
34313227Sngie#include <atf-c.h>
35313227Sngie
36313227Sngie#include <sys/wait.h>
37272914Sngie#include <sys/stat.h>
38313227Sngie
39272343Sngie#include <stdio.h>
40272343Sngie#include <stdlib.h>
41272343Sngie#include <string.h>
42272343Sngie#include <errno.h>
43272343Sngie#include <fcntl.h>
44272343Sngie#include <spawn.h>
45272343Sngie#include <unistd.h>
46272343Sngie
47272343Sngie
48272343SngieATF_TC(t_spawn_openmode);
49272343Sngie
50272343SngieATF_TC_HEAD(t_spawn_openmode, tc)
51272343Sngie{
52272343Sngie	atf_tc_set_md_var(tc, "descr",
53272343Sngie	    "Test the proper handling of 'mode' for 'open' fileactions");
54272343Sngie	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
55272343Sngie}
56272343Sngie
57272343Sngiestatic off_t
58272343Sngiefilesize(const char * restrict fname)
59272343Sngie{
60272343Sngie	struct stat st;
61272343Sngie	int err;
62272343Sngie
63272343Sngie	err = stat(fname, &st);
64272343Sngie	ATF_REQUIRE(err == 0);
65272343Sngie	return st.st_size;
66272343Sngie}
67272343Sngie
68272343Sngie#define TESTFILE	"./the_input_data"
69272343Sngie#define CHECKFILE	"./the_output_data"
70272343Sngie#define TESTCONTENT	"marry has a little lamb"
71272343Sngie
72272343Sngiestatic void
73272343Sngiemake_testfile(const char *restrict file)
74272343Sngie{
75272343Sngie	FILE *f;
76272343Sngie	size_t written;
77272343Sngie
78272343Sngie	f = fopen(file, "w");
79272343Sngie	ATF_REQUIRE(f != NULL);
80272343Sngie	written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f);
81272343Sngie	fclose(f);
82272343Sngie	ATF_REQUIRE(written == strlen(TESTCONTENT));
83272343Sngie}
84272343Sngie
85272343Sngiestatic void
86272343Sngieempty_outfile(const char *restrict filename)
87272343Sngie{
88272343Sngie	FILE *f;
89272343Sngie
90272343Sngie	f = fopen(filename, "w");
91272343Sngie	ATF_REQUIRE(f != NULL);
92272343Sngie	fclose(f);
93272343Sngie}
94272343Sngie
95272343SngieATF_TC_BODY(t_spawn_openmode, tc)
96272343Sngie{
97272343Sngie	int status, err;
98272343Sngie	pid_t pid;
99272343Sngie	size_t insize, outsize;
100272343Sngie	char * const args[2] = { __UNCONST("cat"), NULL };
101272343Sngie	posix_spawn_file_actions_t fa;
102272343Sngie
103272343Sngie	/*
104272343Sngie	 * try a "cat < testfile > checkfile"
105272343Sngie	 */
106272343Sngie	make_testfile(TESTFILE);
107272343Sngie	unlink(CHECKFILE);
108272343Sngie
109272343Sngie	posix_spawn_file_actions_init(&fa);
110272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
111272343Sngie	    TESTFILE, O_RDONLY, 0);
112272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
113272343Sngie	    CHECKFILE, O_WRONLY|O_CREAT, 0600);
114272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
115272343Sngie	posix_spawn_file_actions_destroy(&fa);
116272343Sngie
117272343Sngie	ATF_REQUIRE(err == 0);
118272343Sngie
119272343Sngie	/* ok, wait for the child to finish */
120272343Sngie	waitpid(pid, &status, 0);
121272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
122272343Sngie
123272343Sngie	/* now check that input and output have the same size */
124272343Sngie	insize = filesize(TESTFILE);
125272343Sngie	outsize = filesize(CHECKFILE);
126272343Sngie	ATF_REQUIRE(insize == strlen(TESTCONTENT));
127272343Sngie	ATF_REQUIRE(insize == outsize);
128272343Sngie
129272343Sngie	/*
130272343Sngie	 * try a "cat < testfile >> checkfile"
131272343Sngie	 */
132272343Sngie	make_testfile(TESTFILE);
133272343Sngie	make_testfile(CHECKFILE);
134272343Sngie
135272343Sngie	posix_spawn_file_actions_init(&fa);
136272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
137272343Sngie	    TESTFILE, O_RDONLY, 0);
138272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
139272343Sngie	    CHECKFILE, O_WRONLY|O_APPEND, 0);
140272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
141272343Sngie	posix_spawn_file_actions_destroy(&fa);
142272343Sngie
143272343Sngie	ATF_REQUIRE(err == 0);
144272343Sngie
145272343Sngie	/* ok, wait for the child to finish */
146272343Sngie	waitpid(pid, &status, 0);
147272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
148272343Sngie
149272343Sngie	/* now check that output is twice as long as input */
150272343Sngie	insize = filesize(TESTFILE);
151272343Sngie	outsize = filesize(CHECKFILE);
152272343Sngie	ATF_REQUIRE(insize == strlen(TESTCONTENT));
153272343Sngie	ATF_REQUIRE(insize*2 == outsize);
154272343Sngie
155272343Sngie	/*
156272343Sngie	 * try a "cat < testfile  > checkfile" with input and output swapped
157272343Sngie	 */
158272343Sngie	make_testfile(TESTFILE);
159272343Sngie	empty_outfile(CHECKFILE);
160272343Sngie
161272343Sngie	posix_spawn_file_actions_init(&fa);
162272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
163272343Sngie	    TESTFILE, O_RDONLY, 0);
164272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
165272343Sngie	    CHECKFILE, O_WRONLY, 0);
166272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
167272343Sngie	posix_spawn_file_actions_destroy(&fa);
168272343Sngie
169272343Sngie	ATF_REQUIRE(err == 0);
170272343Sngie
171272343Sngie	/* ok, wait for the child to finish */
172272343Sngie	waitpid(pid, &status, 0);
173272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE);
174272343Sngie
175272343Sngie	/* now check that input and output are still the same size */
176272343Sngie	insize = filesize(TESTFILE);
177272343Sngie	outsize = filesize(CHECKFILE);
178272343Sngie	ATF_REQUIRE(insize == strlen(TESTCONTENT));
179272343Sngie	ATF_REQUIRE(outsize == 0);
180272343Sngie}
181272343Sngie
182272343SngieATF_TC(t_spawn_reopen);
183272343Sngie
184272343SngieATF_TC_HEAD(t_spawn_reopen, tc)
185272343Sngie{
186272343Sngie	atf_tc_set_md_var(tc, "descr",
187272343Sngie	    "an open filehandle can be replaced by a 'open' fileaction");
188272343Sngie	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
189272343Sngie}
190272343Sngie
191272343SngieATF_TC_BODY(t_spawn_reopen, tc)
192272343Sngie{
193272343Sngie	int status, err;
194272343Sngie	pid_t pid;
195272343Sngie	char * const args[2] = { __UNCONST("cat"), NULL };
196272343Sngie	posix_spawn_file_actions_t fa;
197272343Sngie
198272343Sngie	/*
199272343Sngie	 * make sure stdin is open in the parent
200272343Sngie	 */
201272343Sngie	freopen("/dev/zero", "r", stdin);
202272343Sngie	/*
203272343Sngie	 * now request an open for this fd again in the child
204272343Sngie	 */
205272343Sngie	posix_spawn_file_actions_init(&fa);
206272343Sngie	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
207272343Sngie	    "/dev/null", O_RDONLY, 0);
208272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
209272343Sngie	posix_spawn_file_actions_destroy(&fa);
210272343Sngie
211272343Sngie	ATF_REQUIRE(err == 0);
212272343Sngie
213272343Sngie	waitpid(pid, &status, 0);
214272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
215272343Sngie}
216272343Sngie
217272343SngieATF_TC(t_spawn_open_nonexistent);
218272343Sngie
219272343SngieATF_TC_HEAD(t_spawn_open_nonexistent, tc)
220272343Sngie{
221272343Sngie	atf_tc_set_md_var(tc, "descr",
222272343Sngie	    "posix_spawn fails when a file to open does not exist");
223272343Sngie	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
224272343Sngie}
225272343Sngie
226272343SngieATF_TC_BODY(t_spawn_open_nonexistent, tc)
227272343Sngie{
228272343Sngie	int err, status;
229272343Sngie	pid_t pid;
230272343Sngie	char * const args[2] = { __UNCONST("cat"), NULL };
231272343Sngie	posix_spawn_file_actions_t fa;
232272343Sngie
233272343Sngie	posix_spawn_file_actions_init(&fa);
234272343Sngie	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
235272343Sngie	    "./non/ex/ist/ent", O_RDONLY, 0);
236272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
237272343Sngie	if (err == 0) {
238272343Sngie		/*
239272343Sngie		 * The child has been created - it should fail and
240272343Sngie		 * return exit code 127
241272343Sngie		 */
242272343Sngie		waitpid(pid, &status, 0);
243272343Sngie		ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127);
244272343Sngie	} else {
245272343Sngie		/*
246272343Sngie		 * The error has been noticed early enough, no child has
247272343Sngie		 * been run
248272343Sngie		 */
249272343Sngie		ATF_REQUIRE(err == ENOENT);
250272343Sngie	}
251272343Sngie	posix_spawn_file_actions_destroy(&fa);
252272343Sngie}
253272343Sngie
254274626Sngie#ifdef __NetBSD__
255272343SngieATF_TC(t_spawn_open_nonexistent_diag);
256272343Sngie
257272343SngieATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc)
258272343Sngie{
259272343Sngie	atf_tc_set_md_var(tc, "descr",
260272343Sngie	    "posix_spawn fails when a file to open does not exist "
261272343Sngie	    "and delivers proper diagnostic");
262272343Sngie	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
263272343Sngie}
264272343Sngie
265272343SngieATF_TC_BODY(t_spawn_open_nonexistent_diag, tc)
266272343Sngie{
267272343Sngie	int err;
268272343Sngie	pid_t pid;
269272343Sngie	char * const args[2] = { __UNCONST("cat"), NULL };
270272343Sngie	posix_spawnattr_t attr;
271272343Sngie	posix_spawn_file_actions_t fa;
272272343Sngie
273272343Sngie	posix_spawnattr_init(&attr);
274272343Sngie	/*
275272343Sngie	 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
276272343Sngie	 * will cause a "proper" return value from posix_spawn(2)
277272343Sngie	 * instead of a (potential) success there and a 127 exit
278272343Sngie	 * status from the child process (c.f. the non-diag variant
279272343Sngie	 * of this test).
280272343Sngie	 */
281272343Sngie	posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR);
282272343Sngie	posix_spawn_file_actions_init(&fa);
283272343Sngie	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
284272343Sngie	    "./non/ex/ist/ent", O_RDONLY, 0);
285272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL);
286272343Sngie	ATF_REQUIRE(err == ENOENT);
287272343Sngie	posix_spawn_file_actions_destroy(&fa);
288272343Sngie	posix_spawnattr_destroy(&attr);
289272343Sngie}
290272914Sngie#endif
291272343Sngie
292272343SngieATF_TC(t_spawn_fileactions);
293272343Sngie
294272343SngieATF_TC_HEAD(t_spawn_fileactions, tc)
295272343Sngie{
296272343Sngie	atf_tc_set_md_var(tc, "descr",
297272343Sngie	    "Tests various complex fileactions");
298272343Sngie}
299272343Sngie
300272343SngieATF_TC_BODY(t_spawn_fileactions, tc)
301272343Sngie{
302272343Sngie	int fd1, fd2, fd3, status, err;
303272343Sngie	pid_t pid;
304272343Sngie	char * const args[2] = { __UNCONST("h_fileactions"), NULL };
305272343Sngie	char helper[FILENAME_MAX];
306272343Sngie	posix_spawn_file_actions_t fa;
307272343Sngie
308272343Sngie	posix_spawn_file_actions_init(&fa);
309272343Sngie
310272343Sngie	closefrom(fileno(stderr)+1);
311272343Sngie
312272343Sngie	fd1 = open("/dev/null", O_RDONLY);
313272343Sngie	ATF_REQUIRE(fd1 == 3);
314272343Sngie
315272343Sngie	fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC);
316272343Sngie	ATF_REQUIRE(fd2 == 4);
317272343Sngie
318272343Sngie	fd3 = open("/dev/null", O_WRONLY);
319272343Sngie	ATF_REQUIRE(fd3 == 5);
320272343Sngie
321272343Sngie	posix_spawn_file_actions_addclose(&fa, fd1);
322272343Sngie	posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0);
323272343Sngie	posix_spawn_file_actions_adddup2(&fa, 1, 7);
324272343Sngie
325272343Sngie	snprintf(helper, sizeof helper, "%s/h_fileactions",
326272343Sngie	    atf_tc_get_config_var(tc, "srcdir"));
327272343Sngie	err = posix_spawn(&pid, helper, &fa, NULL, args, NULL);
328272343Sngie	posix_spawn_file_actions_destroy(&fa);
329272343Sngie
330272343Sngie	ATF_REQUIRE(err == 0);
331272343Sngie
332272343Sngie	waitpid(pid, &status, 0);
333272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
334272343Sngie}
335272343Sngie
336272343SngieATF_TC(t_spawn_empty_fileactions);
337272343Sngie
338272343SngieATF_TC_HEAD(t_spawn_empty_fileactions, tc)
339272343Sngie{
340272343Sngie	atf_tc_set_md_var(tc, "descr",
341272343Sngie	    "posix_spawn with empty fileactions (PR kern/46038)");
342272343Sngie	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
343272343Sngie}
344272343Sngie
345272343SngieATF_TC_BODY(t_spawn_empty_fileactions, tc)
346272343Sngie{
347272343Sngie	int status, err;
348272343Sngie	pid_t pid;
349272343Sngie	char * const args[2] = { __UNCONST("cat"), NULL };
350272343Sngie	posix_spawn_file_actions_t fa;
351272343Sngie	size_t insize, outsize;
352272343Sngie
353272343Sngie	/*
354272343Sngie	 * try a "cat < testfile > checkfile", but set up stdin/stdout
355272343Sngie	 * already in the parent and pass empty file actions to the child.
356272343Sngie	 */
357272343Sngie	make_testfile(TESTFILE);
358272343Sngie	unlink(CHECKFILE);
359272343Sngie
360272343Sngie	freopen(TESTFILE, "r", stdin);
361272343Sngie	freopen(CHECKFILE, "w", stdout);
362272343Sngie
363272343Sngie	posix_spawn_file_actions_init(&fa);
364272343Sngie	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
365272343Sngie	posix_spawn_file_actions_destroy(&fa);
366272343Sngie
367272343Sngie	ATF_REQUIRE(err == 0);
368272343Sngie
369272343Sngie	/* ok, wait for the child to finish */
370272343Sngie	waitpid(pid, &status, 0);
371272343Sngie	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
372272343Sngie
373272343Sngie	/* now check that input and output have the same size */
374272343Sngie	insize = filesize(TESTFILE);
375272343Sngie	outsize = filesize(CHECKFILE);
376272343Sngie	ATF_REQUIRE(insize == strlen(TESTCONTENT));
377272343Sngie	ATF_REQUIRE(insize == outsize);
378272343Sngie}
379272343Sngie
380272343SngieATF_TP_ADD_TCS(tp)
381272343Sngie{
382272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_fileactions);
383272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent);
384274626Sngie#ifdef __NetBSD__
385272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag);
386272914Sngie#endif
387272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_reopen);
388272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_openmode);
389272343Sngie	ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions);
390272343Sngie
391272343Sngie	return atf_no_error();
392272343Sngie}
393