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