procopen.c   [plain text]


/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2007 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * common process execution support with
 * proper sfio, signal and wait() syncronization
 *
 * _ contains the process path name and is
 * placed at the top of the environment
 */

#include "proclib.h"

#include <ls.h>

/*
 * not quite ready for _use_spawnveg 
 */

#if _use_spawnveg && _lib_fork
#undef	_use_spawnveg
#endif

#ifndef DEBUG_PROC
#define DEBUG_PROC	1
#endif

#if _lib_socketpair
#if _sys_socket
#include <sys/types.h>
#include <sys/socket.h>
#else
#undef	_lib_socketpair
#endif
#endif

Proc_t			proc_default = { -1 };

#if DEBUG_PROC

#include <namval.h>

#define PROC_ENV_OPTIONS	"PROC_OPTIONS"

#define PROC_OPT_ENVIRONMENT	(1<<0)
#define PROC_OPT_EXEC		(1<<1)
#define PROC_OPT_TRACE		(1<<2)
#define PROC_OPT_VERBOSE	(1<<3)

static const Namval_t		options[] =
{
	"debug",	PROC_OPT_VERBOSE,
	"environment",	PROC_OPT_ENVIRONMENT,
	"exec",		PROC_OPT_EXEC,
	"trace",	PROC_OPT_TRACE,
	"verbose",	PROC_OPT_VERBOSE,
	0,		0
};

/*
 * called by stropt() to set options
 */

static int
setopt(register void* a, register const void* p, register int n, const char* v)
{
	NoP(v);
	if (p)
	{
		if (n)
			*((int*)a) |= ((Namval_t*)p)->value;
		else
			*((int*)a) &= ~((Namval_t*)p)->value;
	}
	return 0;
}

#endif

#if _use_spawnveg

typedef struct Fd_s
{
	short		fd;
	short		flag;
} Fd_t;

typedef struct Mod_s
{
	struct Mod_s*	next;
	short		op;
	short		save;

	union
	{

	struct
	{
	Fd_t		parent;
	Fd_t		child;
	}		fd;

	Handler_t	handler;

	}		arg;

} Modify_t;

#endif

#ifdef SIGPIPE

/*
 * catch but ignore sig
 * avoids SIG_IGN being passed to children
 */

static void
ignoresig(int sig)
{
	signal(sig, ignoresig);
}

#endif

/*
 * do modification op and save previous state for restore()
 */

