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 #ifdef __NetBSD__ 255 ATF_TC(t_spawn_open_nonexistent_diag); 256 257 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc) 258 { 259 atf_tc_set_md_var(tc, "descr", 260 "posix_spawn fails when a file to open does not exist " 261 "and delivers proper diagnostic"); 262 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 263 } 264 265 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc) 266 { 267 int err; 268 pid_t pid; 269 char * const args[2] = { __UNCONST("cat"), NULL }; 270 posix_spawnattr_t attr; 271 posix_spawn_file_actions_t fa; 272 273 posix_spawnattr_init(&attr); 274 /* 275 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 276 * will cause a "proper" return value from posix_spawn(2) 277 * instead of a (potential) success there and a 127 exit 278 * status from the child process (c.f. the non-diag variant 279 * of this test). 280 */ 281 posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR); 282 posix_spawn_file_actions_init(&fa); 283 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 284 "./non/ex/ist/ent", O_RDONLY, 0); 285 err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL); 286 ATF_REQUIRE(err == ENOENT); 287 posix_spawn_file_actions_destroy(&fa); 288 posix_spawnattr_destroy(&attr); 289 } 290 #endif 291 292 ATF_TC(t_spawn_fileactions); 293 294 ATF_TC_HEAD(t_spawn_fileactions, tc) 295 { 296 atf_tc_set_md_var(tc, "descr", 297 "Tests various complex fileactions"); 298 } 299 300 ATF_TC_BODY(t_spawn_fileactions, tc) 301 { 302 int fd1, fd2, fd3, status, err; 303 pid_t pid; 304 char *args[3] = { __UNCONST("h_fileactions"), NULL, NULL }; 305 int lowfd; 306 char lowfdstr[32]; 307 char helper[FILENAME_MAX]; 308 posix_spawn_file_actions_t fa; 309 310 posix_spawn_file_actions_init(&fa); 311 312 /* Note: this assumes no gaps in the fd table */ 313 lowfd = open("/", O_RDONLY); 314 ATF_REQUIRE(lowfd > 0); 315 ATF_REQUIRE_EQ(0, close(lowfd)); 316 snprintf(lowfdstr, sizeof(lowfdstr), "%d", lowfd); 317 args[1] = lowfdstr; 318 319 fd1 = open("/dev/null", O_RDONLY); 320 ATF_REQUIRE_EQ(fd1, lowfd); 321 322 fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC); 323 ATF_REQUIRE_EQ(fd2, lowfd + 1); 324 325 fd3 = open("/dev/null", O_WRONLY); 326 ATF_REQUIRE_EQ(fd3, lowfd + 2); 327 328 posix_spawn_file_actions_addclose(&fa, fd1); 329 posix_spawn_file_actions_addopen(&fa, lowfd + 3, "/dev/null", O_RDWR, 330 0); 331 posix_spawn_file_actions_adddup2(&fa, 1, lowfd + 4); 332 333 snprintf(helper, sizeof helper, "%s/h_fileactions", 334 atf_tc_get_config_var(tc, "srcdir")); 335 err = posix_spawn(&pid, helper, &fa, NULL, args, NULL); 336 posix_spawn_file_actions_destroy(&fa); 337 338 ATF_REQUIRE(err == 0); 339 340 waitpid(pid, &status, 0); 341 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 342 } 343 344 ATF_TC(t_spawn_empty_fileactions); 345 346 ATF_TC_HEAD(t_spawn_empty_fileactions, tc) 347 { 348 atf_tc_set_md_var(tc, "descr", 349 "posix_spawn with empty fileactions (PR kern/46038)"); 350 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 351 } 352 353 ATF_TC_BODY(t_spawn_empty_fileactions, tc) 354 { 355 int status, err; 356 pid_t pid; 357 char * const args[2] = { __UNCONST("cat"), NULL }; 358 posix_spawn_file_actions_t fa; 359 size_t insize, outsize; 360 361 /* 362 * try a "cat < testfile > checkfile", but set up stdin/stdout 363 * already in the parent and pass empty file actions to the child. 364 */ 365 make_testfile(TESTFILE); 366 unlink(CHECKFILE); 367 368 freopen(TESTFILE, "r", stdin); 369 freopen(CHECKFILE, "w", stdout); 370 371 posix_spawn_file_actions_init(&fa); 372 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 373 posix_spawn_file_actions_destroy(&fa); 374 375 ATF_REQUIRE(err == 0); 376 377 /* ok, wait for the child to finish */ 378 waitpid(pid, &status, 0); 379 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 380 381 /* now check that input and output have the same size */ 382 insize = filesize(TESTFILE); 383 outsize = filesize(CHECKFILE); 384 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 385 ATF_REQUIRE(insize == outsize); 386 } 387 388 static const char bin_pwd[] = "/bin/pwd"; 389 390 static void 391 t_spawn_chdir_impl(bool chdir) 392 { 393 int status, err, tmpdir_fd; 394 pid_t pid; 395 char * const args[2] = { __UNCONST("pwd"), NULL }; 396 posix_spawn_file_actions_t fa; 397 FILE *f; 398 char read_pwd[128]; 399 size_t ss; 400 static const char tmp_path[] = "/tmp"; 401 402 unlink(TESTFILE); 403 404 posix_spawn_file_actions_init(&fa); 405 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 406 TESTFILE, O_WRONLY | O_CREAT, 0600); 407 if (chdir) { 408 ATF_REQUIRE(posix_spawn_file_actions_addchdir_np(&fa, 409 tmp_path) == 0); 410 } else { 411 tmpdir_fd = open(tmp_path, O_DIRECTORY | O_RDONLY); 412 ATF_REQUIRE(tmpdir_fd > 0); 413 ATF_REQUIRE(posix_spawn_file_actions_addfchdir_np(&fa, 414 tmpdir_fd) == 0); 415 } 416 err = posix_spawn(&pid, bin_pwd, &fa, NULL, args, NULL); 417 posix_spawn_file_actions_destroy(&fa); 418 if (!chdir) 419 close(tmpdir_fd); 420 421 ATF_REQUIRE(err == 0); 422 waitpid(pid, &status, 0); 423 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 424 425 f = fopen(TESTFILE, "r"); 426 ATF_REQUIRE(f != NULL); 427 ss = fread(read_pwd, 1, sizeof(read_pwd), f); 428 fclose(f); 429 ATF_REQUIRE(ss == strlen(tmp_path) + 1); 430 ATF_REQUIRE(strncmp(read_pwd, tmp_path, strlen(tmp_path)) == 0); 431 } 432 433 ATF_TC(t_spawn_chdir); 434 435 ATF_TC_HEAD(t_spawn_chdir, tc) 436 { 437 atf_tc_set_md_var(tc, "descr", 438 "posix_spawn changes directory for the spawned program"); 439 atf_tc_set_md_var(tc, "require.progs", bin_pwd); 440 } 441 442 ATF_TC_BODY(t_spawn_chdir, tc) 443 { 444 t_spawn_chdir_impl(true); 445 } 446 447 ATF_TC(t_spawn_fchdir); 448 449 ATF_TC_HEAD(t_spawn_fchdir, tc) 450 { 451 atf_tc_set_md_var(tc, "descr", 452 "posix_spawn changes directory for the spawned program"); 453 atf_tc_set_md_var(tc, "require.progs", bin_pwd); 454 } 455 456 ATF_TC_BODY(t_spawn_fchdir, tc) 457 { 458 t_spawn_chdir_impl(false); 459 } 460 461 ATF_TP_ADD_TCS(tp) 462 { 463 ATF_TP_ADD_TC(tp, t_spawn_fileactions); 464 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 465 #ifdef __NetBSD__ 466 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag); 467 #endif 468 ATF_TP_ADD_TC(tp, t_spawn_reopen); 469 ATF_TP_ADD_TC(tp, t_spawn_openmode); 470 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 471 ATF_TP_ADD_TC(tp, t_spawn_chdir); 472 ATF_TP_ADD_TC(tp, t_spawn_fchdir); 473 474 return atf_no_error(); 475 } 476