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@NetBSD.org> and 9 * Martin Husemann <martin@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