#include "lint.h"
#include "thr_uberdata.h"
#include <sys/libc_kernel.h>
#include <sys/procset.h>
#include <sys/fork.h>
#include <dirent.h>
#include <alloca.h>
#include <spawn.h>
#include <paths.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_SETSID | \
POSIX_SPAWN_SETSIGIGN_NP | \
POSIX_SPAWN_NOSIGCHLD_NP | \
POSIX_SPAWN_WAITPID_NP | \
POSIX_SPAWN_NOEXECERR_NP)
typedef struct {
int sa_psflags;
int sa_priority;
int sa_schedpolicy;
pid_t sa_pgroup;
sigset_t sa_sigdefault;
sigset_t sa_sigignore;
sigset_t sa_sigmask;
} spawn_attr_t;
typedef enum file_action {
FA_OPEN,
FA_CLOSE,
FA_DUP2,
FA_CLOSEFROM,
FA_CHDIR,
FA_FCHDIR
} file_action_t;
typedef struct file_attr {
struct file_attr *fa_next;
struct file_attr *fa_prev;
file_action_t fa_type;
int fa_need_dirbuf;
char *fa_path;
uint_t fa_pathsize;
int fa_oflag;
mode_t fa_mode;
int fa_filedes;
int fa_newfiledes;
} file_attr_t;
#if defined(_LP64)
#define __open64 __open
#define getdents64 getdents
#define dirent64_t dirent_t
#else
extern int getdents64(int, dirent64_t *, size_t);
#endif
extern const char **_environ;
static int
spawn_closefrom(int lowfd, void *buf)
{
int procfd;
int fd;
int buflen;
dirent64_t *dp;
dirent64_t *dpend;
if (lowfd < 0)
lowfd = 0;
(void) __close(lowfd++);
if ((procfd = __open64("/proc/self/fd", O_RDONLY, 0)) < 0) {
return (-1);
}
for (;;) {
dp = (dirent64_t *)buf;
if ((buflen = getdents64(procfd, dp, DIRBUF)) <= 0) {
(void) __close(procfd);
break;
}
dpend = (dirent64_t *)((uintptr_t)buf + buflen);
do {
if (dp->d_name[0] != '.' &&
(fd = atoi(dp->d_name)) != procfd &&
fd >= lowfd)
(void) __close(fd);
dp = (dirent64_t *)((uintptr_t)dp + dp->d_reclen);
} while (dp < dpend);
}
return (0);
}
static int
perform_flag_actions(spawn_attr_t *sap)
{
int sig;
struct sigaction action;
if (sap->sa_psflags & POSIX_SPAWN_SETSIGMASK) {
(void) __lwp_sigmask(SIG_SETMASK, &sap->sa_sigmask);
}
if (sap->sa_psflags & POSIX_SPAWN_SETSIGIGN_NP) {
(void) memset(&action, 0, sizeof (action));
action.sa_handler = SIG_IGN;
for (sig = 1; sig < NSIG; sig++) {
if (sigismember(&sap->sa_sigignore, sig))
(void) __sigaction(sig, &action, NULL);
}
}
if (sap->sa_psflags & POSIX_SPAWN_SETSIGDEF) {
(void) memset(&action, 0, sizeof (action));
action.sa_handler = SIG_DFL;
for (sig = 1; sig < NSIG; sig++) {
if (sigismember(&sap->sa_sigdefault, sig))
(void) __sigaction(sig, &action, NULL);
}
}
if (sap->sa_psflags & POSIX_SPAWN_RESETIDS) {
if (setgid(getgid()) != 0 || setuid(getuid()) != 0)
return (errno);
}
if (sap->sa_psflags & POSIX_SPAWN_SETSID) {
if (setsid() == (pid_t)-1) {
return (errno);
}
}
if (sap->sa_psflags & POSIX_SPAWN_SETPGROUP) {
if (setpgid(0, sap->sa_pgroup) != 0)
return (errno);
}
if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDULER) {
if (setparam(P_LWPID, P_MYID,
sap->sa_schedpolicy, sap->sa_priority) == -1)
return (errno);
} else if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDPARAM) {
if (setprio(P_LWPID, P_MYID, sap->sa_priority, NULL) == -1)
return (errno);
}
return (0);
}
static int
perform_file_actions(file_attr_t *fap, void *dirbuf)
{
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 (__fcntl(fd, F_DUP2FD, fap->fa_filedes) < 0)
return (errno);
(void) __close(fd);
}
break;
case FA_CHDIR:
if (chdir(fap->fa_path) == -1)
return (errno);
break;
case FA_CLOSE:
if (__close(fap->fa_filedes) == -1 &&
errno != EBADF)
return (errno);
break;
case FA_DUP2:
fd = __fcntl(fap->fa_filedes, F_DUP2FD,
fap->fa_newfiledes);
if (fd < 0)
return (errno);
break;
case FA_CLOSEFROM:
if (spawn_closefrom(fap->fa_filedes, dirbuf))
return (errno);
break;
case FA_FCHDIR:
if (fchdir(fap->fa_filedes) == -1)
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);
}
static int
set_error(int *errp, int err)
{
return (*errp = err);
}
static int
get_error(int *errp)
{
return (*errp);
}
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;
void *dirbuf = NULL;
int error;
pid_t pid;
if (attrp != NULL && sap == NULL)
return (EINVAL);
if (fap != NULL && fap->fa_need_dirbuf) {
if ((dirbuf = lmalloc(DIRBUF)) == NULL)
return (ENOMEM);
}
switch (pid = vforkx(forkflags(sap))) {
case 0:
break;
case -1:
if (dirbuf)
lfree(dirbuf, DIRBUF);
return (errno);
default:
if (pidp != NULL && get_error(&error) == 0)
*pidp = pid;
if (dirbuf)
lfree(dirbuf, DIRBUF);
return (get_error(&error));
}
if (sap != NULL)
if (set_error(&error, perform_flag_actions(sap)) != 0)
_exit(_EVAPORATE);
if (fap != NULL)
if (set_error(&error, perform_file_actions(fap, dirbuf)) != 0)
_exit(_EVAPORATE);
(void) set_error(&error, 0);
(void) execve(path, argv, envp);
if (sap != NULL && (sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP))
_exit(127);
(void) set_error(&error, errno);
_exit(_EVAPORATE);
return (0);
}
extern int libc__xpg4;
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);
}
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;
void *dirbuf = NULL;
const char *pathstr = (strchr(file, '/') == NULL)? getenv("PATH") : "";
int xpg4 = libc__xpg4;
int error = 0;
char path[PATH_MAX+4];
const char *cp;
pid_t pid;
char **newargs;
int argc;
int i;
if (attrp != NULL && sap == NULL)
return (EINVAL);
if (*file == '\0')
return (EACCES);
if (fap != NULL && fap->fa_need_dirbuf) {
if ((dirbuf = lmalloc(DIRBUF)) == NULL)
return (ENOMEM);
}
for (argc = 0; argv[argc] != NULL; argc++)
continue;
newargs = alloca((argc + 2) * sizeof (char *));
switch (pid = vforkx(forkflags(sap))) {
case 0:
break;
case -1:
if (dirbuf)
lfree(dirbuf, DIRBUF);
return (errno);
default:
if (pidp != NULL && get_error(&error) == 0)
*pidp = pid;
if (dirbuf)
lfree(dirbuf, DIRBUF);
return (get_error(&error));
}
if (sap != NULL)
if (set_error(&error, perform_flag_actions(sap)) != 0)
_exit(_EVAPORATE);
if (fap != NULL)
if (set_error(&error, perform_file_actions(fap, dirbuf)) != 0)
_exit(_EVAPORATE);
if (pathstr == NULL) {
if (geteuid() == 0 || 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);
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) execve(path, argv, envp);
if (set_error(&error, errno) == ENOEXEC) {
newargs[0] = "sh";
newargs[1] = path;
for (i = 1; i <= argc; i++)
newargs[i + 1] = argv[i];
(void) set_error(&error, 0);
(void) execve(_PATH_BSHELL, newargs, envp);
if (sap != NULL &&
(sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP))
_exit(127);
(void) set_error(&error, errno);
_exit(_EVAPORATE);
}
} while (cp);
if (sap != NULL &&
(sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP)) {
(void) set_error(&error, 0);
_exit(127);
}
_exit(_EVAPORATE);
return (0);
}
int
posix_spawn_file_actions_init(
posix_spawn_file_actions_t *file_actions)
{
file_actions->__file_attrp = NULL;
return (0);
}
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 || fap->fa_type == FA_CHDIR)
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 = froot = fap;
} else {
fap->fa_next = froot;
fap->fa_prev = froot->fa_prev;
froot->fa_prev->fa_next = fap;
froot->fa_prev = fap;
}
if (fap->fa_type == FA_CLOSEFROM)
froot->fa_need_dirbuf = 1;
}
int
posix_spawn_file_actions_addopen(
posix_spawn_file_actions_t *restrict file_actions,
int filedes,
const char *restrict 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) memcpy(fap->fa_path, path, fap->fa_pathsize);
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);
}
int
posix_spawn_file_actions_addclose(
posix_spawn_file_actions_t *restrict 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);
}
int
posix_spawn_file_actions_adddup2(
posix_spawn_file_actions_t *restrict 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);
}
int
posix_spawn_file_actions_addclosefrom_np(
posix_spawn_file_actions_t *restrict file_actions,
int lowfiledes)
{
file_attr_t *fap;
if (lowfiledes < 0)
return (EBADF);
if ((fap = lmalloc(sizeof (*fap))) == NULL)
return (ENOMEM);
fap->fa_type = FA_CLOSEFROM;
fap->fa_filedes = lowfiledes;
add_file_attr(file_actions, fap);
return (0);
}
int
posix_spawn_file_actions_addchdir(
posix_spawn_file_actions_t *restrict file_actions,
const char *restrict path)
{
file_attr_t *fap;
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) memcpy(fap->fa_path, path, fap->fa_pathsize);
fap->fa_type = FA_CHDIR;
add_file_attr(file_actions, fap);
return (0);
}
int
posix_spawn_file_actions_addchdir_np(
posix_spawn_file_actions_t *restrict file_actions,
const char *restrict path)
{
return (posix_spawn_file_actions_addchdir(file_actions, path));
}
int
posix_spawn_file_actions_addfchdir(
posix_spawn_file_actions_t *restrict file_actions,
int fd)
{
file_attr_t *fap;
if (fd < 0)
return (EBADF);
if ((fap = lmalloc(sizeof (*fap))) == NULL)
return (ENOMEM);
fap->fa_type = FA_FCHDIR;
fap->fa_filedes = fd;
add_file_attr(file_actions, fap);
return (0);
}
int
posix_spawn_file_actions_addfchdir_np(
posix_spawn_file_actions_t *restrict file_actions,
int fd)
{
return (posix_spawn_file_actions_addfchdir(file_actions, fd));
}
int
posix_spawnattr_init(
posix_spawnattr_t *attr)
{
if ((attr->__spawn_attrp = lmalloc(sizeof (posix_spawnattr_t))) == NULL)
return (ENOMEM);
return (0);
}
int
posix_spawnattr_destroy(
posix_spawnattr_t *attr)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
lfree(sap, sizeof (*sap));
attr->__spawn_attrp = NULL;
return (0);
}
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);
sap->sa_psflags = flags;
return (0);
}
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);
}
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);
}
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);
}
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);
sap->sa_priority = schedparam->sched_priority;
return (0);
}
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);
}
int
posix_spawnattr_setschedpolicy(
posix_spawnattr_t *attr,
int schedpolicy)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL || schedpolicy == SCHED_SYS)
return (EINVAL);
if (get_info_by_policy(schedpolicy) == NULL)
return (errno);
sap->sa_schedpolicy = schedpolicy;
return (0);
}
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);
}
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);
}
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);
}
int
posix_spawnattr_setsigignore_np(
posix_spawnattr_t *attr,
const sigset_t *sigignore)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
sap->sa_sigignore = *sigignore;
return (0);
}
int
posix_spawnattr_getsigignore_np(
const posix_spawnattr_t *attr,
sigset_t *sigignore)
{
spawn_attr_t *sap = attr->__spawn_attrp;
if (sap == NULL)
return (EINVAL);
*sigignore = sap->sa_sigignore;
return (0);
}
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);
}
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);
}
int
posix_spawn_pipe_np(pid_t *pidp, int *fdp,
const char *cmd, boolean_t write,
posix_spawn_file_actions_t *fact, posix_spawnattr_t *attr)
{
int p[2];
int myside, yourside, stdio;
const char *shpath = _PATH_BSHELL;
const char *argvec[4] = { "sh", "-c", cmd, NULL };
int error;
if (pipe(p) < 0)
return (errno);
if (access(shpath, X_OK))
shpath = "";
if (write) {
myside = p[1];
yourside = p[0];
stdio = STDIN_FILENO;
} else {
myside = p[0];
yourside = p[1];
stdio = STDOUT_FILENO;
}
error = posix_spawn_file_actions_addclose(fact, myside);
if (yourside != stdio) {
if (error == 0) {
error = posix_spawn_file_actions_adddup2(fact,
yourside, stdio);
}
if (error == 0) {
error = posix_spawn_file_actions_addclose(fact,
yourside);
}
}
if (error)
return (error);
error = posix_spawn(pidp, shpath, fact, attr,
(char *const *)argvec, (char *const *)_environ);
(void) close(yourside);
if (error) {
(void) close(myside);
return (error);
}
*fdp = myside;
return (0);
}