1 /* $NetBSD: t_fileactions.c,v 1.5 2012/04/09 19:42:07 martin 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 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <spawn.h> 41 #include <unistd.h> 42 #include <sys/wait.h> 43 44 45 ATF_TC(t_spawn_openmode); 46 47 ATF_TC_HEAD(t_spawn_openmode, tc) 48 { 49 atf_tc_set_md_var(tc, "descr", 50 "Test the proper handling of 'mode' for 'open' fileactions"); 51 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 52 } 53 54 static off_t 55 filesize(const char * restrict fname) 56 { 57 struct stat st; 58 int err; 59 60 err = stat(fname, &st); 61 ATF_REQUIRE(err == 0); 62 return st.st_size; 63 } 64 65 #define TESTFILE "./the_input_data" 66 #define CHECKFILE "./the_output_data" 67 #define TESTCONTENT "marry has a little lamb" 68 69 static void 70 make_testfile(const char *restrict file) 71 { 72 FILE *f; 73 size_t written; 74 75 f = fopen(file, "w"); 76 ATF_REQUIRE(f != NULL); 77 written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f); 78 fclose(f); 79 ATF_REQUIRE(written == strlen(TESTCONTENT)); 80 } 81 82 static void 83 empty_outfile(const char *restrict filename) 84 { 85 FILE *f; 86 87 f = fopen(filename, "w"); 88 ATF_REQUIRE(f != NULL); 89 fclose(f); 90 } 91 92 ATF_TC_BODY(t_spawn_openmode, tc) 93 { 94 int status, err; 95 pid_t pid; 96 size_t insize, outsize; 97 char * const args[2] = { __UNCONST("cat"), NULL }; 98 posix_spawn_file_actions_t fa; 99 100 /* 101 * try a "cat < testfile > checkfile" 102 */ 103 make_testfile(TESTFILE); 104 unlink(CHECKFILE); 105 106 posix_spawn_file_actions_init(&fa); 107 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 108 TESTFILE, O_RDONLY, 0); 109 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 110 CHECKFILE, O_WRONLY|O_CREAT, 0600); 111 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 112 posix_spawn_file_actions_destroy(&fa); 113 114 ATF_REQUIRE(err == 0); 115 116 /* ok, wait for the child to finish */ 117 waitpid(pid, &status, 0); 118 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 119 120 /* now check that input and output have the same size */ 121 insize = filesize(TESTFILE); 122 outsize = filesize(CHECKFILE); 123 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 124 ATF_REQUIRE(insize == outsize); 125 126 /* 127 * try a "cat < testfile >> checkfile" 128 */ 129 make_testfile(TESTFILE); 130 make_testfile(CHECKFILE); 131 132 posix_spawn_file_actions_init(&fa); 133 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 134 TESTFILE, O_RDONLY, 0); 135 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 136 CHECKFILE, O_WRONLY|O_APPEND, 0); 137 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 138 posix_spawn_file_actions_destroy(&fa); 139 140 ATF_REQUIRE(err == 0); 141 142 /* ok, wait for the child to finish */ 143 waitpid(pid, &status, 0); 144 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 145 146 /* now check that output is twice as long as input */ 147 insize = filesize(TESTFILE); 148 outsize = filesize(CHECKFILE); 149 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 150 ATF_REQUIRE(insize*2 == outsize); 151 152 /* 153 * try a "cat < testfile > checkfile" with input and output swapped 154 */ 155 make_testfile(TESTFILE); 156 empty_outfile(CHECKFILE); 157 158 posix_spawn_file_actions_init(&fa); 159 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 160 TESTFILE, O_RDONLY, 0); 161 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 162 CHECKFILE, O_WRONLY, 0); 163 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 164 posix_spawn_file_actions_destroy(&fa); 165 166 ATF_REQUIRE(err == 0); 167 168 /* ok, wait for the child to finish */ 169 waitpid(pid, &status, 0); 170 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE); 171 172 /* now check that input and output are still the same size */ 173 insize = filesize(TESTFILE); 174 outsize = filesize(CHECKFILE); 175 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 176 ATF_REQUIRE(outsize == 0); 177 } 178 179 ATF_TC(t_spawn_reopen); 180 181 ATF_TC_HEAD(t_spawn_reopen, tc) 182 { 183 atf_tc_set_md_var(tc, "descr", 184 "an open filehandle can be replaced by a 'open' fileaction"); 185 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 186 } 187 188 ATF_TC_BODY(t_spawn_reopen, tc) 189 { 190 int status, err; 191 pid_t pid; 192 char * const args[2] = { __UNCONST("cat"), NULL }; 193 posix_spawn_file_actions_t fa; 194 195 /* 196 * make sure stdin is open in the parent 197 */ 198 freopen("/dev/zero", "r", stdin); 199 /* 200 * now request an open for this fd again in the child 201 */ 202 posix_spawn_file_actions_init(&fa); 203 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 204 "/dev/null", O_RDONLY, 0); 205 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 206 posix_spawn_file_actions_destroy(&fa); 207 208 ATF_REQUIRE(err == 0); 209 210 waitpid(pid, &status, 0); 211 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 212 } 213 214 ATF_TC(t_spawn_open_nonexistent); 215 216 ATF_TC_HEAD(t_spawn_open_nonexistent, tc) 217 { 218 atf_tc_set_md_var(tc, "descr", 219 "posix_spawn fails when a file to open does not exist"); 220 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 221 } 222 223 ATF_TC_BODY(t_spawn_open_nonexistent, tc) 224 { 225 int err, status; 226 pid_t pid; 227 char * const args[2] = { __UNCONST("cat"), NULL }; 228 posix_spawn_file_actions_t fa; 229 230 posix_spawn_file_actions_init(&fa); 231 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 232 "./non/ex/ist/ent", O_RDONLY, 0); 233 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 234 if (err == 0) { 235 /* 236 * The child has been created - it should fail and 237 * return exit code 127 238 */ 239 waitpid(pid, &status, 0); 240 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127); 241 } else { 242 /* 243 * The error has been noticed early enough, no child has 244 * been run 245 */ 246 ATF_REQUIRE(err == ENOENT); 247 } 248 posix_spawn_file_actions_destroy(&fa); 249 } 250 251 ATF_TC(t_spawn_open_nonexistent_diag); 252 253 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc) 254 { 255 atf_tc_set_md_var(tc, "descr", 256 "posix_spawn fails when a file to open does not exist " 257 "and delivers proper diagnostic"); 258 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 259 } 260 261 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc) 262 { 263 int err; 264 pid_t pid; 265 char * const args[2] = { __UNCONST("cat"), NULL }; 266 posix_spawnattr_t attr; 267 posix_spawn_file_actions_t fa; 268 269 posix_spawnattr_init(&attr); 270 /* 271 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 272 * will cause a "proper" return value from posix_spawn(2) 273 * instead of a (potential) success there and a 127 exit 274 * status from the child process (c.f. the non-diag variant 275 * of this test). 276 */ 277 posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR); 278 posix_spawn_file_actions_init(&fa); 279 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 280 "./non/ex/ist/ent", O_RDONLY, 0); 281 err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL); 282 ATF_REQUIRE(err == ENOENT); 283 posix_spawn_file_actions_destroy(&fa); 284 posix_spawnattr_destroy(&attr); 285 } 286 287 ATF_TC(t_spawn_fileactions); 288 289 ATF_TC_HEAD(t_spawn_fileactions, tc) 290 { 291 atf_tc_set_md_var(tc, "descr", 292 "Tests various complex fileactions"); 293 } 294 295 ATF_TC_BODY(t_spawn_fileactions, tc) 296 { 297 int fd1, fd2, fd3, status, err; 298 pid_t pid; 299 char * const args[2] = { __UNCONST("h_fileactions"), NULL }; 300 char helper[FILENAME_MAX]; 301 posix_spawn_file_actions_t fa; 302 303 posix_spawn_file_actions_init(&fa); 304 305 closefrom(fileno(stderr)+1); 306 307 fd1 = open("/dev/null", O_RDONLY); 308 ATF_REQUIRE(fd1 == 3); 309 310 fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC); 311 ATF_REQUIRE(fd2 == 4); 312 313 fd3 = open("/dev/null", O_WRONLY); 314 ATF_REQUIRE(fd3 == 5); 315 316 posix_spawn_file_actions_addclose(&fa, fd1); 317 posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0); 318 posix_spawn_file_actions_adddup2(&fa, 1, 7); 319 320 snprintf(helper, sizeof helper, "%s/h_fileactions", 321 atf_tc_get_config_var(tc, "srcdir")); 322 err = posix_spawn(&pid, helper, &fa, NULL, args, NULL); 323 posix_spawn_file_actions_destroy(&fa); 324 325 ATF_REQUIRE(err == 0); 326 327 waitpid(pid, &status, 0); 328 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 329 } 330 331 ATF_TC(t_spawn_empty_fileactions); 332 333 ATF_TC_HEAD(t_spawn_empty_fileactions, tc) 334 { 335 atf_tc_set_md_var(tc, "descr", 336 "posix_spawn with empty fileactions (PR kern/46038)"); 337 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 338 } 339 340 ATF_TC_BODY(t_spawn_empty_fileactions, tc) 341 { 342 int status, err; 343 pid_t pid; 344 char * const args[2] = { __UNCONST("cat"), NULL }; 345 posix_spawn_file_actions_t fa; 346 size_t insize, outsize; 347 348 /* 349 * try a "cat < testfile > checkfile", but set up stdin/stdout 350 * already in the parent and pass empty file actions to the child. 351 */ 352 make_testfile(TESTFILE); 353 unlink(CHECKFILE); 354 355 freopen(TESTFILE, "r", stdin); 356 freopen(CHECKFILE, "w", stdout); 357 358 posix_spawn_file_actions_init(&fa); 359 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 360 posix_spawn_file_actions_destroy(&fa); 361 362 ATF_REQUIRE(err == 0); 363 364 /* ok, wait for the child to finish */ 365 waitpid(pid, &status, 0); 366 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 367 368 /* now check that input and output have the same size */ 369 insize = filesize(TESTFILE); 370 outsize = filesize(CHECKFILE); 371 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 372 ATF_REQUIRE(insize == outsize); 373 } 374 375 ATF_TP_ADD_TCS(tp) 376 { 377 ATF_TP_ADD_TC(tp, t_spawn_fileactions); 378 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 379 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag); 380 ATF_TP_ADD_TC(tp, t_spawn_reopen); 381 ATF_TP_ADD_TC(tp, t_spawn_openmode); 382 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 383 384 return atf_no_error(); 385 } 386