static int
modify(Proc_t* proc, int forked, int op, long arg1, long arg2)
{
#if _lib_fork
	if (forked)
	{
		switch (op)
		{
		case PROC_fd_dup:
		case PROC_fd_dup|PROC_FD_PARENT:
		case PROC_fd_dup|PROC_FD_CHILD:
		case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
			if (arg1 != arg2)
			{
				if (arg2 != PROC_ARG_NULL)
				{
					close(arg2);
					if (fcntl(arg1, F_DUPFD, arg2) != arg2)
						return -1;
				}
				if (op & PROC_FD_CHILD)
					close(arg1);
			}
			break;
		case PROC_sig_dfl:
			signal(arg1, SIG_DFL);
			break;
		case PROC_sig_ign:
			signal(arg1, SIG_IGN);
			break;
		case PROC_sys_pgrp:
			if (arg1 < 0)
				setsid();
			else if (arg1 > 0)
			{
				if (arg1 == 1)
					arg1 = 0;
				if (setpgid(0, arg1) < 0 && arg1 && errno == EPERM)
					setpgid(0, 0);
			}
			break;
		case PROC_sys_umask:
			umask(arg1);
			break;
		default:
			return -1;
		}
	}
#if _use_spawnveg
	else
#endif
#else
	NoP(forked);
#endif
#if _use_spawnveg
	{
		register Modify_t*	m;

		if (!(m = newof(NiL, Modify_t, 1, 0)))
			return -1;
		m->next = proc->mods;
		proc->mods = m;
		switch (m->op = op)
		{
		case PROC_fd_dup:
		case PROC_fd_dup|PROC_FD_PARENT:
		case PROC_fd_dup|PROC_FD_CHILD:
		case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
			m->arg.fd.parent.fd = (short)arg1;
			m->arg.fd.parent.flag = fcntl(arg1, F_GETFD, 0);
			if ((m->arg.fd.child.fd = (short)arg2) != arg1)
			{
				if (arg2 != PROC_ARG_NULL)
				{
					m->arg.fd.child.flag = fcntl(arg2, F_GETFD, 0);
					if ((m->save = fcntl(arg2, F_DUPFD, 3)) < 0)
					{
						m->op = 0;
						return -1;
					}
					fcntl(m->save, F_SETFD, FD_CLOEXEC);
					close(arg2);
					if (fcntl(arg1, F_DUPFD, arg2) != arg2)
						return -1;
					if (op & PROC_FD_CHILD)
						close(arg1);
				}
				else if (op & PROC_FD_CHILD)
				{
					if (m->arg.fd.parent.flag)
						break;
					fcntl(arg1, F_SETFD, FD_CLOEXEC);
				}
				else if (!m->arg.fd.parent.flag)
					break;
				else
					fcntl(arg1, F_SETFD, 0);
				return 0;
			}
			break;
		case PROC_sig_dfl:
			if ((m->arg.handler = signal(arg1, SIG_DFL)) == SIG_DFL)
				break;
			m->save = (short)arg1;
			return 0;
		case PROC_sig_ign:
			if ((m->arg.handler = signal(arg1, SIG_IGN)) == SIG_IGN)
				break;
			m->save = (short)arg1;
			return 0;
		case PROC_sys_pgrp:
			proc->pgrp = arg1;
			break;
		case PROC_sys_umask:
			if ((m->save = (short)umask(arg1)) == arg1)
				break;
			return 0;
		default:
			proc->mods = m->next;
			free(m);
			return -1;
		}
		proc->mods = m->next;
		free(m);
	}
#else
	NoP(proc);
#endif
	return 0;
}

#if _use_spawnveg

/*
 * restore modifications
 */

static void
restore(Proc_t* proc)
{
	register Modify_t*	m;
	register Modify_t*	p;
	int			oerrno;

	NoP(proc);
	oerrno = errno;
	m = proc->mods;
	proc->mods = 0;
	while (m)
	{
		switch (m->op)
		{
		case PROC_fd_dup:
		case PROC_fd_dup|PROC_FD_PARENT:
		case PROC_fd_dup|PROC_FD_CHILD:
		case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
			if (m->op & PROC_FD_PARENT)
				close(m->arg.fd.parent.fd);
			if (m->arg.fd.child.fd != m->arg.fd.parent.fd && m->arg.fd.child.fd != PROC_ARG_NULL)
			{
				if (!(m->op & PROC_FD_PARENT))
				{
					if (m->op & PROC_FD_CHILD)
					{
						close(m->arg.fd.parent.fd);
						fcntl(m->arg.fd.child.fd, F_DUPFD, m->arg.fd.parent.fd);
					}
					fcntl(m->arg.fd.parent.fd, F_SETFD, m->arg.fd.parent.flag);
				}
				close(m->arg.fd.child.fd);
				fcntl(m->save, F_DUPFD, m->arg.fd.child.fd);
				close(m->save);
				if (m->arg.fd.child.flag)
					fcntl(m->arg.fd.child.fd, F_SETFD, FD_CLOEXEC);
			}
			else if ((m->op & (PROC_FD_PARENT|PROC_FD_CHILD)) == PROC_FD_CHILD)
				fcntl(m->arg.fd.parent.fd, F_SETFD, 0);
			break;
		case PROC_sig_dfl:
		case PROC_sig_ign:
			signal(m->save, m->arg.handler);
			break;
		case PROC_sys_umask:
			umask(m->save);
			break;
		}
		p = m;
		m = m->next;
		free(p);
	}
	errno = oerrno;
}

