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 48ATF_TC(t_spawn_openmode); 49 50ATF_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 57static off_t 58filesize(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 72static void 73make_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 85static void 86empty_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 95ATF_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 182ATF_TC(t_spawn_reopen); 183 184ATF_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 191ATF_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 217ATF_TC(t_spawn_open_nonexistent); 218 219ATF_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 226ATF_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__ 255ATF_TC(t_spawn_open_nonexistent_diag); 256 257ATF_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 265ATF_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 292ATF_TC(t_spawn_fileactions); 293 294ATF_TC_HEAD(t_spawn_fileactions, tc) 295{ 296 atf_tc_set_md_var(tc, "descr", 297 "Tests various complex fileactions"); 298} 299 300ATF_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 344ATF_TC(t_spawn_empty_fileactions); 345 346ATF_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 353ATF_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 388static const char bin_pwd[] = "/bin/pwd"; 389 390static void 391t_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 433ATF_TC(t_spawn_chdir); 434 435ATF_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 442ATF_TC_BODY(t_spawn_chdir, tc) 443{ 444 t_spawn_chdir_impl(true); 445} 446 447ATF_TC(t_spawn_fchdir); 448 449ATF_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 456ATF_TC_BODY(t_spawn_fchdir, tc) 457{ 458 t_spawn_chdir_impl(false); 459} 460 461ATF_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