Home | History | Annotate | Line # | Download | only in posix_spawn
t_fileactions.c revision 1.6
      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 (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 
     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 
     48 ATF_TC(t_spawn_openmode);
     49 
     50 ATF_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 
     57 static off_t
     58 filesize(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 
     72 static void
     73 make_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 
     85 static void
     86 empty_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 
     95 ATF_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 
    182 ATF_TC(t_spawn_reopen);
    183 
    184 ATF_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 
    191 ATF_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 
    217 ATF_TC(t_spawn_open_nonexistent);
    218 
    219 ATF_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 
    226 ATF_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 ATF_TC(t_spawn_open_nonexistent_diag);
    255 
    256 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc)
    257 {
    258 	atf_tc_set_md_var(tc, "descr",
    259 	    "posix_spawn fails when a file to open does not exist "
    260 	    "and delivers proper diagnostic");
    261 	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
    262 }
    263 
    264 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc)
    265 {
    266 	int err;
    267 	pid_t pid;
    268 	char * const args[2] = { __UNCONST("cat"), NULL };
    269 	posix_spawnattr_t attr;
    270 	posix_spawn_file_actions_t fa;
    271 
    272 	posix_spawnattr_init(&attr);
    273 	/*
    274 	 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
    275 	 * will cause a "proper" return value from posix_spawn(2)
    276 	 * instead of a (potential) success there and a 127 exit
    277 	 * status from the child process (c.f. the non-diag variant
    278 	 * of this test).
    279 	 */
    280 	posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR);
    281 	posix_spawn_file_actions_init(&fa);
    282 	posix_spawn_file_actions_addopen(&fa, STDIN_FILENO,
    283 	    "./non/ex/ist/ent", O_RDONLY, 0);
    284 	err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL);
    285 	ATF_REQUIRE(err == ENOENT);
    286 	posix_spawn_file_actions_destroy(&fa);
    287 	posix_spawnattr_destroy(&attr);
    288 }
    289 
    290 ATF_TC(t_spawn_fileactions);
    291 
    292 ATF_TC_HEAD(t_spawn_fileactions, tc)
    293 {
    294 	atf_tc_set_md_var(tc, "descr",
    295 	    "Tests various complex fileactions");
    296 }
    297 
    298 ATF_TC_BODY(t_spawn_fileactions, tc)
    299 {
    300 	int fd1, fd2, fd3, status, err;
    301 	pid_t pid;
    302 	char * const args[2] = { __UNCONST("h_fileactions"), NULL };
    303 	char helper[FILENAME_MAX];
    304 	posix_spawn_file_actions_t fa;
    305 
    306 	posix_spawn_file_actions_init(&fa);
    307 
    308 	closefrom(fileno(stderr)+1);
    309 
    310 	fd1 = open("/dev/null", O_RDONLY);
    311 	ATF_REQUIRE(fd1 == 3);
    312 
    313 	fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC);
    314 	ATF_REQUIRE(fd2 == 4);
    315 
    316 	fd3 = open("/dev/null", O_WRONLY);
    317 	ATF_REQUIRE(fd3 == 5);
    318 
    319 	posix_spawn_file_actions_addclose(&fa, fd1);
    320 	posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0);
    321 	posix_spawn_file_actions_adddup2(&fa, 1, 7);
    322 
    323 	snprintf(helper, sizeof helper, "%s/h_fileactions",
    324 	    atf_tc_get_config_var(tc, "srcdir"));
    325 	err = posix_spawn(&pid, helper, &fa, NULL, args, NULL);
    326 	posix_spawn_file_actions_destroy(&fa);
    327 
    328 	ATF_REQUIRE(err == 0);
    329 
    330 	waitpid(pid, &status, 0);
    331 	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
    332 }
    333 
    334 ATF_TC(t_spawn_empty_fileactions);
    335 
    336 ATF_TC_HEAD(t_spawn_empty_fileactions, tc)
    337 {
    338 	atf_tc_set_md_var(tc, "descr",
    339 	    "posix_spawn with empty fileactions (PR kern/46038)");
    340 	atf_tc_set_md_var(tc, "require.progs", "/bin/cat");
    341 }
    342 
    343 ATF_TC_BODY(t_spawn_empty_fileactions, tc)
    344 {
    345 	int status, err;
    346 	pid_t pid;
    347 	char * const args[2] = { __UNCONST("cat"), NULL };
    348 	posix_spawn_file_actions_t fa;
    349 	size_t insize, outsize;
    350 
    351 	/*
    352 	 * try a "cat < testfile > checkfile", but set up stdin/stdout
    353 	 * already in the parent and pass empty file actions to the child.
    354 	 */
    355 	make_testfile(TESTFILE);
    356 	unlink(CHECKFILE);
    357 
    358 	freopen(TESTFILE, "r", stdin);
    359 	freopen(CHECKFILE, "w", stdout);
    360 
    361 	posix_spawn_file_actions_init(&fa);
    362 	err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL);
    363 	posix_spawn_file_actions_destroy(&fa);
    364 
    365 	ATF_REQUIRE(err == 0);
    366 
    367 	/* ok, wait for the child to finish */
    368 	waitpid(pid, &status, 0);
    369 	ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
    370 
    371 	/* now check that input and output have the same size */
    372 	insize = filesize(TESTFILE);
    373 	outsize = filesize(CHECKFILE);
    374 	ATF_REQUIRE(insize == strlen(TESTCONTENT));
    375 	ATF_REQUIRE(insize == outsize);
    376 }
    377 
    378 ATF_TP_ADD_TCS(tp)
    379 {
    380 	ATF_TP_ADD_TC(tp, t_spawn_fileactions);
    381 	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent);
    382 	ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag);
    383 	ATF_TP_ADD_TC(tp, t_spawn_reopen);
    384 	ATF_TP_ADD_TC(tp, t_spawn_openmode);
    385 	ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions);
    386 
    387 	return atf_no_error();
    388 }
    389