t_fileactions.c revision 313535
11553Srgrimes/* $NetBSD: t_fileactions.c,v 1.6 2017/01/10 22:36:29 christos Exp $ */
21553Srgrimes
31553Srgrimes/*-
41553Srgrimes * Copyright (c) 2012 The NetBSD Foundation, Inc.
51553Srgrimes * All rights reserved.
61553Srgrimes *
71553Srgrimes * This code is derived from software contributed to The NetBSD Foundation
81553Srgrimes * by Charles Zhang <charles@NetBSD.org> and
91553Srgrimes * Martin Husemann <martin@NetBSD.org>.
101553Srgrimes *
111553Srgrimes * Redistribution and use in source and binary forms, with or without
121553Srgrimes * modification, are permitted provided that the following conditions
131553Srgrimes * are met:
141553Srgrimes * 1. Redistributions of source code must retain the above copyright
151553Srgrimes *    notice, this list of conditions and the following disclaimer.
161553Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
171553Srgrimes *    notice, this list of conditions and the following disclaimer in the
181553Srgrimes *    documentation and/or other materials provided with the distribution.
191553Srgrimes *
201553Srgrimes * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
211553Srgrimes * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
221553Srgrimes * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
231553Srgrimes * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
241553Srgrimes * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
251553Srgrimes * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
261553Srgrimes * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
271553Srgrimes * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
281553Srgrimes * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2950479Speter * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
301553Srgrimes * POSSIBILITY OF SUCH DAMAGE.
31169507Swkoszek */
321553Srgrimes
3379537Sru
341553Srgrimes#include <atf-c.h>
351553Srgrimes
361553Srgrimes#include <sys/wait.h>
371553Srgrimes#include <sys/stat.h>
3868965Sru
39169507Swkoszek#include <stdio.h>
4052653Smarcel#include <stdlib.h>
411566Srgrimes#include <string.h>
42169507Swkoszek#include <errno.h>
43169507Swkoszek#include <fcntl.h>
441553Srgrimes#include <spawn.h>
4599968Scharnier#include <unistd.h>
4669850Sben
4799968Scharnier
481553SrgrimesATF_TC(t_spawn_openmode);
491553Srgrimes
501553SrgrimesATF_TC_HEAD(t_spawn_openmode, tc)
511553Srgrimes{
521553Srgrimes	atf_tc_set_md_var(tc, "descr",
5329451Scharnier	    "Test the proper handling of 'mode' for 'open' fileactions");
541553Srgrimes	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
551553Srgrimes}
5629451Scharnier
571553Srgrimesstatic off_t
581553Srgrimesfilesize(const char * restrict fname)
5929451Scharnier{
601553Srgrimes	struct stat st;
616631Sjkh	int err;
6285536Sru
63148916Sobrien	err = stat(fname, &st);
64148916Sobrien	ATF_REQUIRE(err == 0);
65148916Sobrien	return st.st_size;
66148916Sobrien}
67169507Swkoszek
68169507Swkoszek#define TESTFILE	"./the_input_data"
69169507Swkoszek#define CHECKFILE	"./the_output_data"
70169507Swkoszek#define TESTCONTENT	"marry has a little lamb"
71169507Swkoszek
7252653Smarcelstatic void
7352653Smarcelmake_testfile(const char *restrict file)
7452653Smarcel{
7557673Ssheldonh	FILE *f;
7685536Sru	size_t written;
7785536Sru
7885536Sru	f = fopen(file, "w");
7952653Smarcel	ATF_REQUIRE(f != NULL);
8052653Smarcel	written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f);
81209969Snwhitehorn	fclose(f);
82209969Snwhitehorn	ATF_REQUIRE(written == strlen(TESTCONTENT));
83209969Snwhitehorn}
841566Srgrimes
8545579Sgrogstatic void
86169507Swkoszekempty_outfile(const char *restrict filename)
87169507Swkoszek{
88169507Swkoszek	FILE *f;
89233648Seadler
90169507Swkoszek	f = fopen(filename, "w");
91169507Swkoszek	ATF_REQUIRE(f != NULL);
921553Srgrimes	fclose(f);
9329451Scharnier}
941553Srgrimes
951553SrgrimesATF_TC_BODY(t_spawn_openmode, tc)
961553Srgrimes{
9713107Sbde	int status, err;
9813107Sbde	pid_t pid;
9913107Sbde	size_t insize, outsize;
10029451Scharnier	char * const args[2] = { __UNCONST("cat"), NULL };
10145427Sgrog	posix_spawn_file_actions_t fa;
1021553Srgrimes
10356481Scharnier	/*
1041553Srgrimes	 * try a "cat < testfile > checkfile"
1051553Srgrimes	 */
1061553Srgrimes	make_testfile(TESTFILE);
1071553Srgrimes	unlink(CHECKFILE);
10899968Scharnier
10969850Sben	posix_spawn_file_actions_init(&fa);
11099968Scharnier	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
1111553Srgrimes	    TESTFILE, O_RDONLY, 0);
1121553Srgrimes	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
11385536Sru	    CHECKFILE, O_WRONLY|O_CREAT, 0600);
11445579Sgrog	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
11585536Sru	posix_spawn_file_actions_destroy(&fa);
11668716Sru
11768716Sru	ATF_REQUIRE(err == 0);
11899968Scharnier
11969850Sben	/* ok, wait for the child to finish */
12099968Scharnier	waitpid(pid, &status, 0);
12185536Sru	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
12252653Smarcel
12352653Smarcel	/* now check that input and output have the same size */
12479755Sdd	insize = filesize(TESTFILE);
12552653Smarcel	outsize = filesize(CHECKFILE);
1261553Srgrimes	ATF_REQUIRE(insize == strlen(TESTCONTENT));
12729451Scharnier	ATF_REQUIRE(insize == outsize);
1281553Srgrimes
1291566Srgrimes	/*
1301553Srgrimes	 * try a "cat < testfile >> checkfile"
1311553Srgrimes	 */
1321553Srgrimes	make_testfile(TESTFILE);
1331553Srgrimes	make_testfile(CHECKFILE);
1341553Srgrimes
1351553Srgrimes	posix_spawn_file_actions_init(&fa);
1361553Srgrimes	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
13755605Speter	    TESTFILE, O_RDONLY, 0);
1381553Srgrimes	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
1391553Srgrimes	    CHECKFILE, O_WRONLY|O_APPEND, 0);
140101828Sru	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
1411553Srgrimes	posix_spawn_file_actions_destroy(&fa);
1421553Srgrimes
1431553Srgrimes	ATF_REQUIRE(err == 0);
1441553Srgrimes
14599968Scharnier	/* ok, wait for the child to finish */
14669850Sben	waitpid(pid, &status, 0);
14799968Scharnier	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
1481553Srgrimes
1491553Srgrimes	/* now check that output is twice as long as input */
150101828Sru	insize = filesize(TESTFILE);
1511553Srgrimes	outsize = filesize(CHECKFILE);
15229451Scharnier	ATF_REQUIRE(insize == strlen(TESTCONTENT));
1531553Srgrimes	ATF_REQUIRE(insize*2 == outsize);
1541553Srgrimes
1551553Srgrimes	/*
15645427Sgrog	 * try a "cat < testfile  > checkfile" with input and output swapped
15781622Sru	 */
15881622Sru	make_testfile(TESTFILE);
159113749Sbrueffer	empty_outfile(CHECKFILE);
16045579Sgrog
16168575Sru	posix_spawn_file_actions_init(&fa);
16285536Sru	posix_spawn_file_actions_addopen(&fa, fileno(stdout),
16385536Sru	    TESTFILE, O_RDONLY, 0);
16485536Sru	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
16585536Sru	    CHECKFILE, O_WRONLY, 0);
16681622Sru	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
16781622Sru	posix_spawn_file_actions_destroy(&fa);
16881622Sru
16979755Sdd	ATF_REQUIRE(err == 0);
17050000Schris
17145579Sgrog	/* ok, wait for the child to finish */
17245579Sgrog	waitpid(pid, &status, 0);
17345427Sgrog	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE);
17445579Sgrog
17568716Sru	/* now check that input and output are still the same size */
17668716Sru	insize = filesize(TESTFILE);
17768716Sru	outsize = filesize(CHECKFILE);
17845579Sgrog	ATF_REQUIRE(insize == strlen(TESTCONTENT));
17985536Sru	ATF_REQUIRE(outsize == 0);
18085536Sru}
18185536Sru
18285536SruATF_TC(t_spawn_reopen);
18385536Sru
18485536SruATF_TC_HEAD(t_spawn_reopen, tc)
18545579Sgrog{
18685536Sru	atf_tc_set_md_var(tc, "descr",
18785536Sru	    "an open filehandle can be replaced by a 'open' fileaction");
18845579Sgrog	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
18945579Sgrog}
19045579Sgrog
19145579SgrogATF_TC_BODY(t_spawn_reopen, tc)
19285536Sru{
19345579Sgrog	int status, err;
19445579Sgrog	pid_t pid;
19585536Sru	char * const args[2] = { __UNCONST("cat"), NULL };
19685536Sru	posix_spawn_file_actions_t fa;
19785536Sru
19845579Sgrog	/*
19945579Sgrog	 * make sure stdin is open in the parent
20045427Sgrog	 */
20145427Sgrog	freopen("/dev/zero", "r", stdin);
20285536Sru	/*
20385536Sru	 * now request an open for this fd again in the child
20445427Sgrog	 */
20545427Sgrog	posix_spawn_file_actions_init(&fa);
20645427Sgrog	posix_spawn_file_actions_addopen(&fa, fileno(stdin),
20785536Sru	    "/dev/null", O_RDONLY, 0);
20845579Sgrog	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
20985536Sru	posix_spawn_file_actions_destroy(&fa);
210107788Sru
21145427Sgrog	ATF_REQUIRE(err == 0);
21285536Sru
21345579Sgrog	waitpid(pid, &status, 0);
21485536Sru	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
215107788Sru}
21645427Sgrog
2171553SrgrimesATF_TC(t_spawn_open_nonexistent);
21885536Sru
2191553SrgrimesATF_TC_HEAD(t_spawn_open_nonexistent, tc)
2201553Srgrimes{
22185536Sru	atf_tc_set_md_var(tc, "descr",
22285536Sru	    "posix_spawn fails when a file to open does not exist");
22385536Sru	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
22485536Sru}
2251553Srgrimes
22685536SruATF_TC_BODY(t_spawn_open_nonexistent, tc)
2271553Srgrimes{
22885536Sru	int err, status;
22952653Smarcel	pid_t pid;
23085536Sru	char * const args[2] = { __UNCONST("cat"), NULL };
23179236Simp	posix_spawn_file_actions_t fa;
23285536Sru
2331553Srgrimes	posix_spawn_file_actions_init(&fa);
2341553Srgrimes	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
235118564Ssimon	    "./non/ex/ist/ent", O_RDONLY, 0);
236118564Ssimon	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
23756481Scharnier	if (err == 0) {
23856481Scharnier		/*
23956481Scharnier		 * The child has been created - it should fail and
2401553Srgrimes		 * return exit code 127
2411553Srgrimes		 */
2421553Srgrimes		waitpid(pid, &status, 0);
2431553Srgrimes		ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127);
2441553Srgrimes	} else {
2451553Srgrimes		/*
24699968Scharnier		 * The error has been noticed early enough, no child has
2471553Srgrimes		 * been run
248169507Swkoszek		 */
249233648Seadler		ATF_REQUIRE(err == ENOENT);
250169507Swkoszek	}
251169507Swkoszek	posix_spawn_file_actions_destroy(&fa);
252169507Swkoszek}
253169507Swkoszek
254169507Swkoszek#ifdef __NetBSD__
255169507SwkoszekATF_TC(t_spawn_open_nonexistent_diag);
256169507Swkoszek
257169507SwkoszekATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc)
258233648Seadler{
259169507Swkoszek	atf_tc_set_md_var(tc, "descr",
260169507Swkoszek	    "posix_spawn fails when a file to open does not exist "
261169507Swkoszek	    "and delivers proper diagnostic");
262140442Sru	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
263140442Sru}
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