#else

#define restore(p)

#endif

/*
 * fork and exec or spawn proc(argv) and return a Proc_t handle
 *
 * pipe not used when PROC_READ|PROC_WRITE omitted
 * argv==0 duplicates current process if possible
 * cmd==0 names the current shell
 * cmd=="" does error cleanup
 * envv is the child environment
 * modv is the child modification vector of PROC_*() ops
 */

Proc_t*
procopen(const char* cmd, char** argv, char** envv, long* modv, int flags)
{
	register Proc_t*	proc = 0;
	register int		procfd;
	register char**		p;
	char**			v;
	int			i;
	int			forked = 0;
	int			signalled = 0;
	long			n;
	char			path[PATH_MAX];
	char			env[PATH_MAX + 2];
	int			pio[2];
#if !_pipe_rw && !_lib_socketpair
	int			poi[2];
#endif
#if defined(SIGCHLD) && ( _lib_sigprocmask || _lib_sigsetmask )
	Sig_mask_t		mask;
#endif
#if _use_spawnveg
	int			newenv = 0;
#endif
#if DEBUG_PROC
	int			debug = PROC_OPT_EXEC;
#endif

#if _lib_fork
	if (!argv && (flags & PROC_OVERLAY))
#else
	if (!argv)
#endif
	{
		errno = ENOEXEC;
		return 0;
	}
	pio[0] = pio[1] = -1;
#if !_pipe_rw && !_lib_socketpair
	poi[0] = poi[1] = -1;
#endif
	if (cmd && (!*cmd || !pathpath(path, cmd, NiL, PATH_REGULAR|PATH_EXECUTE)))
		goto bad;
	switch (flags & (PROC_READ|PROC_WRITE))
	{
	case 0:
		procfd = -1;
		break;
	case PROC_READ:
		procfd = 1;
		break;
	case PROC_WRITE:
		procfd = 0;
		break;
	case PROC_READ|PROC_WRITE:
		procfd = 2;
		break;
	}
	if (proc_default.pid == -1)
		proc = &proc_default;
	else if (!(proc = newof(0, Proc_t, 1, 0)))
		goto bad;
	proc->pid = -1;
	proc->pgrp = 0;
	proc->rfd = -1;
	proc->wfd = -1;
	proc->flags = flags;
	sfsync(NiL);
	if (environ && envv != (char**)environ && (envv || (flags & PROC_PARANOID) || argv && (environ[0][0] != '_' || environ[0][1] != '=')))
	{
		if (!setenviron(NiL))
			goto bad;
#if _use_spawnveg
		newenv = 1;
#endif
	}
	if (procfd >= 0)
	{
#if _pipe_rw
		if (pipe(pio))
			goto bad;
#else
		if (procfd > 1)
		{
#if _lib_socketpair
			if (socketpair(AF_UNIX, SOCK_STREAM, 0, pio))
				goto bad;
#else
			if (pipe(pio) || pipe(poi))
				goto bad;
#endif
		}
		else if (pipe(pio))
			goto bad;
#endif
	}
	if (flags & PROC_OVERLAY)
	{
		proc->pid = 0;
		forked = 1;
	}
#if _use_spawnveg
	else if (argv)
		proc->pid = 0;
#endif
#if _lib_fork
	else
	{
		if (!(flags & PROC_FOREGROUND))
			sigcritical(SIG_REG_EXEC|SIG_REG_PROC);
		else
		{
			signalled = 1;
			proc->sigint = signal(SIGINT, SIG_IGN);
			proc->sigquit = signal(SIGQUIT, SIG_IGN);
#if defined(SIGCHLD)
#if _lib_sigprocmask
			sigemptyset(&mask);
			sigaddset(&mask, SIGCHLD);
			sigprocmask(SIG_BLOCK, &mask, &proc->mask);
#else
#if _lib_sigsetmask
			mask = sigmask(SIGCHLD);
			proc->mask = sigblock(mask);
#else
			proc->sigchld = signal(SIGCHLD, SIG_DFL);
#endif
#endif
#endif
		}
		proc->pid = fork();
		if (!(flags & PROC_FOREGROUND))
			sigcritical(0);
		else if (!proc->pid)
		{
			if (proc->sigint != SIG_IGN)
			{
				proc->sigint = SIG_DFL;
				signal(SIGINT, proc->sigint);
			}
			if (proc->sigquit != SIG_IGN)
			{
				proc->sigquit = SIG_DFL;
				signal(SIGQUIT, proc->sigquit);
			}
#if defined(SIGCHLD)
#if _lib_sigprocmask
			sigprocmask(SIG_SETMASK, &proc->mask, NiL);
#else
#if _lib_sigsetmask
			sigsetmask(proc->mask);
#else
			if (proc->sigchld != SIG_IGN)
				signal(SIGCHLD, SIG_DFL);
#endif
#endif
#endif
		}
		else if (proc->pid == -1)
			goto bad;
		forked = 1;
	}
#endif
	if (!proc->pid)
	{
		char*		s;
#if _use_spawnveg
		char**		oenviron = 0;
		char*		oenviron0 = 0;

		v = 0;
#endif
#if DEBUG_PROC
		stropt(getenv(PROC_ENV_OPTIONS), options, sizeof(*options), setopt, &debug);
#if _lib_fork
		if (debug & PROC_OPT_TRACE)
		{
			if (!fork())
			{
				sfsprintf(path, sizeof(path), "%d", getppid());
				execlp("trace", "trace", "-p", path, NiL);
				_exit(EXIT_NOTFOUND);
			}
			sleep(2);
		}
#endif
#endif
		if (flags & PROC_DAEMON)
		{
#ifdef SIGHUP
			modify(proc, forked, PROC_sig_ign, SIGHUP, 0);
#endif
			modify(proc, forked, PROC_sig_dfl, SIGTERM, 0);
#ifdef SIGTSTP
			modify(proc, forked, PROC_sig_ign, SIGTSTP, 0);
#endif
#ifdef SIGTTIN
			modify(proc, forked, PROC_sig_ign, SIGTTIN, 0);
#endif
#ifdef SIGTTOU
			modify(proc, forked, PROC_sig_ign, SIGTTOU, 0);
#endif
		}
		if (flags & (PROC_BACKGROUND|PROC_DAEMON))
		{
			modify(proc, forked, PROC_sig_ign, SIGINT, 0);
#ifdef SIGQUIT
			modify(proc, forked, PROC_sig_ign, SIGQUIT, 0);
#endif
		}
		if (flags & (PROC_DAEMON|PROC_SESSION))
			modify(proc, forked, PROC_sys_pgrp, -1, 0);
		if (forked || (flags & PROC_OVERLAY))
		{
			if ((flags & PROC_PRIVELEGED) && !geteuid())
			{
				setuid(geteuid());
				setgid(getegid());
			}
			if (flags & (PROC_PARANOID|PROC_GID))
				setgid(getgid());
			if (flags & (PROC_PARANOID|PROC_UID))
				setuid(getuid());
		}
		if (procfd > 1)
		{
			if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[0], PROC_ARG_NULL))
				goto cleanup;
			if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[1], 1))
				goto cleanup;
