1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2025 Oxide Computer Company 14 */ 15 16 /* 17 * Various tests for posix_spawn(3C). Currently this mostly focuses on 18 * functionality added in POSIX 2024 which relates to SETSID and changing 19 * directories. 20 */ 21 22 #include <err.h> 23 #include <stdlib.h> 24 #include <spawn.h> 25 #include <stdio.h> 26 #include <stdbool.h> 27 #include <unistd.h> 28 #include <sys/sysmacros.h> 29 #include <sys/debug.h> 30 #include <wait.h> 31 #include <string.h> 32 #include <fcntl.h> 33 #include <limits.h> 34 #include <errno.h> 35 #include <libgen.h> 36 #include <inttypes.h> 37 38 /* 39 * This isn't const so we can refer to it in the argv arrays. 40 */ 41 static char *spawn_pwd = "/usr/bin/pwd"; 42 static char spawn_getsid[PATH_MAX]; 43 44 /* 45 * This is an arbitrary fd that we believe will be okay to use in fchdir tests 46 * to overwrite. 47 */ 48 #define SPAWN_FD 23 49 50 typedef struct spawn_dir_test { 51 const char *sdt_desc; 52 bool sdt_pass; 53 const char *sdt_pwd; 54 const char *sdt_dirs[16]; 55 } spawn_dir_test_t; 56 57 static const spawn_dir_test_t spawn_dir_tests[] = { 58 { 59 .sdt_desc = "no chdir", 60 .sdt_pass = true, 61 .sdt_pwd = "/var/tmp" 62 }, { 63 .sdt_desc = "absolute path: /etc", 64 .sdt_pass = true, 65 .sdt_pwd = "/etc", 66 .sdt_dirs = { "/etc" } 67 }, { 68 .sdt_desc = "multiple absolute paths (1)", 69 .sdt_pass = true, 70 .sdt_pwd = "/dev/net", 71 .sdt_dirs = { "/etc", "/dev/net" } 72 }, { 73 .sdt_desc = "multiple absolute paths (2)", 74 .sdt_pass = true, 75 .sdt_pwd = "/var/svc", 76 .sdt_dirs = { "/proc/self", "/var/svc" } 77 }, { 78 .sdt_desc = "single relative path (1)", 79 .sdt_pass = true, 80 .sdt_pwd = "/var/tmp", 81 .sdt_dirs = { "." }, 82 }, { 83 .sdt_desc = "single relative path (2)", 84 .sdt_pass = true, 85 .sdt_pwd = "/var", 86 .sdt_dirs = { ".." }, 87 }, { 88 .sdt_desc = "multiple relative paths (1)", 89 .sdt_pass = true, 90 .sdt_pwd = "/usr/lib/dtrace", 91 .sdt_dirs = { "..", "..", "usr", "lib", "dtrace" }, 92 }, { 93 .sdt_desc = "multiple relative paths (2)", 94 .sdt_pass = true, 95 .sdt_pwd = "/var/tmp", 96 .sdt_dirs = { "..", "tmp" }, 97 }, { 98 .sdt_desc = "mixing absolute and relative paths (1)", 99 .sdt_pass = true, 100 .sdt_pwd = "/usr/lib/fm/fmd", 101 .sdt_dirs = { "..", "/usr/lib/fm", "fmd" }, 102 }, { 103 .sdt_desc = "mixing absolute and relative paths (2)", 104 .sdt_pass = true, 105 .sdt_pwd = "/usr/bin", 106 .sdt_dirs = { "/usr/lib/64", "..", "..", "bin" }, 107 }, { 108 .sdt_desc = "mixing absolute and relative paths (3)", 109 .sdt_pass = true, 110 .sdt_pwd = "/etc/svc/volatile", 111 .sdt_dirs = { "/usr/lib/64", "..", "..", "bin", 112 "/etc/svc/volatile" }, 113 }, { 114 /* 115 * Note, these bad path tests will not be terribly meaningful 116 * for fchdir because the open will fail. 117 */ 118 .sdt_desc = "bad path 1", 119 .sdt_pass = false, 120 .sdt_dirs = { "/#error//?*!@#$!asdf/please/don't/exist" } 121 }, { 122 .sdt_desc = "bad path 2", 123 .sdt_pass = false, 124 .sdt_dirs = { "/tmp", "\x001\x002\x003\x004\x003\x042" } 125 } 126 }; 127 128 typedef struct spawn_flags_test { 129 const char *sft_desc; 130 int sft_ret; 131 short sft_flags; 132 } spawn_flags_test_t; 133 134 static const spawn_flags_test_t spawn_flags_tests[] = { 135 { 136 .sft_desc = "no flags", 137 .sft_ret = 0, 138 .sft_flags = 0 139 }, { 140 .sft_desc = "flag SETPGROUP", 141 .sft_ret = 0, 142 .sft_flags = POSIX_SPAWN_SETPGROUP 143 }, { 144 .sft_desc = "flag SETSID", 145 .sft_ret = 0, 146 .sft_flags = POSIX_SPAWN_SETSID 147 }, { 148 .sft_desc = "flags SETSID | SETPGROUP", 149 .sft_ret = EPERM, 150 .sft_flags = POSIX_SPAWN_SETSID | POSIX_SPAWN_SETPGROUP 151 } 152 }; 153 154 /* 155 * Add standard actions to capture stdout but nothing else. 156 */ 157 static void 158 posix_spawn_setup_fds(posix_spawn_file_actions_t *acts, int pipes[2]) 159 { 160 int ret; 161 162 if (pipe2(pipes, O_NONBLOCK) != 0) { 163 err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to create a " 164 "pipe"); 165 } 166 167 VERIFY3S(pipes[0], >, STDERR_FILENO); 168 VERIFY3S(pipes[1], >, STDERR_FILENO); 169 170 if ((ret = posix_spawn_file_actions_init(acts)) != 0) { 171 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to " 172 "initialize posix_spawn file actions"); 173 } 174 175 if ((ret = posix_spawn_file_actions_addopen(acts, STDIN_FILENO, 176 "/dev/null", O_RDONLY, 0)) != 0) { 177 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add " 178 "/dev/null open action"); 179 } 180 181 if ((ret = posix_spawn_file_actions_adddup2(acts, STDIN_FILENO, 182 STDERR_FILENO)) != 0) { 183 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add " 184 "stderr dup action"); 185 } 186 187 if ((ret = posix_spawn_file_actions_adddup2(acts, pipes[1], 188 STDOUT_FILENO)) != 0) { 189 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add " 190 "stdout dup action"); 191 } 192 193 if ((ret = posix_spawn_file_actions_addclose(acts, pipes[0])) != 0) { 194 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add " 195 "pipes[0] close action"); 196 } 197 198 if ((ret = posix_spawn_file_actions_addclose(acts, pipes[1])) != 0) { 199 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add " 200 "pipes[1] close action"); 201 } 202 } 203 204 static bool 205 posix_spawn_test_one_dir(const spawn_dir_test_t *test, int pipes[2], 206 posix_spawn_file_actions_t *acts, const char *desc) 207 { 208 int ret; 209 bool bret = false; 210 char *const argv[2] = { spawn_pwd, NULL }; 211 char *const envp[1] = { NULL }; 212 pid_t pid; 213 siginfo_t sig; 214 char pwd[PATH_MAX]; 215 ssize_t pwd_len; 216 217 if ((ret = posix_spawn(&pid, spawn_pwd, acts, NULL, argv, envp)) != 0) { 218 if (!test->sdt_pass) { 219 (void) printf("TEST PASSED: %s (%s): posix_spawn " 220 "failed as expected\n", test->sdt_desc, desc); 221 bret = true; 222 goto out; 223 } else { 224 warnx("TEST FAILED: %s posix_spawn() failed with %s, " 225 "but expected success", test->sdt_desc, 226 strerrorname_np(ret)); 227 goto out; 228 } 229 } 230 231 if (waitid(P_PID, pid, &sig, WEXITED) != 0) { 232 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on " 233 "pid %" _PRIdID ", but posix_spawn executed it", 234 test->sdt_desc, pid); 235 } 236 237 if (sig.si_code != CLD_EXITED) { 238 warnx("TEST FAILED: %s: child did not successfully exit: " 239 "foud si_code: %d", test->sdt_desc, sig.si_code); 240 goto out; 241 } 242 243 if (sig.si_status != 0) { 244 if (!test->sdt_pass) { 245 (void) printf("TEST PASSED: %s (%s): child process " 246 "failed", test->sdt_desc, desc); 247 bret = true; 248 goto out; 249 } 250 251 warnx("TEST FAILED: %s: child exited with status %d, expected " 252 "success", test->sdt_desc, sig.si_status); 253 goto out; 254 } else if (!test->sdt_pass) { 255 warnx("TEST FAILED: %s: child exited successfully, but " 256 "expected failure", test->sdt_desc); 257 goto out; 258 } 259 260 /* 261 * At this point we know that we have a pwd process that has 262 * successfully exited. We should be able to perform a non-blocking read 263 * from the pipe successfully and get its working directory. pwd(1) 264 * appends a new line. We remove it. 265 */ 266 pwd[0] = 0; 267 pwd_len = read(pipes[0], pwd, sizeof (pwd)); 268 if (pwd_len < 0) { 269 warn("TEST FAILED: %s: failed to read pwd from pipe", 270 test->sdt_desc); 271 goto out; 272 } else if (pwd_len == 0) { 273 warn("TEST FAILED: %s: got zero byte read from pipe?!", 274 test->sdt_desc); 275 goto out; 276 } 277 pwd[pwd_len - 1] = '\0'; 278 279 if (strcmp(pwd, test->sdt_pwd) != 0) { 280 warnx("TEST FAILED: %s: found pwd '%s', expected '%s'", 281 test->sdt_desc, pwd, test->sdt_pwd); 282 goto out; 283 } 284 285 (void) printf("TEST PASSED: %s (%s)\n", test->sdt_desc, desc); 286 287 bret = true; 288 out: 289 return (bret); 290 } 291 292 static bool 293 posix_spawn_test_one_chdir(const spawn_dir_test_t *test) 294 { 295 int ret, pipes[2]; 296 bool bret = false; 297 posix_spawn_file_actions_t acts; 298 299 /* 300 * We set up a pipe to act as stdout so we can capture the output from 301 * pwd. While we could use /proc to try and do this, we prefer this 302 * mechanism. 303 */ 304 posix_spawn_setup_fds(&acts, pipes); 305 306 for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) { 307 if (test->sdt_dirs[i] == NULL) 308 break; 309 310 ret = posix_spawn_file_actions_addchdir(&acts, 311 test->sdt_dirs[i]); 312 if (ret != 0) { 313 warnc(ret, "TEST FAILED: %s: adding path '%s' " 314 "(%zu) failed unexpectedly", test->sdt_desc, 315 test->sdt_dirs[i], i); 316 goto out; 317 } 318 } 319 320 bret = posix_spawn_test_one_dir(test, pipes, &acts, "chdir"); 321 out: 322 VERIFY0(posix_spawn_file_actions_destroy(&acts)); 323 VERIFY0(close(pipes[1])); 324 VERIFY0(close(pipes[0])); 325 return (bret); 326 } 327 328 static bool 329 posix_spawn_test_one_fchdir(const spawn_dir_test_t *test) 330 { 331 int ret, pipes[2]; 332 bool bret = false; 333 posix_spawn_file_actions_t acts; 334 335 /* 336 * We set up a pipe to act as stdout so we can capture the output from 337 * pwd. While we could use /proc to try and do this, we prefer this 338 * mechanism. 339 */ 340 posix_spawn_setup_fds(&acts, pipes); 341 342 /* 343 * For the fchdir tests we go in a loop over these directories opening 344 * an fd, doing an fchdir to it, and then closing it. 345 */ 346 for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) { 347 if (test->sdt_dirs[i] == NULL) 348 break; 349 350 ret = posix_spawn_file_actions_addopen(&acts, SPAWN_FD, 351 test->sdt_dirs[i], O_RDONLY | O_DIRECTORY, 0); 352 if (ret != 0) { 353 warnc(ret, "TEST FAILED: %s: adding open action for " 354 "path '%s' (%zu) failed unexpectedly", 355 test->sdt_desc, test->sdt_dirs[i], i); 356 goto out; 357 } 358 359 ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD); 360 if (ret != 0) { 361 warnc(ret, "TEST FAILED: %s: adding fchdir action for " 362 "path '%s' (%zu) failed unexpectedly", 363 test->sdt_desc, test->sdt_dirs[i], i); 364 goto out; 365 } 366 367 ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD); 368 if (ret != 0) { 369 warnc(ret, "TEST FAILED: %s: adding close action for " 370 "path '%s' (%zu) failed unexpectedly", 371 test->sdt_desc, test->sdt_dirs[i], i); 372 goto out; 373 } 374 } 375 376 bret = posix_spawn_test_one_dir(test, pipes, &acts, "fchdir"); 377 out: 378 VERIFY0(posix_spawn_file_actions_destroy(&acts)); 379 VERIFY0(close(pipes[1])); 380 VERIFY0(close(pipes[0])); 381 return (bret); 382 } 383 384 /* 385 * Test a few different bad file actions. 386 */ 387 static bool 388 posix_spawn_test_bad_actions(void) 389 { 390 int ret; 391 bool bret = true; 392 posix_spawn_file_actions_t acts; 393 394 if ((ret = posix_spawn_file_actions_init(&acts)) != 0) { 395 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to " 396 "initialize posix_spawn file actions"); 397 } 398 399 if ((ret = posix_spawn_file_actions_addfchdir(&acts, -23)) == 0) { 400 warnx("TEST FAILED: addfchdir() with bad fd: expected EBADF, " 401 "but returned successfully"); 402 bret = false; 403 } else if (ret != EBADF) { 404 warnx("TEST FAILED: addfchdir with bad fd: failed with %s, " 405 "but expected EBADF", strerrorname_np(ret)); 406 bret = false; 407 } else { 408 (void) printf("TEST PASSED: addfchdir() with bad fd: correctly " 409 "got EBADF\n"); 410 } 411 412 if ((ret = posix_spawn_file_actions_addopen(&acts, -23, "/dev/null", 413 O_RDONLY, 0)) == 0) { 414 warnx("TEST FAILED: addopen() with bad fd: expected EBADF, " 415 "but returned successfully"); 416 bret = false; 417 } else if (ret != EBADF) { 418 warnx("TEST FAILED: addopen with bad fd: failed with %s, " 419 "but expected EBADF", strerrorname_np(ret)); 420 bret = false; 421 } else { 422 (void) printf("TEST PASSED: addopen() with bad fd: correctly " 423 "got EBADF\n"); 424 } 425 426 if ((ret = posix_spawn_file_actions_addclose(&acts, -23)) == 0) { 427 warnx("TEST FAILED: addclose() with bad fd: expected EBADF, " 428 "but returned successfully"); 429 bret = false; 430 } else if (ret != EBADF) { 431 warnx("TEST FAILED: addclose with bad fd: failed with %s, " 432 "but expected EBADF", strerrorname_np(ret)); 433 bret = false; 434 } else { 435 (void) printf("TEST PASSED: addclose() with bad fd: correctly " 436 "got EBADF\n"); 437 } 438 439 VERIFY0(posix_spawn_file_actions_destroy(&acts)); 440 return (bret); 441 } 442 443 /* 444 * Verify that if we try to do an fchdir to an invalid fd that everything fails. 445 */ 446 static bool 447 posix_spawn_test_bad_fchdir(void) 448 { 449 int ret, pipes[2]; 450 bool bret = false; 451 posix_spawn_file_actions_t acts; 452 spawn_dir_test_t test; 453 454 (void) memset(&test, 0, sizeof (test)); 455 test.sdt_desc = "fchdir to closed fd"; 456 test.sdt_pass = false; 457 test.sdt_pwd = "/nope"; 458 459 /* 460 * We set up a pipe to act as stdout so we can capture the output from 461 * pwd. While we could use /proc to try and do this, we prefer this 462 * mechanism. 463 */ 464 posix_spawn_setup_fds(&acts, pipes); 465 466 ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD); 467 if (ret != 0) { 468 warnc(ret, "TEST FAILED: %s: adding close action failed " 469 "unexpectedly", test.sdt_desc); 470 goto out; 471 } 472 473 ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD); 474 if (ret != 0) { 475 warnc(ret, "TEST FAILED: %s: adding close action failed " 476 "unexpectedly", test.sdt_desc); 477 goto out; 478 } 479 480 bret = posix_spawn_test_one_dir(&test, pipes, &acts, "fchdir"); 481 out: 482 VERIFY0(posix_spawn_file_actions_destroy(&acts)); 483 VERIFY0(close(pipes[1])); 484 VERIFY0(close(pipes[0])); 485 return (bret); 486 } 487 488 489 static bool 490 posix_spawn_test_one_flags(const spawn_flags_test_t *test) 491 { 492 int ret, pipes[2]; 493 bool bret = true; 494 char *const argv[2] = { spawn_getsid, NULL }; 495 char *const envp[1] = { NULL }; 496 pid_t buf[2]; 497 posix_spawn_file_actions_t acts; 498 posix_spawnattr_t attr; 499 short flags; 500 pid_t pid, exp_sid, exp_pgid; 501 siginfo_t sig; 502 ssize_t buf_len; 503 const char *sid_desc, *pgid_desc; 504 505 posix_spawn_setup_fds(&acts, pipes); 506 507 if ((ret = posix_spawnattr_init(&attr)) != 0) { 508 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to " 509 "initialize posix_spawn attributes"); 510 } 511 512 VERIFY0(posix_spawnattr_getflags(&attr, &flags)); 513 if (flags != 0) { 514 warnx("TEST FAILED: %s: initial flags are not zero, found 0x%x", 515 test->sft_desc, flags); 516 bret = false; 517 } 518 VERIFY0(posix_spawnattr_setflags(&attr, test->sft_flags)); 519 VERIFY0(posix_spawnattr_getflags(&attr, &flags)); 520 if (flags != test->sft_flags) { 521 warnx("TEST FAILED: %s: flags are don't match what we set: " 522 "found 0x%x, expected 0x%x", test->sft_desc, flags, 523 test->sft_flags); 524 bret = false; 525 } 526 527 ret = posix_spawn(&pid, spawn_getsid, &acts, &attr, argv, envp); 528 if (ret != test->sft_ret) { 529 if (test->sft_ret == 0) { 530 warnx("TEST FAILED: %s posix_spawn() failed with %s, " 531 "but expected success", test->sft_desc, 532 strerrorname_np(ret)); 533 } else { 534 warnx("TEST FAILED: %s posix_spawn() failed with %s, " 535 "but expected %s", test->sft_desc, 536 strerrorname_np(ret), 537 strerrorname_np(test->sft_ret)); 538 } 539 bret = false; 540 goto out; 541 } 542 543 if (test->sft_ret != 0) { 544 (void) printf("TEST PASSED: %s: posix_spawn() failed correctly " 545 "with %s\n", test->sft_desc, strerrorname_np(ret)); 546 goto out; 547 } 548 549 if (waitid(P_PID, pid, &sig, WEXITED) != 0) { 550 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on " 551 "pid %" _PRIdID ", but posix_spawn executed it", 552 test->sft_desc, pid); 553 } 554 555 if (sig.si_code != CLD_EXITED) { 556 warnx("TEST FAILED: %s: child did not successfully exit: " 557 "foud si_code: %d", test->sft_desc, sig.si_code); 558 bret = false; 559 goto out; 560 } 561 562 if (sig.si_status != 0) { 563 warnx("TEST FAILED: %s: child exited with status %d, expected " 564 "success", test->sft_desc, sig.si_status); 565 bret = false; 566 goto out; 567 } 568 569 /* 570 * The getsid.64 process writes as binary data the results of getsid(2) 571 * and getpgid(2) to our pipe. We should be able to read all of this in 572 * one swoop. 573 */ 574 buf_len = read(pipes[0], buf, sizeof (buf)); 575 if (buf_len < 0) { 576 warn("TEST FAILED: %s: failed to read IDs from pipe", 577 test->sft_desc); 578 bret = false; 579 goto out; 580 } else if (buf_len == 0) { 581 warn("TEST FAILED: %s: got zero byte read from pipe?!", 582 test->sft_desc); 583 bret = false; 584 goto out; 585 } 586 587 /* 588 * Now we need to check the various process group and session IDs. We 589 * expect the following values: 590 * 591 * If the SETSID flag was set then the session ID should match the 592 * child's pid. Otherwise it should match our value. 593 * 594 * If the SETSID or SETPGROUP flag was set then the process group ID 595 * should match the child's pid. Otherwise it should match our value. 596 */ 597 if ((test->sft_flags & POSIX_SPAWN_SETSID) != 0) { 598 exp_sid = pid; 599 sid_desc = "child's ID"; 600 } else { 601 exp_sid = getsid(0); 602 sid_desc = "test's SID"; 603 } 604 605 if ((test->sft_flags & (POSIX_SPAWN_SETSID | 606 POSIX_SPAWN_SETPGROUP)) != 0) { 607 exp_pgid = pid; 608 pgid_desc = "child's ID"; 609 } else { 610 exp_pgid = getpgid(0); 611 pgid_desc = "test's PGID"; 612 } 613 614 if (buf[0] != exp_sid) { 615 warnx("TEST FAILED: %s: session ID mismatch: expected 0x%" 616 _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc, exp_sid, 617 sid_desc, buf[0]); 618 bret = false; 619 } 620 621 if (buf[1] != exp_pgid) { 622 warnx("TEST FAILED: %s: process group ID mismatch: expected " 623 "0x%" _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc, 624 exp_pgid, pgid_desc, buf[1]); 625 bret = false; 626 } 627 628 if (bret) { 629 (void) printf("TEST PASSED: %s\n", test->sft_desc); 630 } 631 632 out: 633 VERIFY0(posix_spawnattr_destroy(&attr)); 634 VERIFY0(posix_spawn_file_actions_destroy(&acts)); 635 VERIFY0(close(pipes[1])); 636 VERIFY0(close(pipes[0])); 637 return (bret); 638 } 639 640 /* 641 * Set up paths that are dependent on where our binary is found. 642 */ 643 static void 644 posix_spawn_test_paths(void) 645 { 646 ssize_t ret; 647 char origin[PATH_MAX]; 648 649 ret = readlink("/proc/self/path/a.out", origin, PATH_MAX - 1); 650 if (ret < 0) { 651 err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to read " 652 "a.out path"); 653 } 654 655 origin[ret] = '\0'; 656 if (snprintf(spawn_getsid, sizeof (spawn_getsid), "%s/getsid.64", 657 dirname(origin)) >= sizeof (spawn_getsid)) { 658 errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to assemble " 659 "getsid.64 path"); 660 } 661 662 if (access(spawn_getsid, X_OK) != 0) { 663 err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to access %s", 664 spawn_getsid); 665 } 666 } 667 668 int 669 main(void) 670 { 671 int ret = EXIT_SUCCESS; 672 673 /* 674 * Because this test wants to rely on a known starting directory, we're 675 * going to chdir into /var/tmp at the start of this. 676 */ 677 if (chdir("/var/tmp") != 0) { 678 err(EXIT_FAILURE, "INTERNAL TEST ERROR: failed to cd into " 679 "/var/tmp"); 680 } 681 682 posix_spawn_test_paths(); 683 684 for (size_t i = 0; i < ARRAY_SIZE(spawn_dir_tests); i++) { 685 if (!posix_spawn_test_one_chdir(&spawn_dir_tests[i])) 686 ret = EXIT_FAILURE; 687 688 if (!posix_spawn_test_one_fchdir(&spawn_dir_tests[i])) 689 ret = EXIT_FAILURE; 690 } 691 692 if (!posix_spawn_test_bad_actions()) { 693 ret = EXIT_FAILURE; 694 } 695 696 if (!posix_spawn_test_bad_fchdir()) { 697 ret = EXIT_FAILURE; 698 } 699 700 for (size_t i = 0; i < ARRAY_SIZE(spawn_flags_tests); i++) { 701 if (!posix_spawn_test_one_flags(&spawn_flags_tests[i])) 702 ret = EXIT_FAILURE; 703 } 704 705 if (ret == EXIT_SUCCESS) { 706 (void) printf("All tests passed successfully!\n"); 707 } 708 709 return (ret); 710 } 711