/* ----------------------------------------------------------------------------
(c) The University of Glasgow 2004
Support for System.Process
(c) XT 2016
I've extracted this from the process library and modified it to fit the
need for the rawfilepath package.
------------------------------------------------------------------------- */
/* XXX This is a nasty hack; should put everything necessary in this package */
#include "HsBase.h"
#include "Rts.h"
#include "execvpe.h"
#include
#include
#include
#include
#include "processFlags.h"
// If a process was terminated by a signal, the exit status we return
// via the System.Process API is (-signum). This encoding avoids collision with
// normal process termination status codes. See also #7229.
#define TERMSIG_EXITSTATUS(s) (-(WTERMSIG(s)))
static long max_fd = 0;
// Rts internal API, not exposed in a public header file:
extern void blockUserSignals(void);
extern void unblockUserSignals(void);
// See #1593. The convention for the exit code when
// exec() fails seems to be 127 (gleened from C's
// system()), but there's no equivalent convention for
// chdir(), so I'm picking 126 --SimonM.
#define forkChdirFailed 126
#define forkExecFailed 127
// These are arbitrarily chosen -- JP
#define forkSetgidFailed 124
#define forkSetuidFailed 125
__attribute__((__noreturn__))
static void childFailed(int pipe, int failCode) {
int err;
ssize_t unused __attribute__((unused));
err = errno;
unused = write(pipe, &failCode, sizeof(failCode));
unused = write(pipe, &err, sizeof(err));
// As a fallback, exit with the failCode
_exit(failCode);
}
pid_t
runInteractiveProcess (char *const args[],
char *workingDirectory, char **environment,
int fdStdIn, int fdStdOut, int fdStdErr,
int *pfdStdInput, int *pfdStdOutput, int *pfdStdError,
gid_t *childGroup, uid_t *childUser,
int reset_int_quit_handlers,
int flags,
char **failed_doing)
{
int close_fds = ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0);
int pid;
int fdStdInput[2], fdStdOutput[2], fdStdError[2];
int forkCommunicationFds[2];
int r;
int failCode, err;
// Ordering matters here, see below [Note #431].
if (fdStdIn == -1) {
r = pipe(fdStdInput);
if (r == -1) {
*failed_doing = "runInteractiveProcess: pipe";
return -1;
}
}
if (fdStdOut == -1) {
r = pipe(fdStdOutput);
if (r == -1) {
*failed_doing = "runInteractiveProcess: pipe";
return -1;
}
}
if (fdStdErr == -1) {
r = pipe(fdStdError);
if (r == -1) {
*failed_doing = "runInteractiveProcess: pipe";
return -1;
}
}
r = pipe(forkCommunicationFds);
if (r == -1) {
*failed_doing = "runInteractiveProcess: pipe";
return -1;
}
// Block signals with Haskell handlers. The danger here is that
// with the threaded RTS, a signal arrives in the child process,
// the RTS writes the signal information into the pipe (which is
// shared between parent and child), and the parent behaves as if
// the signal had been raised.
blockUserSignals();
// See #4074. Sometimes fork() gets interrupted by the timer
// signal and keeps restarting indefinitely.
stopTimer();
switch(pid = fork())
{
case -1:
unblockUserSignals();
startTimer();
if (fdStdIn == -1) {
close(fdStdInput[0]);
close(fdStdInput[1]);
}
if (fdStdOut == -1) {
close(fdStdOutput[0]);
close(fdStdOutput[1]);
}
if (fdStdErr == -1) {
close(fdStdError[0]);
close(fdStdError[1]);
}
close(forkCommunicationFds[0]);
close(forkCommunicationFds[1]);
*failed_doing = "fork";
return -1;
case 0:
// WARNING! We may now be in the child of vfork(), and any
// memory we modify below may also be seen in the parent
// process.
close(forkCommunicationFds[0]);
fcntl(forkCommunicationFds[1], F_SETFD, FD_CLOEXEC);
if ((flags & RUN_PROCESS_NEW_SESSION) != 0) {
setsid();
}
if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {
setpgid(0, 0);
}
if ( childGroup) {
if ( setgid( *childGroup) != 0) {
// ERROR
childFailed(forkCommunicationFds[1], forkSetgidFailed);
}
}
if ( childUser) {
if ( setuid( *childUser) != 0) {
// ERROR
childFailed(forkCommunicationFds[1], forkSetuidFailed);
}
}
unblockUserSignals();
if (workingDirectory) {
if (chdir (workingDirectory) < 0) {
childFailed(forkCommunicationFds[1], forkChdirFailed);
}
}
// [Note #431]: Ordering matters here. If any of the FDs
// 0,1,2 were initially closed, then our pipes may have used
// these FDs. So when we dup2 the pipe FDs down to 0,1,2, we
// must do it in that order, otherwise we could overwrite an
// FD that we need later.
if (fdStdIn == -1) {
if (fdStdInput[0] != STDIN_FILENO) {
dup2 (fdStdInput[0], STDIN_FILENO);
close(fdStdInput[0]);
}
close(fdStdInput[1]);
} else if (fdStdIn == -2) {
close(STDIN_FILENO);
} else {
dup2(fdStdIn, STDIN_FILENO);
}
if (fdStdOut == -1) {
if (fdStdOutput[1] != STDOUT_FILENO) {
dup2 (fdStdOutput[1], STDOUT_FILENO);
close(fdStdOutput[1]);
}
close(fdStdOutput[0]);
} else if (fdStdOut == -2) {
close(STDOUT_FILENO);
} else {
dup2(fdStdOut, STDOUT_FILENO);
}
if (fdStdErr == -1) {
if (fdStdError[1] != STDERR_FILENO) {
dup2 (fdStdError[1], STDERR_FILENO);
close(fdStdError[1]);
}
close(fdStdError[0]);
} else if (fdStdErr == -2) {
close(STDERR_FILENO);
} else {
dup2(fdStdErr, STDERR_FILENO);
}
if (close_fds) {
int i;
if (max_fd == 0) {
#if HAVE_SYSCONF
max_fd = sysconf(_SC_OPEN_MAX);
if (max_fd == -1) {
max_fd = 256;
}
#else
max_fd = 256;
#endif
}
// XXX Not the pipe
for (i = 3; i < max_fd; i++) {
if (i != forkCommunicationFds[1]) {
close(i);
}
}
}
/* Reset the SIGINT/SIGQUIT signal handlers in the child, if requested
*/
if (reset_int_quit_handlers) {
struct sigaction dfl;
(void)sigemptyset(&dfl.sa_mask);
dfl.sa_flags = 0;
dfl.sa_handler = SIG_DFL;
(void)sigaction(SIGINT, &dfl, NULL);
(void)sigaction(SIGQUIT, &dfl, NULL);
}
/* the child */
if (environment) {
// XXX Check result
execvpe(args[0], args, environment);
} else {
// XXX Check result
execvp(args[0], args);
}
childFailed(forkCommunicationFds[1], forkExecFailed);
default:
if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {
setpgid(pid, pid);
}
if (fdStdIn == -1) {
close(fdStdInput[0]);
fcntl(fdStdInput[1], F_SETFD, FD_CLOEXEC);
*pfdStdInput = fdStdInput[1];
}
if (fdStdOut == -1) {
close(fdStdOutput[1]);
fcntl(fdStdOutput[0], F_SETFD, FD_CLOEXEC);
*pfdStdOutput = fdStdOutput[0];
}
if (fdStdErr == -1) {
close(fdStdError[1]);
fcntl(fdStdError[0], F_SETFD, FD_CLOEXEC);
*pfdStdError = fdStdError[0];
}
close(forkCommunicationFds[1]);
fcntl(forkCommunicationFds[0], F_SETFD, FD_CLOEXEC);
break;
}
// If the child process had a problem, then it will tell us via the
// forkCommunicationFds pipe. First we try to read what the problem
// was. Note that if none of these conditionals match then we fall
// through and just return pid.
r = read(forkCommunicationFds[0], &failCode, sizeof(failCode));
if (r == -1) {
*failed_doing = "runInteractiveProcess: read pipe";
pid = -1;
}
else if (r == sizeof(failCode)) {
// This is the case where we successfully managed to read
// the problem
switch (failCode) {
case forkChdirFailed:
*failed_doing = "runInteractiveProcess: chdir";
break;
case forkExecFailed:
*failed_doing = "runInteractiveProcess: exec";
break;
case forkSetgidFailed:
*failed_doing = "runInteractiveProcess: setgid";
break;
case forkSetuidFailed:
*failed_doing = "runInteractiveProcess: setuid";
default:
*failed_doing = "runInteractiveProcess: unknown";
break;
}
// Now we try to get the errno from the child
r = read(forkCommunicationFds[0], &err, sizeof(err));
if (r == -1) {
*failed_doing = "runInteractiveProcess: read pipe";
}
else if (r != sizeof(failCode)) {
*failed_doing = "runInteractiveProcess: read pipe bad length";
}
else {
// If we succeed then we set errno. It'll be saved and
// restored again below. Note that in any other case we'll
// get the errno of whatever else went wrong instead.
errno = err;
}
// We forked the child, but the child had a problem and stopped so it's
// our responsibility to reap here as nobody else can.
waitpid(pid, NULL, 0);
pid = -1;
}
else if (r != 0) {
*failed_doing = "runInteractiveProcess: read pipe bad length";
pid = -1;
}
if (pid == -1) {
err = errno;
}
close(forkCommunicationFds[0]);
unblockUserSignals();
startTimer();
if (pid == -1) {
errno = err;
}
return pid;
}
int
terminateProcess (pid_t handle)
{
return (kill(handle, SIGTERM) == 0);
}
int
getProcessExitCode (pid_t handle, int *pExitCode)
{
int wstat, res;
*pExitCode = 0;
if ((res = waitpid(handle, &wstat, WNOHANG)) > 0)
{
if (WIFEXITED(wstat))
{
*pExitCode = WEXITSTATUS(wstat);
return 1;
}
else
if (WIFSIGNALED(wstat))
{
*pExitCode = TERMSIG_EXITSTATUS(wstat);
return 1;
}
else
{
/* This should never happen */
}
}
if (res == 0) return 0;
if (errno == ECHILD)
{
*pExitCode = 0;
return 1;
}
return -1;
}
int waitForProcess (pid_t handle, int *pret)
{
int wstat;
if (waitpid(handle, &wstat, 0) < 0)
{
return -1;
}
if (WIFEXITED(wstat)) {
*pret = WEXITSTATUS(wstat);
return 0;
}
else {
if (WIFSIGNALED(wstat))
{
*pret = TERMSIG_EXITSTATUS(wstat);
return 0;
}
else
{
/* This should never happen */
}
}
return -1;
}