spawn.c revision 657b1f3d64bcf8eaa2385dba72a6047f089433b2
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "lint.h"
#include "thr_uberdata.h"
#include <sys/libc_kernel.h>
#include <sys/procset.h>
#include <sys/rtpriocntl.h>
#include <sys/tspriocntl.h>
#include <sys/fork.h>
#include <sys/rt.h>
#include <sys/ts.h>
#include <alloca.h>
#include <spawn.h>
#include "rtsched.h"
#define ALL_POSIX_SPAWN_FLAGS \
(POSIX_SPAWN_RESETIDS | \
POSIX_SPAWN_SETPGROUP | \
POSIX_SPAWN_SETSIGDEF | \
POSIX_SPAWN_SETSIGMASK | \
POSIX_SPAWN_SETSCHEDPARAM | \
POSIX_SPAWN_SETSCHEDULER | \
POSIX_SPAWN_NOSIGCHLD_NP | \
POSIX_SPAWN_WAITPID_NP)
typedef struct {
short sa_psflags; /* POSIX_SPAWN_* flags */
pri_t sa_priority;
int sa_schedpolicy;
pid_t sa_pgroup;
sigset_t sa_sigdefault;
sigset_t sa_sigmask;
} spawn_attr_t;
typedef struct file_attr {
struct file_attr *fa_next; /* circular list of file actions */
struct file_attr *fa_prev;
enum {FA_OPEN, FA_CLOSE, FA_DUP2} fa_type;
uint_t fa_pathsize; /* size of fa_path[] array */
char *fa_path; /* copied pathname for open() */
int fa_oflag; /* oflag for open() */
mode_t fa_mode; /* mode for open() */
int fa_filedes; /* file descriptor for open()/close() */
int fa_newfiledes; /* new file descriptor for dup2() */
} file_attr_t;
extern struct pcclass ts_class, rt_class;
extern pid_t _vforkx(int);
#pragma unknown_control_flow(_vforkx)
extern void *_private_memset(void *, int, size_t);
extern int __lwp_sigmask(int, const sigset_t *, sigset_t *);
extern int __open(const char *, int, mode_t);
extern int __sigaction(int, const struct sigaction *, struct sigaction *);
extern int _private_close(int);
extern int _private_execve(const char *, char *const *, char *const *);
extern int _private_fcntl(int, int, intptr_t);
extern int _private_setgid(gid_t);
extern int _private_setpgid(pid_t, pid_t);
extern int _private_setuid(uid_t);
extern int _private_sigismember(sigset_t *, int);
extern gid_t _private_getgid(void);
extern uid_t _private_getuid(void);
extern uid_t _private_geteuid(void);
extern void _private_exit(int);
/*
* We call this function rather than priocntl() because we must not call
* any function that is exported from libc while in the child of vfork().
* Also, we are not using PC_GETXPARMS or PC_SETXPARMS so we can use
* the simple call to __priocntlset() rather than the varargs version.
*/
static long
_private_priocntl(idtype_t idtype, id_t id, int cmd, caddr_t arg)
{
extern long _private__priocntlset(int, procset_t *, int, caddr_t, ...);
procset_t procset;
setprocset(&procset, POP_AND, idtype, id, P_ALL, 0);
return (_private__priocntlset(PC_VERSION, &procset, cmd, arg, 0));
}
/*
* The following two functions are blatently stolen from
* sched_setscheduler() and sched_setparam() in librt.
* This would be a lot easier if librt were folded into libc.
*/
static int
setscheduler(int policy, pri_t prio)
{
pcparms_t pcparm;
tsinfo_t *tsi;
tsparms_t *tsp;
int scale;
switch (policy) {
case SCHED_FIFO:
case SCHED_RR:
if (prio < rt_class.pcc_primin || prio > rt_class.pcc_primax) {
errno = EINVAL;
return (-1);
}
pcparm.pc_cid = rt_class.pcc_info.pc_cid;
((rtparms_t *)pcparm.pc_clparms)->rt_pri = prio;
((rtparms_t *)pcparm.pc_clparms)->rt_tqnsecs =
(policy == SCHED_RR ? RT_TQDEF : RT_TQINF);
break;
case SCHED_OTHER:
pcparm.pc_cid = ts_class.pcc_info.pc_cid;
tsi = (tsinfo_t *)ts_class.pcc_info.pc_clinfo;
scale = tsi->ts_maxupri;
tsp = (tsparms_t *)pcparm.pc_clparms;
tsp->ts_uprilim = tsp->ts_upri = -(scale * prio) / 20;
break;
default:
errno = EINVAL;
return (-1);
}
return (_private_priocntl(P_PID, P_MYID,
PC_SETPARMS, (caddr_t)&pcparm));
}
static int
setparam(pcparms_t *pcparmp, pri_t prio)
{
tsparms_t *tsp;
tsinfo_t *tsi;
int scale;
if (pcparmp->pc_cid == rt_class.pcc_info.pc_cid) {
/* SCHED_FIFO or SCHED_RR policy */
if (prio < rt_class.pcc_primin || prio > rt_class.pcc_primax) {
errno = EINVAL;
return (-1);
}
((rtparms_t *)pcparmp->pc_clparms)->rt_tqnsecs = RT_NOCHANGE;
((rtparms_t *)pcparmp->pc_clparms)->rt_pri = prio;
} else if (pcparmp->pc_cid == ts_class.pcc_info.pc_cid) {
/* SCHED_OTHER policy */
tsi = (tsinfo_t *)ts_class.pcc_info.pc_clinfo;
scale = tsi->ts_maxupri;
tsp = (tsparms_t *)pcparmp->pc_clparms;
tsp->ts_uprilim = tsp->ts_upri = -(scale * prio) / 20;
} else {
errno = EINVAL;
return (-1);
}
return (_private_priocntl(P_PID, P_MYID,
PC_SETPARMS, (caddr_t)pcparmp));
}
static int
perform_flag_actions(spawn_attr_t *sap)
{
int sig;
if (sap->sa_psflags & POSIX_SPAWN_SETSIGMASK) {
(void) __lwp_sigmask(SIG_SETMASK, &sap->sa_sigmask, NULL);
}
if (sap->sa_psflags & POSIX_SPAWN_SETSIGDEF) {
struct sigaction sigdfl;
(void) _private_memset(&sigdfl, 0, sizeof (sigdfl));
for (sig = 1; sig < NSIG; sig++) {
if (_private_sigismember(&sap->sa_sigdefault, sig))
(void) __sigaction(sig, &sigdfl, NULL);
}
}
if (sap->sa_psflags & POSIX_SPAWN_RESETIDS) {
if (_private_setgid(_private_getgid()) != 0 ||
_private_setuid(_private_getuid()) != 0)
return (errno);
}
if (sap->sa_psflags & POSIX_SPAWN_SETPGROUP) {
if (_private_setpgid(0, sap->sa_pgroup) != 0)
return (errno);
}
if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDULER) {
if (setscheduler(sap->sa_schedpolicy, sap->sa_priority) != 0)
return (errno);
} else if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDPARAM) {
/*
* Get the process's current scheduling parameters,
* then modify to set the new priority.
*/
pcparms_t pcparm;
pcparm.pc_cid = PC_CLNULL;
if (_private_priocntl(P_PID, P_MYID,
PC_GETPARMS, (caddr_t)&pcparm) == -1)
return (errno);
if (setparam(&pcparm, sap->sa_priority) != 0)
return (errno);
}
return (0);
}
static int
perform_file_actions(file_attr_t *fap)
{
file_attr_t *froot = fap;
int fd;
do {
switch (fap->fa_type) {
case FA_OPEN:
fd = __open(fap->fa_path, fap->fa_oflag, fap->fa_mode);
if (fd < 0)
return (errno);
if (fd != fap->fa_filedes) {
if (_private_fcntl(fd, F_DUP2FD,
fap->fa_filedes) < 0)
return (errno);
(void) _private_close(fd);
}
break;
case FA_CLOSE:
if (_private_close(fap->fa_filedes) == -1)
return (errno);
break;
case FA_DUP2:
fd = _private_fcntl(fap->fa_filedes, F_DUP2FD,
fap->fa_newfiledes);
if (fd < 0)
return (errno);
break;
}
} while ((fap = fap->fa_next) != froot);
return (0);
}
static int
forkflags(spawn_attr_t *sap)
{
int flags = 0;
if (sap != NULL) {
if (sap->sa_psflags & POSIX_SPAWN_NOSIGCHLD_NP)
flags |= FORK_NOSIGCHLD;
if (sap->sa_psflags & POSIX_SPAWN_WAITPID_NP)
flags |= FORK_WAITPID;
}
return (flags);
}
/*
* set_error() / get_error() are used to guarantee that the local variable
* 'error' is set correctly in memory on return from vfork() in the parent.
*/
static int
set_error(int *errp, int err)
{
return (*errp = err);
}
static int
get_error(int *errp)
{
return (*errp);
}
/*
* For MT safety, do not invoke the dynamic linker after calling vfork().
* If some other thread was in the dynamic linker when this thread's parent
* called vfork() then the dynamic linker's lock would still be held here
* (with a defunct owner) and we would deadlock ourself if we invoked it.
*
* Therefore, all of the functions we call here after returning from
* _vforkx() in the child are not and must never be exported from libc
* as global symbols. To do so would risk invoking the dynamic linker.
*/
#pragma weak posix_spawn = _posix_spawn
int
_posix_spawn(
pid_t *pidp,
const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[],
char *const envp[])
{
spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL;
file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL;
int error; /* this will be set by the child */
pid_t pid;
if (attrp != NULL && sap == NULL)
return (EINVAL);
switch (pid = _vforkx(forkflags(sap))) {
case 0: /* child */
break;
case -1: /* parent, failure */
return (errno);
default: /* parent, success */
/*
* We don't get here until the child exec()s or exit()s
*/
if (pidp != NULL && get_error(&error) == 0)
*pidp = pid;
return (get_error(&error));
}
if (sap != NULL)
if (set_error(&error, perform_flag_actions(sap)) != 0)
_private_exit(_EVAPORATE);
if (fap != NULL)
if (set_error(&error, perform_file_actions(fap)) != 0)
_private_exit(_EVAPORATE);
(void) set_error(&error, 0);
(void) _private_execve(path, argv, envp);
(void) set_error(&error, errno);
_private_exit(_EVAPORATE);
return (0); /* not reached */
}
/*
* Much of posix_spawnp() blatently stolen from execvp() (port/gen/execvp.c).
*/
extern int __xpg4; /* defined in xpg4.c; 0 if not xpg4-compiled program */
static const char *
execat(const char *s1, const char *s2, char *si)
{
int cnt = PATH_MAX + 1;
char *s;
char c;
for (s = si; (c = *s1) != '\0' && c != ':'; s1++) {
if (cnt > 0) {
*s++ = c;
cnt--;
}
}
if (si != s && cnt > 0) {
*s++ = '/';
cnt--;
}
for (; (c = *s2) != '\0' && cnt > 0; s2++) {
*s++ = c;
cnt--;
}
*s = '\0';
return (*s1? ++s1: NULL);
}
#pragma weak posix_spawnp = _posix_spawnp
/* ARGSUSED */
int
_posix_spawnp(
pid_t *pidp,
const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[],
char *const envp[])
{
spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL;
file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL;
const char *pathstr = (strchr(file, '/') == NULL)? getenv("PATH") : "";
int xpg4 = __xpg4;
int error; /* this will be set by the child */
char path[PATH_MAX+4];
const char *cp;
pid_t pid;
char **newargs;
int argc;
int i;
static const char *sun_path = "/bin/sh";
static const char *xpg4_path = "/usr/xpg4/bin/sh";
static const char *shell = "sh";
if (attrp != NULL && sap == NULL)
return (EINVAL);
if (*file == '\0')
return (EACCES);
/*
* We may need to invoke the shell with a slightly modified
* argv[] array. To do this we need to preallocate the array.
* We must call alloca() before calling vfork() because doing
* it after vfork() (in the child) would corrupt the parent.
*/
for (argc = 0; argv[argc] != NULL; argc++)
continue;
newargs = alloca((argc + 2) * sizeof (char *));
switch (pid = _vforkx(forkflags(sap))) {
case 0: /* child */
break;
case -1: /* parent, failure */
return (errno);
default: /* parent, success */
/*
* We don't get here until the child exec()s or exit()s
*/
if (pidp != NULL && get_error(&error) == 0)
*pidp = pid;
return (get_error(&error));
}
if (sap != NULL)
if (set_error(&error, perform_flag_actions(sap)) != 0)
_private_exit(_EVAPORATE);
if (fap != NULL)
if (set_error(&error, perform_file_actions(fap)) != 0)
_private_exit(_EVAPORATE);
if (pathstr == NULL) {
/*
* XPG4: pathstr is equivalent to _CS_PATH, except that
* :/usr/sbin is appended when root, and pathstr must end
* with a colon when not root. Keep these paths in sync
* with _CS_PATH in confstr.c. Note that pathstr must end
* with a colon when not root so that when file doesn't
* contain '/', the last call to execat() will result in an
* attempt to execv file from the current directory.
*/
if (_private_geteuid() == 0 || _private_getuid() == 0) {
if (!xpg4)
pathstr = "/usr/sbin:/usr/ccs/bin:/usr/bin";
else
pathstr = "/usr/xpg4/bin:/usr/ccs/bin:"
"/usr/bin:/opt/SUNWspro/bin:/usr/sbin";
} else {
if (!xpg4)
pathstr = "/usr/ccs/bin:/usr/bin:";
else
pathstr = "/usr/xpg4/bin:/usr/ccs/bin:"
"/usr/bin:/opt/SUNWspro/bin:";
}
}
cp = pathstr;
do {
cp = execat(cp, file, path);
/*
* 4025035 and 4038378
* if a filename begins with a "-" prepend "./" so that
* the shell can't interpret it as an option
*/
if (*path == '-') {
char *s;
for (s = path; *s != '\0'; s++)
continue;
for (; s >= path; s--)
*(s + 2) = *s;
path[0] = '.';
path[1] = '/';
}
(void) set_error(&error, 0);
(void) _private_execve(path, argv, envp);
if (set_error(&error, errno) == ENOEXEC) {
newargs[0] = (char *)shell;
newargs[1] = path;
for (i = 1; i <= argc; i++)
newargs[i + 1] = argv[i];
(void) set_error(&error, 0);
(void) _private_execve(xpg4? xpg4_path : sun_path,
newargs, envp);
(void) set_error(&error, errno);
_private_exit(_EVAPORATE);
}
} while (cp);
_private_exit(_EVAPORATE);
return (0); /* not reached */
}
#pragma weak posix_spawn_file_actions_init = \
_posix_spawn_file_actions_init
int
_posix_spawn_file_actions_init(
posix_spawn_file_actions_t *file_actions)
{
file_actions->__file_attrp = NULL;
return (0);
}
#pragma weak posix_spawn_file_actions_destroy = \
_posix_spawn_file_actions_destroy
int
_posix_spawn_file_actions_destroy(
posix_spawn_file_actions_t *file_actions)
{
file_attr_t *froot = file_actions->__file_attrp;
file_attr_t *fap;
file_attr_t *next;
if ((fap = froot) != NULL) {
do {
next = fap->fa_next;
if (fap-> fa_type == FA_OPEN)
lfree(fap->fa_path, fap->fa_pathsize);
lfree(fap, sizeof (*fap));
} while ((fap = next) != froot);
}
file_actions->__file_attrp = NULL;
return (0);
}
static void
add_file_attr(posix_spawn_file_actions_t *file_actions, file_attr_t *fap)
{
file_attr_t *froot = file_actions->__file_attrp;
if (froot == NULL) {
fap->fa_next = fap->fa_prev = fap;
file_actions->__file_attrp = fap;
} else {
fap->fa_next = froot;
fap->fa_prev = froot->fa_prev;
froot->fa_prev->fa_next = fap;
froot->fa_prev = fap;
}
}
#pragma weak posix_spawn_file_actions_addopen = \
_posix_spawn_file_actions_addopen
int
_posix_spawn_file_actions_addopen(
posix_spawn_file_actions_t *file_actions,
int filedes,
const char *path,
int oflag,
mode_t mode)
{
file_attr_t *fap;
if (filedes < 0)
return (EBADF);
if ((fap = lmalloc(sizeof (*fap))) == NULL)
return (ENOMEM);
fap->fa_pathsize = strlen(path) + 1;
if ((fap->fa_path = lmalloc(fap->fa_pathsize)) == NULL) {
lfree(fap, sizeof (*fap));
return (ENOMEM);
}
(void) strcpy(fap->fa_path, path);
fap->fa_type = FA_OPEN;
fap->fa_oflag = oflag;
fap->fa_mode = mode;
fap->fa_filedes = filedes;
add_file_attr(file_actions, fap);
return (0);
}
#pragma weak posix_spawn_file_actions_addclose = \
_posix_spawn_file_actions_addclose
int
_posix_spawn_file_actions_addclose(
posix_spawn_file_actions_t *file_actions,
int filedes)
{
file_attr_t *fap;
if (filedes < 0)
return (EBADF);
if ((fap = lmalloc(sizeof (*fap))) == NULL)
return (ENOMEM);
fap->fa_type = FA_CLOSE;
fap->fa_filedes = filedes;
add_file_attr(file_actions, fap);
return (0);
}
#pragma weak posix_spawn_file_actions_adddup2 = \
_posix_spawn_file_actions_adddup2
int
_posix_spawn_file_actions_adddup2(
posix_spawn_file_actions_t *file_actions,
int filedes,
int newfiledes)
{
file_attr_t *fap;
if (filedes < 0 || newfiledes < 0)
return (EBADF);
if ((fap = lmalloc(sizeof (*fap))) == NULL)
return (ENOMEM);
fap->fa_type = FA_DUP2;
fap->fa_filedes = filedes;
fap->fa_newfiledes = newfiledes;
add_file_attr(file_actions, fap);
return (0);
}
#pragma weak posix_spawnattr_init = \
_posix_spawnattr_init
int
_posix_spawnattr_init(
posix_spawnattr_t *attr)
{
if ((attr->__spawn_attrp = lmalloc(sizeof (posix_spawnattr_t))) == NULL)
return (ENOMEM);
/*
* Add default stuff here?
*/
return (0);
}
#pragma weak posix_spawnattr_destroy = \
_posix_spawnattr_destroy
int
_posix_spawnattr_destroy(
posix_spawnattr_t *attr)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
/*
* deallocate stuff here?
*/
lfree(sap, sizeof (*sap));
attr->__spawn_attrp = NULL;
return (0);
}
#pragma weak posix_spawnattr_setflags = \
_posix_spawnattr_setflags
int
_posix_spawnattr_setflags(
posix_spawnattr_t *attr,
short flags)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL ||
(flags & ~ALL_POSIX_SPAWN_FLAGS))
return (EINVAL);
if (flags & (POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER)) {
/*
* Populate ts_class and rt_class.
* We will need them in the child of vfork().
*/
if (rt_class.pcc_state == 0)
(void) get_info_by_policy(SCHED_FIFO);
if (ts_class.pcc_state == 0)
(void) get_info_by_policy(SCHED_OTHER);
}
sap->sa_psflags = flags;
return (0);
}
#pragma weak posix_spawnattr_getflags = \
_posix_spawnattr_getflags
int
_posix_spawnattr_getflags(
const posix_spawnattr_t *attr,
short *flags)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*flags = sap->sa_psflags;
return (0);
}
#pragma weak posix_spawnattr_setpgroup = \
_posix_spawnattr_setpgroup
int
_posix_spawnattr_setpgroup(
posix_spawnattr_t *attr,
pid_t pgroup)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
sap->sa_pgroup = pgroup;
return (0);
}
#pragma weak posix_spawnattr_getpgroup = \
_posix_spawnattr_getpgroup
int
_posix_spawnattr_getpgroup(
const posix_spawnattr_t *attr,
pid_t *pgroup)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*pgroup = sap->sa_pgroup;
return (0);
}
#pragma weak posix_spawnattr_setschedparam = \
_posix_spawnattr_setschedparam
int
_posix_spawnattr_setschedparam(
posix_spawnattr_t *attr,
const struct sched_param *schedparam)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
/*
* Check validity?
*/
sap->sa_priority = schedparam->sched_priority;
return (0);
}
#pragma weak posix_spawnattr_getschedparam = \
_posix_spawnattr_getschedparam
int
_posix_spawnattr_getschedparam(
const posix_spawnattr_t *attr,
struct sched_param *schedparam)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
schedparam->sched_priority = sap->sa_priority;
return (0);
}
#pragma weak posix_spawnattr_setschedpolicy = \
_posix_spawnattr_setschedpolicy
int
_posix_spawnattr_setschedpolicy(
posix_spawnattr_t *attr,
int schedpolicy)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
switch (schedpolicy) {
case SCHED_OTHER:
case SCHED_FIFO:
case SCHED_RR:
break;
default:
return (EINVAL);
}
sap->sa_schedpolicy = schedpolicy;
return (0);
}
#pragma weak posix_spawnattr_getschedpolicy = \
_posix_spawnattr_getschedpolicy
int
_posix_spawnattr_getschedpolicy(
const posix_spawnattr_t *attr,
int *schedpolicy)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*schedpolicy = sap->sa_schedpolicy;
return (0);
}
#pragma weak posix_spawnattr_setsigdefault = \
_posix_spawnattr_setsigdefault
int
_posix_spawnattr_setsigdefault(
posix_spawnattr_t *attr,
const sigset_t *sigdefault)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
sap->sa_sigdefault = *sigdefault;
return (0);
}
#pragma weak posix_spawnattr_getsigdefault = \
_posix_spawnattr_getsigdefault
int
_posix_spawnattr_getsigdefault(
const posix_spawnattr_t *attr,
sigset_t *sigdefault)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*sigdefault = sap->sa_sigdefault;
return (0);
}
#pragma weak posix_spawnattr_setsigmask = \
_posix_spawnattr_setsigmask
int
_posix_spawnattr_setsigmask(
posix_spawnattr_t *attr,
const sigset_t *sigmask)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
sap->sa_sigmask = *sigmask;
return (0);
}
#pragma weak posix_spawnattr_getsigmask = \
_posix_spawnattr_getsigmask
int
_posix_spawnattr_getsigmask(
const posix_spawnattr_t *attr,
sigset_t *sigmask)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*sigmask = sap->sa_sigmask;
return (0);
}