Home | History | Annotate | Line # | Download | only in posix_spawn
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