#if _pipe_rw || _lib_socketpair
			if (modify(proc, forked, PROC_fd_dup, 1, 0))
				goto cleanup;
#else
			if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, poi[0], 0))
				goto cleanup;
			if (poi[1] != 0 && modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, poi[1], PROC_ARG_NULL))
				goto cleanup;
#endif
		}
		else if (procfd >= 0)
		{
			if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[!!procfd], !!procfd))
				goto cleanup;
			if (pio[!procfd] != !!procfd && modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[!procfd], PROC_ARG_NULL))
				goto cleanup;
		}
		if (modv)
			for (i = 0; n = modv[i]; i++)
				switch (PROC_OP(n))
				{
				case PROC_fd_dup:
				case PROC_fd_dup|PROC_FD_PARENT:
				case PROC_fd_dup|PROC_FD_CHILD:
				case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
					if (modify(proc, forked, PROC_OP(n), PROC_ARG(n, 1), PROC_ARG(n, 2)))
						goto cleanup;
					break;
				default:
					if (modify(proc, forked, PROC_OP(n), PROC_ARG(n, 1), 0))
						goto cleanup;
					break;
				}
#if _lib_fork
		if (forked && (flags & PROC_ENVCLEAR))
			environ = 0;
#if _use_spawnveg
		else
