1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include "namespace.h" 30 #include <sys/param.h> 31 #include <sys/mman.h> 32 #include <sys/procctl.h> 33 #include <sys/procdesc.h> 34 #include <sys/queue.h> 35 #include <sys/wait.h> 36 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <sched.h> 40 #include <spawn.h> 41 #include <signal.h> 42 #include <stdbool.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 #include "un-namespace.h" 47 #include "libc_private.h" 48 49 struct __posix_spawnattr { 50 short sa_flags; 51 pid_t sa_pgroup; 52 struct sched_param sa_schedparam; 53 int sa_schedpolicy; 54 sigset_t sa_sigdefault; 55 sigset_t sa_sigmask; 56 int sa_execfd; 57 int *sa_pdrfork_fdp; 58 int sa_pdflags; 59 }; 60 61 struct __posix_spawn_file_actions { 62 STAILQ_HEAD(, __posix_spawn_file_actions_entry) fa_list; 63 }; 64 65 typedef struct __posix_spawn_file_actions_entry { 66 STAILQ_ENTRY(__posix_spawn_file_actions_entry) fae_list; 67 enum { 68 FAE_OPEN, 69 FAE_DUP2, 70 FAE_CLOSE, 71 FAE_CHDIR, 72 FAE_FCHDIR, 73 FAE_CLOSEFROM, 74 } fae_action; 75 76 int fae_fildes; 77 union { 78 struct { 79 char *path; 80 #define fae_path fae_data.open.path 81 int oflag; 82 #define fae_oflag fae_data.open.oflag 83 mode_t mode; 84 #define fae_mode fae_data.open.mode 85 } open; 86 struct { 87 int newfildes; 88 #define fae_newfildes fae_data.dup2.newfildes 89 } dup2; 90 } fae_data; 91 } posix_spawn_file_actions_entry_t; 92 93 /* 94 * Spawn routines 95 */ 96 97 static int 98 process_spawnattr(const posix_spawnattr_t sa) 99 { 100 struct sigaction sigact = { .sa_flags = 0, .sa_handler = SIG_DFL }; 101 int aslr, i; 102 103 /* 104 * POSIX doesn't really describe in which order everything 105 * should be set. We'll just set them in the order in which they 106 * are mentioned. 107 */ 108 109 /* Set process group */ 110 if (sa->sa_flags & POSIX_SPAWN_SETPGROUP) { 111 if (setpgid(0, sa->sa_pgroup) != 0) 112 return (errno); 113 } 114 115 /* Set scheduler policy */ 116 if (sa->sa_flags & POSIX_SPAWN_SETSCHEDULER) { 117 if (sched_setscheduler(0, sa->sa_schedpolicy, 118 &sa->sa_schedparam) != 0) 119 return (errno); 120 } else if (sa->sa_flags & POSIX_SPAWN_SETSCHEDPARAM) { 121 if (sched_setparam(0, &sa->sa_schedparam) != 0) 122 return (errno); 123 } 124 125 /* Reset user ID's */ 126 if (sa->sa_flags & POSIX_SPAWN_RESETIDS) { 127 if (setegid(getgid()) != 0) 128 return (errno); 129 if (seteuid(getuid()) != 0) 130 return (errno); 131 } 132 133 /* 134 * Set signal masks/defaults. 135 * Use unwrapped syscall, libthr is in undefined state after vfork(). 136 */ 137 if (sa->sa_flags & POSIX_SPAWN_SETSIGMASK) { 138 __sys_sigprocmask(SIG_SETMASK, &sa->sa_sigmask, NULL); 139 } 140 141 if (sa->sa_flags & POSIX_SPAWN_SETSIGDEF) { 142 for (i = 1; i <= _SIG_MAXSIG; i++) { 143 if (sigismember(&sa->sa_sigdefault, i)) 144 if (__sys_sigaction(i, &sigact, NULL) != 0) 145 return (errno); 146 } 147 } 148 149 /* Disable ASLR. */ 150 if ((sa->sa_flags & POSIX_SPAWN_DISABLE_ASLR_NP) != 0) { 151 aslr = PROC_ASLR_FORCE_DISABLE; 152 if (procctl(P_PID, 0, PROC_ASLR_CTL, &aslr) != 0) 153 return (errno); 154 } 155 156 return (0); 157 } 158 159 static int 160 process_file_actions_entry(posix_spawn_file_actions_entry_t *fae) 161 { 162 int fd, saved_errno; 163 164 switch (fae->fae_action) { 165 case FAE_OPEN: 166 /* Perform an open(), make it use the right fd */ 167 fd = _open(fae->fae_path, fae->fae_oflag, fae->fae_mode); 168 if (fd < 0) 169 return (errno); 170 if (fd != fae->fae_fildes) { 171 if (_dup2(fd, fae->fae_fildes) == -1) { 172 saved_errno = errno; 173 (void)_close(fd); 174 return (saved_errno); 175 } 176 if (_close(fd) != 0) { 177 if (errno == EBADF) 178 return (EBADF); 179 } 180 } 181 if (_fcntl(fae->fae_fildes, F_SETFD, 0) == -1) 182 return (errno); 183 break; 184 case FAE_DUP2: 185 /* Perform a dup2() */ 186 if (_dup2(fae->fae_fildes, fae->fae_newfildes) == -1) 187 return (errno); 188 if (_fcntl(fae->fae_newfildes, F_SETFD, 0) == -1) 189 return (errno); 190 break; 191 case FAE_CLOSE: 192 /* Perform a close(), do not fail if already closed */ 193 (void)_close(fae->fae_fildes); 194 break; 195 case FAE_CHDIR: 196 if (chdir(fae->fae_path) != 0) 197 return (errno); 198 break; 199 case FAE_FCHDIR: 200 if (fchdir(fae->fae_fildes) != 0) 201 return (errno); 202 break; 203 case FAE_CLOSEFROM: 204 closefrom(fae->fae_fildes); 205 break; 206 } 207 return (0); 208 } 209 210 static int 211 process_file_actions(const posix_spawn_file_actions_t fa) 212 { 213 posix_spawn_file_actions_entry_t *fae; 214 int error; 215 216 /* Replay all file descriptor modifications */ 217 STAILQ_FOREACH(fae, &fa->fa_list, fae_list) { 218 error = process_file_actions_entry(fae); 219 if (error) 220 return (error); 221 } 222 return (0); 223 } 224 225 struct posix_spawn_args { 226 const char *path; 227 const posix_spawn_file_actions_t *fa; 228 const posix_spawnattr_t *sa; 229 char * const * argv; 230 char * const * envp; 231 int use_env_path; 232 volatile int error; 233 }; 234 235 #define _RFORK_THREAD_STACK_SIZE 4096 236 237 static int 238 _posix_spawn_thr(void *data) 239 { 240 struct posix_spawn_args *psa; 241 char * const *envp; 242 243 psa = data; 244 if (psa->sa != NULL) { 245 psa->error = process_spawnattr(*psa->sa); 246 if (psa->error) 247 _exit(127); 248 } 249 if (psa->fa != NULL) { 250 psa->error = process_file_actions(*psa->fa); 251 if (psa->error) 252 _exit(127); 253 } 254 envp = psa->envp != NULL ? psa->envp : environ; 255 if (psa->sa != NULL && (*(psa->sa))->sa_execfd != -1) 256 fexecve((*(psa->sa))->sa_execfd, psa->argv, envp); 257 else if (psa->use_env_path) 258 __libc_execvpe(psa->path, psa->argv, envp); 259 else 260 _execve(psa->path, psa->argv, envp); 261 psa->error = errno; 262 263 /* This is called in such a way that it must not exit. */ 264 _exit(127); 265 } 266 267 static int 268 do_posix_spawn(pid_t *pid, const char *path, 269 const posix_spawn_file_actions_t *fa, 270 const posix_spawnattr_t *sa, 271 char * const argv[], char * const envp[], bool use_env_path) 272 { 273 struct posix_spawn_args psa; 274 pid_t p; 275 int pfd; 276 bool do_pfd; 277 char *guard, *stack; 278 size_t stacksz, pagesz; 279 280 #if defined(__i386__) || defined(__amd64__) 281 stacksz = _RFORK_THREAD_STACK_SIZE; 282 pagesz = getpagesize(); 283 284 if (use_env_path) { 285 size_t cnt; 286 287 /* 288 * We need to make sure we have enough room on the stack for the 289 * potential alloca() in execvPe if it gets kicked back an 290 * ENOEXEC from execve(2), plus the original buffer we gave 291 * ourselves; this protects us in the event that the caller 292 * intentionally or inadvertently supplies enough arguments to 293 * make us blow past the stack we've allocated from it. 294 */ 295 for (cnt = 0; argv[cnt] != NULL; ++cnt) 296 ; 297 stacksz += MAX(3, cnt + 2) * sizeof(char *); 298 } 299 300 stacksz = round_page(stacksz); 301 guard = mmap(NULL, stacksz + pagesz, 0, MAP_GUARD, -1, 0); 302 if (guard == MAP_FAILED) 303 return (ENOMEM); 304 stack = mmap(guard + pagesz, stacksz, PROT_READ | PROT_WRITE, 305 MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0); 306 if (stack == MAP_FAILED) { 307 munmap(guard, stacksz + pagesz); 308 return (ENOMEM); 309 } 310 #else 311 guard = stack = NULL; 312 pagesz = stacksz = 0; 313 #endif 314 psa.path = path; 315 psa.fa = fa; 316 psa.sa = sa; 317 psa.argv = argv; 318 psa.envp = envp; 319 psa.use_env_path = use_env_path; 320 psa.error = 0; 321 322 do_pfd = sa != NULL && (*sa)->sa_pdrfork_fdp != NULL; 323 324 /* 325 * Passing RFSPAWN to rfork(2) gives us effectively a vfork that drops 326 * non-ignored signal handlers. We'll fall back to the slightly less 327 * ideal vfork(2) if we get an EINVAL from rfork -- this should only 328 * happen with newer libc on older kernel that doesn't accept 329 * RFSPAWN. 330 * 331 * Combination of vfork() (or its equivalent rfork() form) and 332 * a special property of the libthr rtld locks ensure that 333 * rtld is operational in the child. In particular, libthr 334 * rtld locks do not store owner' tid into the lock word. 335 * 336 * x86 stores the return address on the stack, so rfork(2) 337 * cannot work as-is because the child would clobber the 338 * return address of the parent. Because of this, we must use 339 * rfork_thread instead. 340 * 341 * Every other architecture stores the return address in a 342 * register, the trivial rfork_thread() wrapper is provided 343 * for them. The only minor drawback is that the stack is 344 * temporarily allocated. 345 */ 346 if (do_pfd) { 347 p = pdrfork_thread(&pfd, PD_CLOEXEC | (*sa)->sa_pdflags, 348 RFSPAWN, stack + stacksz, _posix_spawn_thr, &psa); 349 } else { 350 p = rfork_thread(RFSPAWN, stack + stacksz, _posix_spawn_thr, 351 &psa); 352 } 353 if (stack != NULL) 354 munmap(guard, stacksz + pagesz); 355 356 /* 357 * The above block should leave us in a state where we've either 358 * succeeded and we're ready to process the results, or we need to 359 * fallback to vfork() if the kernel didn't like RFSPAWN. 360 */ 361 362 if (p == -1 && errno == EINVAL) { 363 if (do_pfd) 364 return (EOPNOTSUPP); 365 p = vfork(); 366 if (p == 0) 367 /* _posix_spawn_thr does not return */ 368 _posix_spawn_thr(&psa); 369 } 370 if (p == -1) 371 return (errno); 372 if (psa.error != 0) { 373 /* Failed; ready to reap */ 374 if (do_pfd) 375 (void)_close(pfd); 376 else 377 _waitpid(p, NULL, WNOHANG); 378 } else if (pid != NULL) { 379 /* exec succeeded */ 380 *pid = p; 381 if (do_pfd) 382 *((*sa)->sa_pdrfork_fdp) = pfd; 383 } 384 return (psa.error); 385 } 386 387 int 388 posix_spawn(pid_t *pid, const char *path, 389 const posix_spawn_file_actions_t *fa, 390 const posix_spawnattr_t *sa, 391 char * const argv[], char * const envp[]) 392 { 393 return (do_posix_spawn(pid, path, fa, sa, argv, envp, false)); 394 } 395 396 int 397 posix_spawnp(pid_t *pid, const char *path, 398 const posix_spawn_file_actions_t *fa, 399 const posix_spawnattr_t *sa, 400 char * const argv[], char * const envp[]) 401 { 402 return (do_posix_spawn(pid, path, fa, sa, argv, envp, true)); 403 } 404 405 /* 406 * File descriptor actions 407 */ 408 409 int 410 posix_spawn_file_actions_init(posix_spawn_file_actions_t *ret) 411 { 412 posix_spawn_file_actions_t fa; 413 414 fa = malloc(sizeof(struct __posix_spawn_file_actions)); 415 if (fa == NULL) 416 return (errno); 417 418 STAILQ_INIT(&fa->fa_list); 419 *ret = fa; 420 return (0); 421 } 422 423 int 424 posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *fa) 425 { 426 posix_spawn_file_actions_entry_t *fae; 427 428 while ((fae = STAILQ_FIRST(&(*fa)->fa_list)) != NULL) { 429 /* Remove file action entry from the queue */ 430 STAILQ_REMOVE_HEAD(&(*fa)->fa_list, fae_list); 431 432 /* Deallocate file action entry */ 433 if (fae->fae_action == FAE_OPEN || 434 fae->fae_action == FAE_CHDIR) 435 free(fae->fae_path); 436 free(fae); 437 } 438 439 free(*fa); 440 return (0); 441 } 442 443 int 444 posix_spawn_file_actions_addopen(posix_spawn_file_actions_t * __restrict fa, 445 int fildes, const char * __restrict path, int oflag, mode_t mode) 446 { 447 posix_spawn_file_actions_entry_t *fae; 448 int error; 449 450 if (fildes < 0) 451 return (EBADF); 452 453 /* Allocate object */ 454 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 455 if (fae == NULL) 456 return (errno); 457 458 /* Set values and store in queue */ 459 fae->fae_action = FAE_OPEN; 460 fae->fae_path = strdup(path); 461 if (fae->fae_path == NULL) { 462 error = errno; 463 free(fae); 464 return (error); 465 } 466 fae->fae_fildes = fildes; 467 fae->fae_oflag = oflag; 468 fae->fae_mode = mode; 469 470 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 471 return (0); 472 } 473 474 int 475 posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *fa, 476 int fildes, int newfildes) 477 { 478 posix_spawn_file_actions_entry_t *fae; 479 480 if (fildes < 0 || newfildes < 0) 481 return (EBADF); 482 483 /* Allocate object */ 484 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 485 if (fae == NULL) 486 return (errno); 487 488 /* Set values and store in queue */ 489 fae->fae_action = FAE_DUP2; 490 fae->fae_fildes = fildes; 491 fae->fae_newfildes = newfildes; 492 493 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 494 return (0); 495 } 496 497 int 498 posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *fa, 499 int fildes) 500 { 501 posix_spawn_file_actions_entry_t *fae; 502 503 if (fildes < 0) 504 return (EBADF); 505 506 /* Allocate object */ 507 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 508 if (fae == NULL) 509 return (errno); 510 511 /* Set values and store in queue */ 512 fae->fae_action = FAE_CLOSE; 513 fae->fae_fildes = fildes; 514 515 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 516 return (0); 517 } 518 519 int 520 posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t * 521 __restrict fa, const char *__restrict path) 522 { 523 posix_spawn_file_actions_entry_t *fae; 524 int error; 525 526 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 527 if (fae == NULL) 528 return (errno); 529 530 fae->fae_action = FAE_CHDIR; 531 fae->fae_path = strdup(path); 532 if (fae->fae_path == NULL) { 533 error = errno; 534 free(fae); 535 return (error); 536 } 537 538 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 539 return (0); 540 } 541 __weak_reference(posix_spawn_file_actions_addchdir_np, 542 posix_spawn_file_actions_addchdir); 543 544 int 545 posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *__restrict fa, 546 int fildes) 547 { 548 posix_spawn_file_actions_entry_t *fae; 549 550 if (fildes < 0) 551 return (EBADF); 552 553 /* Allocate object */ 554 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 555 if (fae == NULL) 556 return (errno); 557 558 fae->fae_action = FAE_FCHDIR; 559 fae->fae_fildes = fildes; 560 561 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 562 return (0); 563 } 564 565 __weak_reference(posix_spawn_file_actions_addfchdir_np, 566 posix_spawn_file_actions_addfchdir); 567 568 int 569 posix_spawn_file_actions_addclosefrom_np (posix_spawn_file_actions_t * 570 __restrict fa, int from) 571 { 572 posix_spawn_file_actions_entry_t *fae; 573 574 if (from < 0) 575 return (EBADF); 576 577 /* Allocate object */ 578 fae = malloc(sizeof(posix_spawn_file_actions_entry_t)); 579 if (fae == NULL) 580 return (errno); 581 582 fae->fae_action = FAE_CLOSEFROM; 583 fae->fae_fildes = from; 584 585 STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list); 586 return (0); 587 } 588 589 /* 590 * Spawn attributes 591 */ 592 593 int 594 posix_spawnattr_init(posix_spawnattr_t *ret) 595 { 596 posix_spawnattr_t sa; 597 598 sa = calloc(1, sizeof(struct __posix_spawnattr)); 599 if (sa == NULL) 600 return (errno); 601 sa->sa_execfd = -1; 602 603 /* Set defaults as specified by POSIX, cleared above */ 604 *ret = sa; 605 return (0); 606 } 607 608 int 609 posix_spawnattr_destroy(posix_spawnattr_t *sa) 610 { 611 free(*sa); 612 return (0); 613 } 614 615 int 616 posix_spawnattr_getflags(const posix_spawnattr_t * __restrict sa, 617 short * __restrict flags) 618 { 619 *flags = (*sa)->sa_flags; 620 return (0); 621 } 622 623 int 624 posix_spawnattr_getpgroup(const posix_spawnattr_t * __restrict sa, 625 pid_t * __restrict pgroup) 626 { 627 *pgroup = (*sa)->sa_pgroup; 628 return (0); 629 } 630 631 int 632 posix_spawnattr_getschedparam(const posix_spawnattr_t * __restrict sa, 633 struct sched_param * __restrict schedparam) 634 { 635 *schedparam = (*sa)->sa_schedparam; 636 return (0); 637 } 638 639 int 640 posix_spawnattr_getschedpolicy(const posix_spawnattr_t * __restrict sa, 641 int * __restrict schedpolicy) 642 { 643 *schedpolicy = (*sa)->sa_schedpolicy; 644 return (0); 645 } 646 647 int 648 posix_spawnattr_getsigdefault(const posix_spawnattr_t * __restrict sa, 649 sigset_t * __restrict sigdefault) 650 { 651 *sigdefault = (*sa)->sa_sigdefault; 652 return (0); 653 } 654 655 int 656 posix_spawnattr_getsigmask(const posix_spawnattr_t * __restrict sa, 657 sigset_t * __restrict sigmask) 658 { 659 *sigmask = (*sa)->sa_sigmask; 660 return (0); 661 } 662 663 int 664 posix_spawnattr_getexecfd_np(const posix_spawnattr_t * __restrict sa, 665 int * __restrict fdp) 666 { 667 *fdp = (*sa)->sa_execfd; 668 return (0); 669 } 670 671 int 672 posix_spawnattr_getprocdescp_np(const posix_spawnattr_t * __restrict sa, 673 int ** __restrict fdpp, int * __restrict pdrflagsp) 674 { 675 *fdpp = (*sa)->sa_pdrfork_fdp; 676 *pdrflagsp = (*sa)->sa_pdflags; 677 return (0); 678 } 679 680 int 681 posix_spawnattr_setflags(posix_spawnattr_t *sa, short flags) 682 { 683 if ((flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | 684 POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | 685 POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | 686 POSIX_SPAWN_DISABLE_ASLR_NP)) != 0) 687 return (EINVAL); 688 (*sa)->sa_flags = flags; 689 return (0); 690 } 691 692 int 693 posix_spawnattr_setpgroup(posix_spawnattr_t *sa, pid_t pgroup) 694 { 695 (*sa)->sa_pgroup = pgroup; 696 return (0); 697 } 698 699 int 700 posix_spawnattr_setschedparam(posix_spawnattr_t * __restrict sa, 701 const struct sched_param * __restrict schedparam) 702 { 703 (*sa)->sa_schedparam = *schedparam; 704 return (0); 705 } 706 707 int 708 posix_spawnattr_setschedpolicy(posix_spawnattr_t *sa, int schedpolicy) 709 { 710 (*sa)->sa_schedpolicy = schedpolicy; 711 return (0); 712 } 713 714 int 715 posix_spawnattr_setsigdefault(posix_spawnattr_t * __restrict sa, 716 const sigset_t * __restrict sigdefault) 717 { 718 (*sa)->sa_sigdefault = *sigdefault; 719 return (0); 720 } 721 722 int 723 posix_spawnattr_setsigmask(posix_spawnattr_t * __restrict sa, 724 const sigset_t * __restrict sigmask) 725 { 726 (*sa)->sa_sigmask = *sigmask; 727 return (0); 728 } 729 730 int 731 posix_spawnattr_setexecfd_np(posix_spawnattr_t * __restrict sa, 732 int execfd) 733 { 734 (*sa)->sa_execfd = execfd; 735 return (0); 736 } 737 738 int 739 posix_spawnattr_setprocdescp_np(const posix_spawnattr_t * __restrict sa, 740 int * __restrict fdp, int pdrflags) 741 { 742 (*sa)->sa_pdrfork_fdp = fdp; 743 (*sa)->sa_pdflags = pdrflags; 744 return (0); 745 } 746