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