#endif
#endif
#if _use_spawnveg
		if (newenv)
		{
			p = environ;
			while (*p++);
			if (!(oenviron = (char**)memdup(environ, (p - environ) * sizeof(char*))))
				goto cleanup;
		}
#endif
		if (argv && envv != (char**)environ)
		{
#if _use_spawnveg
			if (!newenv && environ[0][0] == '_' && environ[0][1] == '=')
				oenviron0 = environ[0];
#endif
			env[0] = '_';
			env[1] = '=';
			env[2] = 0;
			if (!setenviron(env))
				goto cleanup;
		}
		if ((flags & PROC_PARANOID) && setenv("PATH", astconf("PATH", NiL, NiL), 1))
			goto cleanup;
		if ((p = envv) && p != (char**)environ)
			while (*p)
				if (!setenviron(*p++))
					goto cleanup;
		p = argv;
#if _lib_fork
		if (forked && !p)
			return proc;
#endif
#if DEBUG_PROC
		if (!(debug & PROC_OPT_EXEC) || (debug & PROC_OPT_VERBOSE))
		{
			if ((debug & PROC_OPT_ENVIRONMENT) && (p = environ))
				while (*p)
					sfprintf(sfstderr, "%s\n", *p++);
			sfprintf(sfstderr, "+ %s", cmd ? path : "sh");
			if ((p = argv) && *p)
				while (*++p)
					sfprintf(sfstderr, " %s", *p);
			sfprintf(sfstderr, "\n");
sfsync(sfstderr);
			if (!(debug & PROC_OPT_EXEC))
				_exit(0);
			p = argv;
		}
#endif
		if (cmd)
		{
			strcpy(env + 2, path);
			if (forked || (flags & PROC_OVERLAY))
				execve(path, p, environ);
#if _use_spawnveg
			else if ((proc->pid = spawnveg(path, p, environ, proc->pgrp)) != -1)
				goto cleanup;
#endif
			if (errno != ENOEXEC)
				goto cleanup;

			/*
			 * try cmd as a shell script
			 */

			if (!(flags & PROC_ARGMOD))
			{
				while (*p++);
				if (!(v = newof(0, char*, p - argv + 2, 0)))
					goto cleanup;
				p = v + 2;
				if (*argv)
					argv++;
				while (*p++ = *argv++);
				p = v + 1;
			}
			*p = path;
			*--p = "sh";
		}
		strcpy(env + 2, (flags & PROC_PARANOID) ? astconf("SH", NiL, NiL) : pathshell());
		if (forked || (flags & PROC_OVERLAY))
			execve(env + 2, p, environ);
#if _use_spawnveg
		else
			proc->pid = spawnveg(env + 2, p, environ, proc->pgrp);
#endif
	cleanup:
		if (forked)
		{
			if (!(flags & PROC_OVERLAY))
				_exit(errno == ENOENT ? EXIT_NOTFOUND : EXIT_NOEXEC);
			goto bad;
		}
#if _use_spawnveg
		if (v)
			free(v);
		if (p = oenviron)
		{
			environ = 0;
			while (*p)
				if (!setenviron(*p++))
					goto bad;
			free(oenviron);
		}
		else if (oenviron0)
			environ[0] = oenviron0;
		restore(proc);
		if (flags & PROC_OVERLAY)
			exit(0);
