1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include "lint.h" 30 #include "thr_uberdata.h" 31 #include <sys/libc_kernel.h> 32 #include <sys/procset.h> 33 #include <sys/fork.h> 34 #include <alloca.h> 35 #include <spawn.h> 36 37 #define ALL_POSIX_SPAWN_FLAGS \ 38 (POSIX_SPAWN_RESETIDS | \ 39 POSIX_SPAWN_SETPGROUP | \ 40 POSIX_SPAWN_SETSIGDEF | \ 41 POSIX_SPAWN_SETSIGMASK | \ 42 POSIX_SPAWN_SETSCHEDPARAM | \ 43 POSIX_SPAWN_SETSCHEDULER | \ 44 POSIX_SPAWN_NOSIGCHLD_NP | \ 45 POSIX_SPAWN_WAITPID_NP) 46 47 typedef struct { 48 int sa_psflags; /* POSIX_SPAWN_* flags */ 49 int sa_priority; 50 int sa_schedpolicy; 51 pid_t sa_pgroup; 52 sigset_t sa_sigdefault; 53 sigset_t sa_sigmask; 54 } spawn_attr_t; 55 56 typedef struct file_attr { 57 struct file_attr *fa_next; /* circular list of file actions */ 58 struct file_attr *fa_prev; 59 enum {FA_OPEN, FA_CLOSE, FA_DUP2} fa_type; 60 uint_t fa_pathsize; /* size of fa_path[] array */ 61 char *fa_path; /* copied pathname for open() */ 62 int fa_oflag; /* oflag for open() */ 63 mode_t fa_mode; /* mode for open() */ 64 int fa_filedes; /* file descriptor for open()/close() */ 65 int fa_newfiledes; /* new file descriptor for dup2() */ 66 } file_attr_t; 67 68 extern pid_t _vforkx(int); 69 #pragma unknown_control_flow(_vforkx) 70 extern void *_private_memset(void *, int, size_t); 71 extern int __lwp_sigmask(int, const sigset_t *, sigset_t *); 72 extern int __sigaction(int, const struct sigaction *, struct sigaction *); 73 extern int _private_close(int); 74 extern int _private_execve(const char *, char *const *, char *const *); 75 extern int _private_fcntl(int, int, intptr_t); 76 extern int _private_setgid(gid_t); 77 extern int _private_setpgid(pid_t, pid_t); 78 extern int _private_setuid(uid_t); 79 extern int _private_sigismember(sigset_t *, int); 80 extern gid_t _private_getgid(void); 81 extern uid_t _private_getuid(void); 82 extern uid_t _private_geteuid(void); 83 extern void _private_exit(int); 84 85 static int 86 perform_flag_actions(spawn_attr_t *sap) 87 { 88 int sig; 89 90 if (sap->sa_psflags & POSIX_SPAWN_SETSIGMASK) { 91 (void) __lwp_sigmask(SIG_SETMASK, &sap->sa_sigmask, NULL); 92 } 93 94 if (sap->sa_psflags & POSIX_SPAWN_SETSIGDEF) { 95 struct sigaction sigdfl; 96 97 (void) _private_memset(&sigdfl, 0, sizeof (sigdfl)); 98 for (sig = 1; sig < NSIG; sig++) { 99 if (_private_sigismember(&sap->sa_sigdefault, sig)) 100 (void) __sigaction(sig, &sigdfl, NULL); 101 } 102 } 103 104 if (sap->sa_psflags & POSIX_SPAWN_RESETIDS) { 105 if (_private_setgid(_private_getgid()) != 0 || 106 _private_setuid(_private_getuid()) != 0) 107 return (errno); 108 } 109 110 if (sap->sa_psflags & POSIX_SPAWN_SETPGROUP) { 111 if (_private_setpgid(0, sap->sa_pgroup) != 0) 112 return (errno); 113 } 114 115 if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDULER) { 116 if (setparam(P_LWPID, P_MYID, 117 sap->sa_schedpolicy, sap->sa_priority) == -1) 118 return (errno); 119 } else if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDPARAM) { 120 if (setprio(P_LWPID, P_MYID, sap->sa_priority, NULL) == -1) 121 return (errno); 122 } 123 124 return (0); 125 } 126 127 static int 128 perform_file_actions(file_attr_t *fap) 129 { 130 file_attr_t *froot = fap; 131 int fd; 132 133 do { 134 switch (fap->fa_type) { 135 case FA_OPEN: 136 fd = _private_open(fap->fa_path, 137 fap->fa_oflag, fap->fa_mode); 138 if (fd < 0) 139 return (errno); 140 if (fd != fap->fa_filedes) { 141 if (_private_fcntl(fd, F_DUP2FD, 142 fap->fa_filedes) < 0) 143 return (errno); 144 (void) _private_close(fd); 145 } 146 break; 147 case FA_CLOSE: 148 if (_private_close(fap->fa_filedes) == -1) 149 return (errno); 150 break; 151 case FA_DUP2: 152 fd = _private_fcntl(fap->fa_filedes, F_DUP2FD, 153 fap->fa_newfiledes); 154 if (fd < 0) 155 return (errno); 156 break; 157 } 158 } while ((fap = fap->fa_next) != froot); 159 160 return (0); 161 } 162 163 static int 164 forkflags(spawn_attr_t *sap) 165 { 166 int flags = 0; 167 168 if (sap != NULL) { 169 if (sap->sa_psflags & POSIX_SPAWN_NOSIGCHLD_NP) 170 flags |= FORK_NOSIGCHLD; 171 if (sap->sa_psflags & POSIX_SPAWN_WAITPID_NP) 172 flags |= FORK_WAITPID; 173 } 174 175 return (flags); 176 } 177 178 /* 179 * set_error() / get_error() are used to guarantee that the local variable 180 * 'error' is set correctly in memory on return from vfork() in the parent. 181 */ 182 183 static int 184 set_error(int *errp, int err) 185 { 186 return (*errp = err); 187 } 188 189 static int 190 get_error(int *errp) 191 { 192 return (*errp); 193 } 194 195 /* 196 * For MT safety, do not invoke the dynamic linker after calling vfork(). 197 * If some other thread was in the dynamic linker when this thread's parent 198 * called vfork() then the dynamic linker's lock would still be held here 199 * (with a defunct owner) and we would deadlock ourself if we invoked it. 200 * 201 * Therefore, all of the functions we call here after returning from 202 * _vforkx() in the child are not and must never be exported from libc 203 * as global symbols. To do so would risk invoking the dynamic linker. 204 */ 205 206 #pragma weak posix_spawn = _posix_spawn 207 int 208 _posix_spawn( 209 pid_t *pidp, 210 const char *path, 211 const posix_spawn_file_actions_t *file_actions, 212 const posix_spawnattr_t *attrp, 213 char *const argv[], 214 char *const envp[]) 215 { 216 spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL; 217 file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL; 218 int error; /* this will be set by the child */ 219 pid_t pid; 220 221 if (attrp != NULL && sap == NULL) 222 return (EINVAL); 223 224 switch (pid = _vforkx(forkflags(sap))) { 225 case 0: /* child */ 226 break; 227 case -1: /* parent, failure */ 228 return (errno); 229 default: /* parent, success */ 230 /* 231 * We don't get here until the child exec()s or exit()s 232 */ 233 if (pidp != NULL && get_error(&error) == 0) 234 *pidp = pid; 235 return (get_error(&error)); 236 } 237 238 if (sap != NULL) 239 if (set_error(&error, perform_flag_actions(sap)) != 0) 240 _private_exit(_EVAPORATE); 241 242 if (fap != NULL) 243 if (set_error(&error, perform_file_actions(fap)) != 0) 244 _private_exit(_EVAPORATE); 245 246 (void) set_error(&error, 0); 247 (void) _private_execve(path, argv, envp); 248 (void) set_error(&error, errno); 249 _private_exit(_EVAPORATE); 250 return (0); /* not reached */ 251 } 252 253 /* 254 * Much of posix_spawnp() blatently stolen from execvp() (port/gen/execvp.c). 255 */ 256 257 extern int libc__xpg4; 258 259 static const char * 260 execat(const char *s1, const char *s2, char *si) 261 { 262 int cnt = PATH_MAX + 1; 263 char *s; 264 char c; 265 266 for (s = si; (c = *s1) != '\0' && c != ':'; s1++) { 267 if (cnt > 0) { 268 *s++ = c; 269 cnt--; 270 } 271 } 272 if (si != s && cnt > 0) { 273 *s++ = '/'; 274 cnt--; 275 } 276 for (; (c = *s2) != '\0' && cnt > 0; s2++) { 277 *s++ = c; 278 cnt--; 279 } 280 *s = '\0'; 281 return (*s1? ++s1: NULL); 282 } 283 284 #pragma weak posix_spawnp = _posix_spawnp 285 /* ARGSUSED */ 286 int 287 _posix_spawnp( 288 pid_t *pidp, 289 const char *file, 290 const posix_spawn_file_actions_t *file_actions, 291 const posix_spawnattr_t *attrp, 292 char *const argv[], 293 char *const envp[]) 294 { 295 spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL; 296 file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL; 297 const char *pathstr = (strchr(file, '/') == NULL)? getenv("PATH") : ""; 298 int xpg4 = libc__xpg4; 299 int error; /* this will be set by the child */ 300 char path[PATH_MAX+4]; 301 const char *cp; 302 pid_t pid; 303 char **newargs; 304 int argc; 305 int i; 306 static const char *sun_path = "/bin/sh"; 307 static const char *xpg4_path = "/usr/xpg4/bin/sh"; 308 static const char *shell = "sh"; 309 310 if (attrp != NULL && sap == NULL) 311 return (EINVAL); 312 313 if (*file == '\0') 314 return (EACCES); 315 316 /* 317 * We may need to invoke the shell with a slightly modified 318 * argv[] array. To do this we need to preallocate the array. 319 * We must call alloca() before calling vfork() because doing 320 * it after vfork() (in the child) would corrupt the parent. 321 */ 322 for (argc = 0; argv[argc] != NULL; argc++) 323 continue; 324 newargs = alloca((argc + 2) * sizeof (char *)); 325 326 switch (pid = _vforkx(forkflags(sap))) { 327 case 0: /* child */ 328 break; 329 case -1: /* parent, failure */ 330 return (errno); 331 default: /* parent, success */ 332 /* 333 * We don't get here until the child exec()s or exit()s 334 */ 335 if (pidp != NULL && get_error(&error) == 0) 336 *pidp = pid; 337 return (get_error(&error)); 338 } 339 340 if (sap != NULL) 341 if (set_error(&error, perform_flag_actions(sap)) != 0) 342 _private_exit(_EVAPORATE); 343 344 if (fap != NULL) 345 if (set_error(&error, perform_file_actions(fap)) != 0) 346 _private_exit(_EVAPORATE); 347 348 if (pathstr == NULL) { 349 /* 350 * XPG4: pathstr is equivalent to _CS_PATH, except that 351 * :/usr/sbin is appended when root, and pathstr must end 352 * with a colon when not root. Keep these paths in sync 353 * with _CS_PATH in confstr.c. Note that pathstr must end 354 * with a colon when not root so that when file doesn't 355 * contain '/', the last call to execat() will result in an 356 * attempt to execv file from the current directory. 357 */ 358 if (_private_geteuid() == 0 || _private_getuid() == 0) { 359 if (!xpg4) 360 pathstr = "/usr/sbin:/usr/ccs/bin:/usr/bin"; 361 else 362 pathstr = "/usr/xpg4/bin:/usr/ccs/bin:" 363 "/usr/bin:/opt/SUNWspro/bin:/usr/sbin"; 364 } else { 365 if (!xpg4) 366 pathstr = "/usr/ccs/bin:/usr/bin:"; 367 else 368 pathstr = "/usr/xpg4/bin:/usr/ccs/bin:" 369 "/usr/bin:/opt/SUNWspro/bin:"; 370 } 371 } 372 373 cp = pathstr; 374 do { 375 cp = execat(cp, file, path); 376 /* 377 * 4025035 and 4038378 378 * if a filename begins with a "-" prepend "./" so that 379 * the shell can't interpret it as an option 380 */ 381 if (*path == '-') { 382 char *s; 383 384 for (s = path; *s != '\0'; s++) 385 continue; 386 for (; s >= path; s--) 387 *(s + 2) = *s; 388 path[0] = '.'; 389 path[1] = '/'; 390 } 391 (void) set_error(&error, 0); 392 (void) _private_execve(path, argv, envp); 393 if (set_error(&error, errno) == ENOEXEC) { 394 newargs[0] = (char *)shell; 395 newargs[1] = path; 396 for (i = 1; i <= argc; i++) 397 newargs[i + 1] = argv[i]; 398 (void) set_error(&error, 0); 399 (void) _private_execve(xpg4? xpg4_path : sun_path, 400 newargs, envp); 401 (void) set_error(&error, errno); 402 _private_exit(_EVAPORATE); 403 } 404 } while (cp); 405 _private_exit(_EVAPORATE); 406 return (0); /* not reached */ 407 } 408 409 #pragma weak posix_spawn_file_actions_init = \ 410 _posix_spawn_file_actions_init 411 int 412 _posix_spawn_file_actions_init( 413 posix_spawn_file_actions_t *file_actions) 414 { 415 file_actions->__file_attrp = NULL; 416 return (0); 417 } 418 419 #pragma weak posix_spawn_file_actions_destroy = \ 420 _posix_spawn_file_actions_destroy 421 int 422 _posix_spawn_file_actions_destroy( 423 posix_spawn_file_actions_t *file_actions) 424 { 425 file_attr_t *froot = file_actions->__file_attrp; 426 file_attr_t *fap; 427 file_attr_t *next; 428 429 if ((fap = froot) != NULL) { 430 do { 431 next = fap->fa_next; 432 if (fap-> fa_type == FA_OPEN) 433 lfree(fap->fa_path, fap->fa_pathsize); 434 lfree(fap, sizeof (*fap)); 435 } while ((fap = next) != froot); 436 } 437 file_actions->__file_attrp = NULL; 438 return (0); 439 } 440 441 static void 442 add_file_attr(posix_spawn_file_actions_t *file_actions, file_attr_t *fap) 443 { 444 file_attr_t *froot = file_actions->__file_attrp; 445 446 if (froot == NULL) { 447 fap->fa_next = fap->fa_prev = fap; 448 file_actions->__file_attrp = fap; 449 } else { 450 fap->fa_next = froot; 451 fap->fa_prev = froot->fa_prev; 452 froot->fa_prev->fa_next = fap; 453 froot->fa_prev = fap; 454 } 455 } 456 457 #pragma weak posix_spawn_file_actions_addopen = \ 458 _posix_spawn_file_actions_addopen 459 int 460 _posix_spawn_file_actions_addopen( 461 posix_spawn_file_actions_t *file_actions, 462 int filedes, 463 const char *path, 464 int oflag, 465 mode_t mode) 466 { 467 file_attr_t *fap; 468 469 if (filedes < 0) 470 return (EBADF); 471 if ((fap = lmalloc(sizeof (*fap))) == NULL) 472 return (ENOMEM); 473 474 fap->fa_pathsize = strlen(path) + 1; 475 if ((fap->fa_path = lmalloc(fap->fa_pathsize)) == NULL) { 476 lfree(fap, sizeof (*fap)); 477 return (ENOMEM); 478 } 479 (void) strcpy(fap->fa_path, path); 480 481 fap->fa_type = FA_OPEN; 482 fap->fa_oflag = oflag; 483 fap->fa_mode = mode; 484 fap->fa_filedes = filedes; 485 add_file_attr(file_actions, fap); 486 487 return (0); 488 } 489 490 #pragma weak posix_spawn_file_actions_addclose = \ 491 _posix_spawn_file_actions_addclose 492 int 493 _posix_spawn_file_actions_addclose( 494 posix_spawn_file_actions_t *file_actions, 495 int filedes) 496 { 497 file_attr_t *fap; 498 499 if (filedes < 0) 500 return (EBADF); 501 if ((fap = lmalloc(sizeof (*fap))) == NULL) 502 return (ENOMEM); 503 504 fap->fa_type = FA_CLOSE; 505 fap->fa_filedes = filedes; 506 add_file_attr(file_actions, fap); 507 508 return (0); 509 } 510 511 #pragma weak posix_spawn_file_actions_adddup2 = \ 512 _posix_spawn_file_actions_adddup2 513 int 514 _posix_spawn_file_actions_adddup2( 515 posix_spawn_file_actions_t *file_actions, 516 int filedes, 517 int newfiledes) 518 { 519 file_attr_t *fap; 520 521 if (filedes < 0 || newfiledes < 0) 522 return (EBADF); 523 if ((fap = lmalloc(sizeof (*fap))) == NULL) 524 return (ENOMEM); 525 526 fap->fa_type = FA_DUP2; 527 fap->fa_filedes = filedes; 528 fap->fa_newfiledes = newfiledes; 529 add_file_attr(file_actions, fap); 530 531 return (0); 532 } 533 534 #pragma weak posix_spawnattr_init = \ 535 _posix_spawnattr_init 536 int 537 _posix_spawnattr_init( 538 posix_spawnattr_t *attr) 539 { 540 if ((attr->__spawn_attrp = lmalloc(sizeof (posix_spawnattr_t))) == NULL) 541 return (ENOMEM); 542 /* 543 * Add default stuff here? 544 */ 545 return (0); 546 } 547 548 #pragma weak posix_spawnattr_destroy = \ 549 _posix_spawnattr_destroy 550 int 551 _posix_spawnattr_destroy( 552 posix_spawnattr_t *attr) 553 { 554 spawn_attr_t *sap = attr->__spawn_attrp; 555 556 if (sap == NULL) 557 return (EINVAL); 558 559 /* 560 * deallocate stuff here? 561 */ 562 lfree(sap, sizeof (*sap)); 563 attr->__spawn_attrp = NULL; 564 return (0); 565 } 566 567 #pragma weak posix_spawnattr_setflags = \ 568 _posix_spawnattr_setflags 569 int 570 _posix_spawnattr_setflags( 571 posix_spawnattr_t *attr, 572 short flags) 573 { 574 spawn_attr_t *sap = attr->__spawn_attrp; 575 576 if (sap == NULL || 577 (flags & ~ALL_POSIX_SPAWN_FLAGS)) 578 return (EINVAL); 579 580 sap->sa_psflags = flags; 581 return (0); 582 } 583 584 #pragma weak posix_spawnattr_getflags = \ 585 _posix_spawnattr_getflags 586 int 587 _posix_spawnattr_getflags( 588 const posix_spawnattr_t *attr, 589 short *flags) 590 { 591 spawn_attr_t *sap = attr->__spawn_attrp; 592 593 if (sap == NULL) 594 return (EINVAL); 595 596 *flags = sap->sa_psflags; 597 return (0); 598 } 599 600 #pragma weak posix_spawnattr_setpgroup = \ 601 _posix_spawnattr_setpgroup 602 int 603 _posix_spawnattr_setpgroup( 604 posix_spawnattr_t *attr, 605 pid_t pgroup) 606 { 607 spawn_attr_t *sap = attr->__spawn_attrp; 608 609 if (sap == NULL) 610 return (EINVAL); 611 612 sap->sa_pgroup = pgroup; 613 return (0); 614 } 615 616 #pragma weak posix_spawnattr_getpgroup = \ 617 _posix_spawnattr_getpgroup 618 int 619 _posix_spawnattr_getpgroup( 620 const posix_spawnattr_t *attr, 621 pid_t *pgroup) 622 { 623 spawn_attr_t *sap = attr->__spawn_attrp; 624 625 if (sap == NULL) 626 return (EINVAL); 627 628 *pgroup = sap->sa_pgroup; 629 return (0); 630 } 631 632 #pragma weak posix_spawnattr_setschedparam = \ 633 _posix_spawnattr_setschedparam 634 int 635 _posix_spawnattr_setschedparam( 636 posix_spawnattr_t *attr, 637 const struct sched_param *schedparam) 638 { 639 spawn_attr_t *sap = attr->__spawn_attrp; 640 641 if (sap == NULL) 642 return (EINVAL); 643 644 /* 645 * Check validity? 646 */ 647 sap->sa_priority = schedparam->sched_priority; 648 return (0); 649 } 650 651 #pragma weak posix_spawnattr_getschedparam = \ 652 _posix_spawnattr_getschedparam 653 int 654 _posix_spawnattr_getschedparam( 655 const posix_spawnattr_t *attr, 656 struct sched_param *schedparam) 657 { 658 spawn_attr_t *sap = attr->__spawn_attrp; 659 660 if (sap == NULL) 661 return (EINVAL); 662 663 schedparam->sched_priority = sap->sa_priority; 664 return (0); 665 } 666 667 #pragma weak posix_spawnattr_setschedpolicy = \ 668 _posix_spawnattr_setschedpolicy 669 int 670 _posix_spawnattr_setschedpolicy( 671 posix_spawnattr_t *attr, 672 int schedpolicy) 673 { 674 spawn_attr_t *sap = attr->__spawn_attrp; 675 676 if (sap == NULL || schedpolicy == SCHED_SYS) 677 return (EINVAL); 678 679 /* 680 * Cache the policy information for later use 681 * by the vfork() child of posix_spawn(). 682 */ 683 if (get_info_by_policy(schedpolicy) == NULL) 684 return (errno); 685 686 sap->sa_schedpolicy = schedpolicy; 687 return (0); 688 } 689 690 #pragma weak posix_spawnattr_getschedpolicy = \ 691 _posix_spawnattr_getschedpolicy 692 int 693 _posix_spawnattr_getschedpolicy( 694 const posix_spawnattr_t *attr, 695 int *schedpolicy) 696 { 697 spawn_attr_t *sap = attr->__spawn_attrp; 698 699 if (sap == NULL) 700 return (EINVAL); 701 702 *schedpolicy = sap->sa_schedpolicy; 703 return (0); 704 } 705 706 #pragma weak posix_spawnattr_setsigdefault = \ 707 _posix_spawnattr_setsigdefault 708 int 709 _posix_spawnattr_setsigdefault( 710 posix_spawnattr_t *attr, 711 const sigset_t *sigdefault) 712 { 713 spawn_attr_t *sap = attr->__spawn_attrp; 714 715 if (sap == NULL) 716 return (EINVAL); 717 718 sap->sa_sigdefault = *sigdefault; 719 return (0); 720 } 721 722 #pragma weak posix_spawnattr_getsigdefault = \ 723 _posix_spawnattr_getsigdefault 724 int 725 _posix_spawnattr_getsigdefault( 726 const posix_spawnattr_t *attr, 727 sigset_t *sigdefault) 728 { 729 spawn_attr_t *sap = attr->__spawn_attrp; 730 731 if (sap == NULL) 732 return (EINVAL); 733 734 *sigdefault = sap->sa_sigdefault; 735 return (0); 736 } 737 738 #pragma weak posix_spawnattr_setsigmask = \ 739 _posix_spawnattr_setsigmask 740 int 741 _posix_spawnattr_setsigmask( 742 posix_spawnattr_t *attr, 743 const sigset_t *sigmask) 744 { 745 spawn_attr_t *sap = attr->__spawn_attrp; 746 747 if (sap == NULL) 748 return (EINVAL); 749 750 sap->sa_sigmask = *sigmask; 751 return (0); 752 } 753 754 #pragma weak posix_spawnattr_getsigmask = \ 755 _posix_spawnattr_getsigmask 756 int 757 _posix_spawnattr_getsigmask( 758 const posix_spawnattr_t *attr, 759 sigset_t *sigmask) 760 { 761 spawn_attr_t *sap = attr->__spawn_attrp; 762 763 if (sap == NULL) 764 return (EINVAL); 765 766 *sigmask = sap->sa_sigmask; 767 return (0); 768 } 769