#endif
	}
	if (proc->pid != -1)
	{
		if (!forked)
		{
			if (flags & PROC_FOREGROUND)
			{
				signalled = 1;
				proc->sigint = signal(SIGINT, SIG_IGN);
				proc->sigquit = signal(SIGQUIT, SIG_IGN);
#if defined(SIGCHLD)
#if _lib_sigprocmask
				sigemptyset(&mask);
				sigaddset(&mask, SIGCHLD);
				sigprocmask(SIG_BLOCK, &mask, &proc->mask);
#else
#if _lib_sigsetmask
				mask = sigmask(SIGCHLD);
				proc->mask = sigblock(mask);
#else
				proc->sigchld = signal(SIGCHLD, SIG_DFL);
#endif
#endif
#endif
			}
		}
		else if (modv)
			for (i = 0; n = modv[i]; i++)
				switch (PROC_OP(n))
				{
				case PROC_fd_dup|PROC_FD_PARENT:
				case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
					close(PROC_ARG(n, 1));
					break;
				case PROC_sys_pgrp:
					if (proc->pgrp < 0)
						proc->pgrp = proc->pid;
					else if (proc->pgrp > 0)
					{
						if (proc->pgrp == 1)
							proc->pgrp = proc->pid;
						if (setpgid(proc->pid, proc->pgrp) < 0 && proc->pid != proc->pgrp && errno == EPERM)
							setpgid(proc->pid, proc->pid);
					}
					break;
				}
		if (procfd >= 0)
		{
#ifdef SIGPIPE
			if ((flags & (PROC_WRITE|PROC_IGNORE)) == (PROC_WRITE|PROC_IGNORE))
			{
				Handler_t	handler;

				if ((handler = signal(SIGPIPE, ignoresig)) != SIG_DFL && handler != ignoresig)
					signal(SIGPIPE, handler);
			}
#endif
			switch (procfd)
			{
			case 0:
				proc->wfd = pio[1];
				close(pio[0]);
				break;
			default:
#if _pipe_rw || _lib_socketpair
				proc->wfd = pio[0];
#else
				proc->wfd = poi[1];
				close(poi[0]);
#endif
				/*FALLTHROUGH*/
			case 1:
				proc->rfd = pio[0];
				close(pio[1]);
				break;
			}
			if (proc->rfd > 2)
				fcntl(proc->rfd, F_SETFD, FD_CLOEXEC);
			if (proc->wfd > 2)
				fcntl(proc->wfd, F_SETFD, FD_CLOEXEC);
		}
		if (!proc->pid)
			proc->pid = getpid();
		return proc;
	}
 bad:
	if (signalled)
	{
		if (proc->sigint != SIG_IGN)
			signal(SIGINT, proc->sigint);
		if (proc->sigquit != SIG_IGN)
			signal(SIGQUIT, proc->sigquit);
#if defined(SIGCHLD)
#if _lib_sigprocmask
		sigprocmask(SIG_SETMASK, &proc->mask, NiL);
#else
#if _lib_sigsetmask
		sigsetmask(proc->mask);
#else
		if (proc->sigchld != SIG_DFL)
			signal(SIGCHLD, proc->sigchld);
#endif
#endif
#endif
	}
	if ((flags & PROC_CLEANUP) && modv)
		for (i = 0; n = modv[i]; i++)
			switch (PROC_OP(n))
			{
			case PROC_fd_dup:
			case PROC_fd_dup|PROC_FD_PARENT:
			case PROC_fd_dup|PROC_FD_CHILD:
			case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD:
				if (PROC_ARG(n, 2) != PROC_ARG_NULL)
					close(PROC_ARG(n, 1));
				break;
			}
	if (pio[0] >= 0)
		close(pio[0]);
	if (pio[1] >= 0)
		close(pio[1]);
#if !_pipe_rw && !_lib_socketpair
	if (poi[0] >= 0)
		close(poi[0]);
	if (poi[1] >= 0)
		close(poi[1]);
#endif
	procfree(proc);
	return 0;
}