/************************************************************************
 *
 * exec.cpp - Definitions of the child process subsystem
 *
 * $Id: exec.cpp 598869 2007-11-28 05:08:25Z sebor $
 *
 ************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 * 
 **************************************************************************/

/* disable Compaq/HP C++ pure libc headers to allow POSIX symbols
   such as SIGALRM or SIGKILL to be defined. */
#undef __PURE_CNAME

#include <assert.h> /* for assert */
#include <ctype.h> /* for tolower */
#include <errno.h> /* for errno */
#include <fcntl.h> /* for O_*, */
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for str*, mem* */

#if !defined (_WIN32) && !defined (_WIN64)
#  include <unistd.h> /* for close, dup, exec, fork */
#  include <sys/wait.h>
#  include <sys/times.h> /* for times - is this XSI? */
#  ifdef _XOPEN_UNIX
#    include <sys/resource.h> /* for setlimit(), RLIMIT_CORE, ... */
#  endif
#else
#  ifndef _WIN32_WINNT
#    define _WIN32_WINNT 0x0500
#  endif
#  include <windows.h> /* for PROCESS_INFORMATION, CreateProcess, ... */
#  ifndef SIGTRAP
#    define SIGTRAP   5   // STATUS_BREAKPOINT translated into SIGTRAP
#  endif
#  ifndef SIGBUS
#    define SIGBUS    10  // STATUS_IN_PAGE_ERROR translated into SIGBUS
#  endif
#  ifndef SIGSYS
#    define SIGSYS    12  // STATUS_INVALID_PARAMETER translated into SIGSYS
#  endif
#  ifndef SIGSTKFLT
#    define SIGSTKFLT 16  // STATUS_FLOAT_STACK_CHECK translated into SIGSTKFLT
#  endif
#  ifndef STATUS_INVALID_PARAMETER
#    define STATUS_INVALID_PARAMETER          ((DWORD)0xC000000DL)
#  endif
#  ifndef STATUS_HEAP_CORRUPTION
#    define STATUS_HEAP_CORRUPTION            ((DWORD)0xC0000374L)
#  endif
#  ifndef STATUS_STACK_BUFFER_OVERRUN
#    define STATUS_STACK_BUFFER_OVERRUN       ((DWORD)0xC0000409L)
#  endif
#  ifndef STATUS_INVALID_CRUNTIME_PARAMETER
#    define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD)0xC0000417L)
#  endif
#endif

#ifndef SIGHUP
#  define SIGHUP     1   /* Linux value */
#endif
#ifndef SIGQUIT
#  define SIGQUIT    3   /* Linux value */
#endif
#ifndef SIGKILL
#  define SIGKILL    9   /* Linux value */
#endif
#ifndef SIGALRM
#  define SIGALRM   14   /* Linux value */
#endif


#ifndef ESRCH
#  define ESRCH      3   /* Linux value */
#endif
#ifndef EINTR
#  define EINTR      4   /* Linux value */
#endif
#ifndef ECHILD
#  define ECHILD    10   /* Linux value */
#endif
#ifndef EINVAL
#  define EINVAL    22   /* Linux value */
#endif

#include <sys/stat.h> /* for S_* */
#include <sys/types.h>

#include "cmdopt.h"
#include "target.h" /* For struct target_opts */
#include "util.h"

#include "exec.h"

#if !defined (_WIN32) && !defined (_RWSTD_NO_PURE_C_HEADERS)
#  ifdef __cplusplus
extern "C" {
#  endif

int kill (pid_t pid, int sig);

FILE* fdopen (int fd, const char *mode);

#  ifdef __cplusplus
}   /* extern "C" */
#  endif
#endif   /* !_WIN32 && !_RWSTD_NO_PURE_C_HEADERS */


/**
   Status flag used to comunicate that an alarm has triggered.

   Value is 0 when alarm hasn't been triggered or has been handled
   Value is 1 when alarm has been triggered and hasn't been handled

   @see handle_alrm
   @see handle_term_signal
*/
static int alarm_timeout;

/**
   Record of fatal signal recieved.  Used to raise() the signal again after
   child process has been killed.

   @see handle_term_signal
*/
static int kill_signal;

/**
    Utility macro to generate a signal number/name pair.
    
    @parm val 'short' signal name (no leading SIG) to generate pair for.
    @see signal_names []
*/
#undef SIGNAL
#define SIGNAL(val)   { SIG ## val, #val }

/**
    Signal name/number translation table.
    
    This table is populated using the SIGNAL helper macro to translate
    system SIG* macro values into name/value pairs.
    
    @see SIGNAL ()
*/
static const struct {
    int         val; /**< Signal value for lookup pair */
    const char* str; /**< Signal name for lookup pair */
} signal_names [] = {
#ifdef SIGABRT
    SIGNAL (ABRT),
#endif   /* SIGABRT */
#ifdef SIGALRM
    SIGNAL (ALRM),
#endif   /* SIGALRM */
#ifdef SIGBUS
    SIGNAL (BUS),
#endif   /* SIGBUS */
#ifdef SIGCANCEL
    SIGNAL (CANCEL),
#endif   /* SIGCANCEL */
#ifdef SIGCHLD
    SIGNAL (CHLD),
#endif   /* SIGCHLD */
#ifdef SIGCKPT
    SIGNAL (CKPT),
#endif   /* SIGCKPT */
#ifdef SIGCLD
    SIGNAL (CLD),
#endif   /* SIGCLD */
#ifdef SIGCONT
    SIGNAL (CONT),
#endif   /* SIGCONT */
#ifdef SIGDIL
    SIGNAL (DIL),
#endif   /* SIGDIL */
#ifdef SIGEMT
    SIGNAL (EMT),
#endif   /* SIGEMT */
#ifdef SIGFPE
    SIGNAL (FPE),
#endif   /* SIGFPE */
#ifdef SIGFREEZE
    SIGNAL (FREEZE),
#endif   /* SIGFREEZE */
#ifdef SIGGFAULT
    SIGNAL (GFAULT),
#endif   /* SIGGFAULT */
#ifdef SIGHUP
    SIGNAL (HUP),
#endif   /* SIGHUP */
#ifdef SIGILL
    SIGNAL (ILL),
#endif   /* SIGILL */
#ifdef SIGINFO
    SIGNAL (INFO),
#endif   /* SIGINFO */
#ifdef SIGINT
    SIGNAL (INT),
#endif   /* SIGINT */
#ifdef SIGIO
    SIGNAL (IO),
#endif   /* SIGIO */
#ifdef SIGIOT
    SIGNAL (IOT),
#endif   /* SIGIOT */
#ifdef SIGK32
    SIGNAL (K32),
#endif   /* SIGK32 */
#ifdef SIGKILL
    SIGNAL (KILL),
#endif   /* SIGKILL */
#ifdef SIGLOST
    SIGNAL (LOST),
#endif   /* SIGLOST */
#ifdef SIGLWP
    SIGNAL (LWP),
#endif   /* SIGLWP */
#ifdef SIGPIPE
    SIGNAL (PIPE),
#endif   /* SIGPIPE */
#ifdef SIGPOLL
    SIGNAL (POLL),
#endif   /* SIGPOLL */
#ifdef SIGPROF
    SIGNAL (PROF),
#endif   /* SIGPROF */
#ifdef SIGPTINTR
    SIGNAL (PTINTR),
#endif   /* SIGPTINTR */
#ifdef SIGPTRESCHED
    SIGNAL (PTRESCHED),
#endif   /* SIGPTRESCHED */
#ifdef SIGPWR
    SIGNAL (PWR),
#endif   /* SIGPWR */
#ifdef SIGQUIT
    SIGNAL (QUIT),
#endif   /* SIGQUIT */
#ifdef SIGRESTART
    SIGNAL (RESTART),
#endif   /* SIGRESTART */
#ifdef SIGRESV
    SIGNAL (RESV),
#endif   /* SIGRESV */
#ifdef SIGSEGV
    SIGNAL (SEGV),
#endif   /* SIGSEGV */
#ifdef SIGSTKFLT
    SIGNAL (STKFLT),
#endif   /* SIGSTKFLT */
#ifdef SIGSTOP
    SIGNAL (STOP),
#endif   /* SIGSTOP */
#ifdef SIGSYS
    SIGNAL (SYS),
#endif   /* SIGSYS */
#ifdef SIGTERM
    SIGNAL (TERM),
#endif   /* SIGTERM */
#ifdef SIGTHAW
    SIGNAL (THAW),
#endif   /* SIGTHAW */
#ifdef SIGTRAP
    SIGNAL (TRAP),
#endif   /* SIGTRAP */
#ifdef SIGTSTP
    SIGNAL (TSTP),
#endif   /* SIGTSTP */
#ifdef SIGTTIN
    SIGNAL (TTIN),
#endif   /* SIGTTIN */
#ifdef SIGTTOU
    SIGNAL (TTOU),
#endif   /* SIGTTOU */
#ifdef SIGUNUSED
    SIGNAL (UNUSED),
#endif   /* SIGUNUSED */
#ifdef SIGURG
    SIGNAL (URG),
#endif   /* SIGURG */
#ifdef SIGUSR1
    SIGNAL (USR1),
#endif   /* SIGUSR1 */
#ifdef SIGUSR2
    SIGNAL (USR2),
#endif   /* SIGUSR2 */
#ifdef SIGVTALRM
    SIGNAL (VTALRM),
#endif   /* SIGVTALRM */
#ifdef SIGWAITING
    SIGNAL (WAITING),
#endif   /* SIGWAITING */
#ifdef SIGWINCH
    SIGNAL (WINCH),
#endif   /* SIGWINCH */
#ifdef SIGWINDOW
    SIGNAL (WINDOW),
#endif   /* SIGWINDOW */
#ifdef SIGXCPU
    SIGNAL (XCPU),
#endif   /* SIGXCPU */
#ifdef SIGXFSZ
    SIGNAL (XFSZ),
#endif   /* SIGXFSZ */
#ifdef SIGXRES
    SIGNAL (XRES),
#endif   /* SIGXRES */
    { -1, 0 }
};

/**
   Compare two characters in a case-insensitive manner

   @param c1 first character to compare
   @param c2 second character to compare
   @return an integer less than, equal to, or greater than 0, coresponding
   to whether c1 is less than, equal to, or greater than c2 when compared
   in a case insensitive manner.
*/
static int
rw_charcasecmp (char c1, char c2)
{
    typedef unsigned char UChar; 
    return tolower ((UChar)c1) - tolower ((UChar)c2);
}

/**
   Reimplementation of the POSIX strcasecmp function.

   This is a simplistic (re)implementation of the strcasecmp function
   specified in the XSI extension to the IEEE Std 1003.1 (POSIX) standard.

   @param s1 pointer to first string to compare
   @param s2 pointer to second string to compare
   @return an integer less than, equal to, or greater than 0, coresponding
   to whether s1 is less than, equal to, or greater than s2 when compared
   in a case insensitive manner.
*/
static int
rw_strcasecmp (const char* s1, const char* s2)
{
    int delta;

    assert (0 != s1);
    assert (0 != s2);

    for (delta = rw_charcasecmp (*s1, *s2); 
         *s1 && *s2 && 0 == delta; 
         delta = rw_charcasecmp (*(++s1), *(++s2)));
    return delta;
}

int
get_signo (const char* signame)
{
    size_t i;
    typedef unsigned char UChar; 

    assert (0 != signame);

    if (isdigit (signame [0])) {
        char *junk;
        int trans = strtol (signame, &junk, 10);

        if (0 == *junk && 0 == errno)
            return trans;
        else
            return -1;
    }

    if (   's' == tolower ((UChar)signame [0]) 
        && 'i' == tolower ((UChar)signame [1]) 
        && 'g' == tolower ((UChar)signame [2]))
        signame += 3;
    
    for (i = 0; signal_names [i].str; ++i) {
        if (0 == rw_strcasecmp (signal_names [i].str, signame)) {
            return signal_names [i].val;
        }
    }
    
    return -1;
}

const char* 
get_signame (int signo)
{
    size_t i;
    static char def [32];

    for (i = 0; signal_names [i].str; ++i) {
        if (signal_names [i].val == signo) {
            return signal_names [i].str;
        }
    }

    /* We've run out of known signal numbers, so use a default name */
    sprintf (def, "SIG#%d", signo);
    return def;
}

#if !defined (_WIN32) && !defined (_WIN64)
/**
   Callback used to set the alarm_timeout flag in response to recieving
   the signal SIGALRM

   @param signo the signal recieved (should be SIGALRM)
   @see alarm_timeout
*/
#ifdef __cplusplus
extern "C" {
#endif

static void
handle_alrm (int signo)
{
    if (SIGALRM == signo)
        alarm_timeout = 1;
}

/**
   Callback used to gracefully terminate the utility if signaled to do so
   while running a target

   @param signo the signal recieved (should be in {SIGHUP, SIGINT, SIGQUIT, 
   SIGTERM})
   @see alarm_timeout

*/
static void
handle_term_signal (int signo)
{
    kill_signal = signo;
    alarm_timeout = 1;
}

typedef void (*alarm_handler)(int);

#ifdef __cplusplus
}
#endif

/**
   Monitors child_pid and determines how/when terminated.

   This method is built around a waitpid loop, which is responsible for
   determining the status of child_pid.  This loop breaks when we've detected
   that the child process has terminated, or when we conclude it won't 
   terminate.  To prevent the child process from running forever, we set 
   handle_alrm as the handler for SIGALRM using the sigaction system call and 
   set a timeout using the alarm () system call.  If this timeout expires, we 
   try to kill the child process using several different signals.

   @todo add better handling for corner conditions

   @param child_pid process ID number for the child process to monitor
   @return structure describing how the child procees exited
   @see handle_alrm
   @see alarm_timeout
*/
static void
wait_for_child (pid_t child_pid, int timeout, struct target_status* result)
{
    /* note that processes with no controlling terminal ignore
       the SIGINT and SIGQUIT signals
    */
    static const int signals [] = {
        SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGKILL, SIGKILL
    };

    static const unsigned sigcount = sizeof (signals) / sizeof (int);

    unsigned siginx = 0;

    int stopped = 0;

#ifdef WCONTINUED
    int waitopts = WUNTRACED | WCONTINUED;
#else
    int waitopts = WUNTRACED;
#endif

    int status;

    assert (1 < child_pid);

    /* Clear timeout */
    alarm_timeout = 0;

    /* Set handler (if needed).
    */
    rw_signal (SIGALRM, handle_alrm);
    
    /* Set handlers for SIGHUP, SIGINT, SIGQUIT, SIGTERM so we can kill the
       child process prior to dieing.
    */
    kill_signal = 0;

    rw_signal (SIGHUP, handle_term_signal);
    rw_signal (SIGINT, handle_term_signal);
    rw_signal (SIGQUIT, handle_term_signal);
    rw_signal (SIGTERM, handle_term_signal);

    if (timeout > 0)
        alarm (timeout);

    while (1) {
        const pid_t wait_pid = waitpid (child_pid, &status, waitopts);
        if (child_pid == wait_pid) {
            if (WIFEXITED (status)) {
                result->exit = WEXITSTATUS (status);
                switch (result->exit) {
                case 126:
                    result->status = ST_EXIST;
                    break;
                case 127:
                    result->status = ST_EXECUTE;
                    break;
                }
                break; /*we've got an exit state, so let's bail*/
            }
            else if (WIFSIGNALED (status)) {
                result->exit = WTERMSIG (status);
                result->signaled = 1;
                break; /*we've got an exit state, so let's bail*/
            }
            else if (WIFSTOPPED (status))
                stopped = status;
#ifdef WIFCONTINUED /*Perhaps we should guard WIFSTOPPED with this guard also */
            else if (WIFCONTINUED (status))
                stopped = 0;
#endif
            else
                ;   /* huh? */
        }
        else if ((pid_t)-1 == wait_pid) {
            if (EINTR == errno && alarm_timeout) {
                /* timeout elapsed, so send a signal to the child */
                if (stopped) {
                    /* If the child process is stopped, it is incapable of
                       recieving signals.  Therefore, we'll record this
                       and break out of the loop.
                    */
                    result->exit = WTERMSIG (stopped);
                    result->signaled = 1;
                    break;
                }

                if (0 != kill (-child_pid, signals [siginx])) {
                    if (ESRCH == errno)
                        /* ESRCH means 'No process (group) found'.  Since 
                           there aren't any processes in the process group, 
                           we'll continue so we can collect the return value
                           if needed.
                        */
                        continue;
                    /* In addition to ESRCH, kill () may also set errno to 
                       EINVAL or EPERM, according to the POSIX spec, in 
                       addition to any platform specific extensions.
                       EPERM means 'No permissions to signal any recieving 
                       process'.  It is unlikely that this situation will
                       change, but we will try the remaining signals in the
                       signals array, in the same manner as if the signal had
                       been sent correctly.
                       EINVAL means 'The signal is an invalid or unsupported
                       signal number'.  As the signal number macros used in 
                       the signal array are hard coded, issues should be 
                       detected at compile time, not run time.  This is not a
                       fatal situation, so the remainder of signals in the
                       signal array will be tried, as if this transmission
                       had been successfull.
                       The correct behavior for any platform-specific 
                       extensions needs to be evaluated, but we are treating
                       them like EPERM or EINVAL at this time. */
                }

                /* Consider recording the signal used here.*/

                ++siginx;

                /* Step to the next signal */
                if (siginx >= sigcount) {
                    /* Still running, but we've run out of signals to try
                       Therefore, we'll set error flags and break out of 
                       the loop.
                    */
                    result->status = ST_NOT_KILLED;
                    break;
                }

                /* Reset the alarm */
                alarm_timeout = 0;
                alarm (1);
            }
            else if (EINTR == errno && !alarm_timeout) {
                /* continue iterating */
            }
            else if (EINVAL == errno) {
                if (waitopts)
                    /* bad waitpid options, reset to 0 and try again */
                    waitopts = 0;
                else
                    ; /* Now what? */
            }
            else if (ECHILD == errno) {
                /* should not happen */
                warn ("waitpid (%d) error: %s\n", (int)child_pid, 
                      strerror (errno));
            }
            else {
                /* waitpid () error */
                warn ("waitpid (%d) error: %s\n", (int)child_pid, 
                      strerror (errno));
            }
        }
        else if ((pid_t)0 == wait_pid) {
            /* should not happen */
        }
        else {
            /* what the heck? */
        }
    }

    /* Clear alarm */
    alarm (0);

    /* Kill/cleanup any grandchildren. */
    /* On solaris, this logic tries to avoid the situation where grandchild
       process times are rolled into the timing of a later process */
    while (siginx < sigcount && 0 == kill (-child_pid, signals [siginx])) {
        ++siginx;
        sleep (1);
    }

    /* Check if we were signaled to quit. */
    if (kill_signal) {
        /* Reset the handlers to normal */
        rw_signal (SIGHUP, SIG_DFL);
        rw_signal (SIGINT, SIG_DFL);
        rw_signal (SIGQUIT, SIG_DFL);
        rw_signal (SIGTERM, SIG_DFL);

        if (0 > raise (kill_signal))
            terminate (1, "raise(%s) failed: %s\n",
                       get_signame (kill_signal), strerror (errno));
    }
}


/**
   Replaces one file descriptor with a second, closing second after replacing
   the first.

   @param source file descriptor to copy
   @param dest file descriptor to replace
   @param name human understanable name for dest, used in error messages
*/
static void
replace_file (int source, int dest, const char* name)
{
    int result;

    assert (source != dest);
    assert (0 <= source);
    assert (0 <= dest);
    assert (0 != name);

    result = dup2 (source, dest);
    if (-1 == result)
        terminate (1, "redirecting to %s failed: %s\n", name, 
                   strerror (errno));

    result = close (source);
    if (-1 == result)
        terminate (1, "closing source for %s redirection failed: %s\n", 
                   name, strerror (errno));
}

#ifdef _XOPEN_UNIX
/**
    Utility macro to generate an rlimit tuple.
    
    @parm val 'short' resource name (no leading RLIMIT_).
    @param idx rw_rlimit pointer in the target_opts structure
    @see limit_process
    @see target_opts
*/
#undef LIMIT
#define LIMIT(val, idx)   { RLIMIT_ ## val, options->idx, #val }

/**
   Set process resource limits, based on the child_limits global.

   This method uses the LIMIT macro to build an internal array of limits to 
   try setting.  If setrlimit fails, we print a warning message (into the 
   output file) and move on.
   @param options structure containing limits to set.
*/
static void
limit_process (const struct target_opts* options)
{
    static const struct {
        int resource;
        rw_rlimit* limit;
        const char* name;
    } limits[] = {
#ifdef RLIMIT_CORE
        LIMIT (CORE, core),
#endif   /* RLIMIT_CORE */
#ifdef RLIMIT_CPU
        LIMIT (CPU, cpu),
#endif   /* RLIMIT_CPU */
#ifdef RLIMIT_DATA
        LIMIT (DATA, data),
#endif   /* RLIMIT_DATA */
#ifdef RLIMIT_FSIZE
        LIMIT (FSIZE, fsize),
#endif   /* RLIMIT_FSIZE */
#ifdef RLIMIT_NOFILE
        LIMIT (NOFILE, nofile),
#endif   /* RLIMIT_NOFILE */
#ifdef RLIMIT_STACK
        LIMIT (STACK, stack),
#endif   /* RLIMIT_STACK */
#ifdef RLIMIT_AS
        LIMIT (AS, as),
#endif   /* RLIMIT_AS */    
        { 0, 0, 0 }
    };

    for (size_t i = 0; limits [i].name; ++i) {
        struct rlimit local;

        if (!limits [i].limit)
            continue;

        memcpy (&local, limits [i].limit, sizeof local);

        if (setrlimit (limits [i].resource, &local)) {
            warn ("error setting process limits for %s (soft: %lu, hard: "
                  "%lu): %s\n", limits [i].name, local.rlim_cur, 
                  local.rlim_max, strerror (errno));
        }
    }
}
#endif /* _XOPEN_UNIX */

/**
   Calculates the amount of resources used by the child processes.

   This method uses the times() system call to calculate the resources used 
   by the child process.  However, times() only is able to calcualte agragate 
   usage by all child processes, not usage by a specific child process.
   Therefore, we must keep a running tally of how much resources had been used
   the previous time we calculated the usage.  This difference is the resources
   that were used by the process that just completed.

   @param result target_status structure to populate with process usage.
   @param h_clk starting (wall clock) time
   @param h_tms starting (system/user) time
*/
static void
calculate_usage (struct target_status* result, const clock_t h_clk, 
                 const struct tms* const h_tms)
{
    struct tms c_tms;
    clock_t c_clk;

    assert (0 != result);
    assert (0 != h_tms);

    c_clk = times (&c_tms);

    if (-1 == c_clk) {
        warn ("Failed to retrieve ending times: %s", strerror (errno));
        return;
    }

    /* time calculations */
    result->wall_time = c_clk - h_clk;
    result->usr_time  = c_tms.tms_cutime - h_tms->tms_cutime;
    result->sys_time  = c_tms.tms_cstime - h_tms->tms_cstime;
}

void exec_file (const struct target_opts* options, struct target_status* result)
{
    const pid_t child_pid = fork ();

    assert (0 != options);
    assert (0 != options->argv);
    assert (0 != options->argv [0]);

    if (0 == child_pid) {   /* child */
        const char* const target_name = get_target ();
        FILE* error_file;

        assert (0 != target_name);

        /* Set process group ID (so entire group can be killed)*/
        {
            const pid_t pgroup = setsid ();
            if (getpid () != pgroup)
                terminate (1, "Error setting process group: %s\n",
                           strerror (errno));
        }

        /* Cache stdout for use if execv () fails */
        {
            const int error_cache = dup (2);
            if (-1 == error_cache)
                terminate (1, "Error duplicating stderr: %s\n", 
                           strerror (errno));

            error_file = fdopen (error_cache,"a");
            if (0 == error_file)
                terminate (1, "Error opening file handle  from cloned "
                           "stderr file descriptor: %s\n", strerror (errno));
        }

        /* Redirect stdin */
        {
            const int intermit = open (options->infname, O_RDONLY);
            replace_file (intermit, 0, "stdin");
        }

        /* Redirect stdout */
        {
            int intermit;

            intermit = open (options->outfname,
                             O_WRONLY | O_CREAT | O_TRUNC, 
                             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

            if (-1 == intermit)
                terminate (1, "Error opening %s for output redirection: "
                           "%s\n", options->outfname, strerror (errno));

            replace_file (intermit, 1, "stdout");
        }

        /* Redirect stderr */
        if (-1 == dup2 (1, 2))
            terminate (1, "Redirection of stderr to stdout failed: %s\n", 
                       strerror (errno));

#ifdef _XOPEN_UNIX
        limit_process (options);
#endif /* _XOPEN_UNIX */

        execv (options->argv [0], options->argv);

        fprintf (error_file, "%s (%s): execv (\"%s\", ...) error: %s\n",
                 exe_name, target_name, options->argv [0], strerror (errno));

        exit (1);
    }

    if (-1 == child_pid) {
        result->status = ST_EXECUTE;
        warn ("Unable to create child process for %s: %s\n", options->argv [0],
              strerror (errno));
    }
    else {
        /* parent */
        struct tms h_tms;
        clock_t h_clk = times (&h_tms);
        wait_for_child (child_pid, options->timeout, result);
        if (-1 != h_clk)
            calculate_usage (result, h_clk, &h_tms);
        else
            warn ("Failed to retrieve start times: %s", strerror (errno));
    }
}
#else  /* _WIN{32,64} */

// map between NT_STATUS value and corresponding UNIX signal
static const struct {
    DWORD nt_status;
    int   signal;
} nt_status_map [] = {
    { STATUS_BREAKPOINT,                 SIGTRAP   },
    { STATUS_ACCESS_VIOLATION,           SIGSEGV   },
    { STATUS_STACK_OVERFLOW,             SIGSEGV   },
    { STATUS_HEAP_CORRUPTION,            SIGSEGV   },
    { STATUS_STACK_BUFFER_OVERRUN,       SIGSEGV   },
    { STATUS_IN_PAGE_ERROR,              SIGBUS    },
    { STATUS_ILLEGAL_INSTRUCTION,        SIGILL    },
    { STATUS_PRIVILEGED_INSTRUCTION,     SIGILL    },
    { STATUS_FLOAT_DENORMAL_OPERAND,     SIGFPE    },
    { STATUS_FLOAT_DIVIDE_BY_ZERO,       SIGFPE    },
    { STATUS_FLOAT_INEXACT_RESULT,       SIGFPE    },
    { STATUS_FLOAT_INVALID_OPERATION,    SIGFPE    },
    { STATUS_FLOAT_OVERFLOW,             SIGFPE    },
    { STATUS_FLOAT_UNDERFLOW,            SIGFPE    },
    { STATUS_INTEGER_DIVIDE_BY_ZERO,     SIGFPE    },
    { STATUS_INTEGER_OVERFLOW,           SIGFPE    },
    { STATUS_FLOAT_STACK_CHECK,          SIGSTKFLT },
    { STATUS_INVALID_PARAMETER,          SIGSYS    },
    { STATUS_INVALID_CRUNTIME_PARAMETER, SIGSYS    }
};


/**
   Convert an argv array into a string that can be passed to CreateProcess.

   This method allocates memory which the caller is responsible for free ()ing.
   The provided argv array is converted into a series of quoted strings.

   @param argv argv array to convert
   @return allocated array with converted contents
*/
static char*
merge_argv (char** argv)
{
    size_t len = 0;
    char** opts;
    char* term;
    char* merge;
    char* pos;

    assert (0 != argv);

    for (opts = argv; *opts; ++opts) {
        len += 3; /* for open ", close " and trailing space or null */
        for (term = *opts; *term; ++term) {
            if ('"' == *term || escape_code == *term)
                ++len; /* Escape embedded "s and ^s*/
            ++len;
        }
    }

    pos = merge = (char*)RW_MALLOC (len);
    for (opts = argv; *opts; ++opts) {
        *(pos++) = '"';
        for (term = *opts; *term; ++term) {
            if ('"' == *term || escape_code == *term)
                *(pos++) = escape_code;  /* Escape embedded "s and ^s*/
            *(pos++) = *term;
        }
        *(pos++) = '"';
        *(pos++) = ' ';
    }
    *(pos-1) = '\0'; /* convert trailing space to null */
    return merge;
}

/**
   Wrapper function around warn for windows native API calls.

   @param action Human understandable description of failed action.
   @return Value of GetLastError.
*/
static DWORD
warn_last_error (const char* action)
{
    DWORD error = GetLastError (); 

    if (error) {
        LPTSTR error_text = 0;
        if (FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                           FORMAT_MESSAGE_FROM_SYSTEM, NULL, error,
                           MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPTSTR)&error_text, 0, NULL)) {
            warn ("%s failed with error %d: %s\n", action, error, error_text);
            LocalFree (error_text);
        }
        else {
            warn ("%s failed with error %d.  Additionally, FormatMessage "
                  "failed with error %d.\n", action, error, GetLastError ());
        }
    }
    return error;
}

/**
   Try killing the child process with what (limited) facilities we have at
   our disposal.  Does not collect exit code or close child process handle.

   @param process Process handle of process to kill.
   @param result status structure to record system errors in.
*/
static void
kill_child_process (PROCESS_INFORMATION child, struct target_status* result)
{
    OSVERSIONINFO OSVer;

    OSVer.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
    if (0 == GetVersionEx (&OSVer)) {
        result->status = ST_SYSTEM_ERROR;
        warn_last_error ("Retrieving host version");
        return;
    }
    /* Try to soft kill child process group if it didn't terminate, but only 
       on NT */

    if (VER_PLATFORM_WIN32_NT == OSVer.dwPlatformId) {

        struct sig_event
        {
            DWORD       signal_;
            const char* msg_;
        }
        sig_events [] = {
            { CTRL_C_EVENT,     "Sending child process Control-C"     },
            { CTRL_BREAK_EVENT, "Sending child process Control-Break" }
        };

        for (unsigned long i = 0;
            i < sizeof (sig_events) / sizeof (*sig_events); ++i) {
            DWORD wait_code;

            if (0 == GenerateConsoleCtrlEvent (sig_events [i].signal_,
                                               child.dwProcessId))
                warn_last_error (sig_events [i].msg_);

            wait_code = WaitForSingleObject (child.hProcess, 1000);
            if (WAIT_TIMEOUT == wait_code)
                continue;

            if (WAIT_OBJECT_0 != wait_code) {
                result->status = ST_SYSTEM_ERROR;
                warn_last_error ("Waiting for child process");
            }
            return;
        }
    }
    /* Then hard kill the child process */
    if (0 == TerminateProcess (child.hProcess, 3))
        warn_last_error ("Terminating child process");
    else if (WAIT_FAILED == WaitForSingleObject (child.hProcess, 1000))
        warn_last_error ("Waiting for child process");
}

/* FILETIME to ULONGLONG */
inline ULONGLONG fttoull (const FILETIME& ft)
{
    ULARGE_INTEGER __ft;
    __ft.LowPart  = ft.dwLowDateTime;
    __ft.HighPart = ft.dwHighDateTime;
    return __ft.QuadPart;
}

void exec_file (const struct target_opts* options, struct target_status* result)
{
    char* merged;
    PROCESS_INFORMATION child;
    STARTUPINFO context;
    SECURITY_ATTRIBUTES child_sa = /* SA for inheritable handle. */
          {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
    DWORD real_timeout, wait_code;
    FILETIME start, end;

    assert (0 != options);
    assert (0 != options->argv);
    assert (0 != options->argv [0]);

    real_timeout = (options->timeout > 0) ? options->timeout * 1000 : INFINITE;

    /* Borrow our startup info */
    GetStartupInfo (&context);
    context.dwFlags = STARTF_USESTDHANDLES;

    /* Create I/O handles */
    {
        /* Output redirection */

        context.hStdOutput = CreateFile (options->outfname, GENERIC_WRITE, 
                FILE_SHARE_WRITE, &child_sa, CREATE_ALWAYS, 
                FILE_ATTRIBUTE_NORMAL, NULL);
        if (INVALID_HANDLE_VALUE == context.hStdOutput) { 
            result->status = ST_SYSTEM_ERROR;
            warn_last_error ("Opening child output stream");
            return;
        }

        context.hStdError = context.hStdOutput;

        /* Input redirection */
        context.hStdInput =
            CreateFile (options->infname, GENERIC_READ, FILE_SHARE_READ, 
                        &child_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if (INVALID_HANDLE_VALUE == context.hStdInput) { 
            CloseHandle (context.hStdOutput);
            result->status = ST_SYSTEM_ERROR;
            warn_last_error ("Opening child input stream");
            return;
        }
    }    

    merged = merge_argv (options->argv);

    /* set appropriate error mode (the child process inherits this
       error mode) to disable displaying the critical-error-handler
       and general-protection-fault message boxes */
    UINT old_mode = SetErrorMode (SEM_FAILCRITICALERRORS
                                | SEM_NOGPFAULTERRORBOX);

    /* Create the child process in suspended state */
    if (0 == CreateProcess (options->argv [0], merged, 0, 0, 1, 
        CREATE_NEW_PROCESS_GROUP | CREATE_SUSPENDED, 0, 0, &context, &child)) {
        /* record the status if we failed to create the process */
        result->status = ST_SYSTEM_ERROR;
        warn_last_error ("Creating child process");;
    }

    /* restore the previous error mode */
    SetErrorMode (old_mode);

    /* Clean up handles */
    if (context.hStdInput)
        if (0 == CloseHandle (context.hStdInput))
            warn_last_error ("Closing child input stream");
    if (0 == CloseHandle (context.hStdOutput))
        warn_last_error ("Closing child output stream");

    /* Clean up argument string*/
    free (merged);

    /* Return if we failed to create the child process */
    if (ST_SYSTEM_ERROR == result->status)
        return;

#if _WIN32_WINNT >= 0x0500
    if (options->as) {
        if (HANDLE hJob = CreateJobObject (NULL, NULL)) {
            if (AssignProcessToJobObject (hJob, child.hProcess)) {
                JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = { 0 };
                
                job_info.BasicLimitInformation.LimitFlags =
                    JOB_OBJECT_LIMIT_PROCESS_MEMORY;
                
                const rw_rlimit* as = options->as;
                job_info.ProcessMemoryLimit =
                    as->rlim_cur < as->rlim_max ? as->rlim_cur : as->rlim_max;

                if (!SetInformationJobObject (hJob,
                                              JobObjectExtendedLimitInformation,
                                              &job_info, sizeof (job_info)))
                    warn_last_error ("Setting process limits");
            }
            else 
                warn_last_error ("Assigning process to job object");

            if (!CloseHandle (hJob))
                warn_last_error ("Closing job object handle");
        }
        else
            warn_last_error ("Creating job object");
    }
#endif   // _WIN32_WINNT >= 0x0500

    /* Check the wall clock before resuming the process */
    GetSystemTimeAsFileTime(&start);

    if (DWORD (-1) == ResumeThread (child.hThread))
        warn_last_error ("Resuming process");

    if (0 == CloseHandle (child.hThread))
        warn_last_error ("Closing child main thread handle");

    /* Wait for the child process to terminate */
    wait_code = WaitForSingleObject (child.hProcess, real_timeout);

    switch (wait_code) {
    case WAIT_OBJECT_0:
        break;
    case WAIT_TIMEOUT:
        /* Child process didn't shut down, so note that we killed it. */
        result->status = ST_KILLED;

        kill_child_process (child, result);
        break;
    default:
        result->status = ST_SYSTEM_ERROR;
        warn_last_error ("Waiting for child process");
    }

    /* Calculate wall clock time elapsed while the process ran */
    GetSystemTimeAsFileTime(&end);

    /* 100 nanosecond units in a second */
    const DWORD UNITS_PER_SEC = 10000000;
    const DWORD UNITS_PER_CLOCK = UNITS_PER_SEC / CLOCKS_PER_SEC;
    assert (UNITS_PER_CLOCK * CLOCKS_PER_SEC == UNITS_PER_SEC);

#if _WIN32_WINNT >= 0x0500
    FILETIME stime, utime;

    if (GetProcessTimes (child.hProcess, &start, &end, &stime, &utime)) {
        result->usr_time = clock_t (fttoull (utime) / UNITS_PER_CLOCK);
        result->sys_time = clock_t (fttoull (stime) / UNITS_PER_CLOCK);
    }
    else
        warn_last_error ("Getting child process times");
#endif  // _WIN32_WINNT >= 0x0500

    result->wall_time =
        clock_t ((fttoull (end) - fttoull (start)) / UNITS_PER_CLOCK);


    if (0 == GetExitCodeProcess (child.hProcess, (LPDWORD)&result->exit)) {
        warn_last_error ("Retrieving child process exit code");
        result->status = ST_SYSTEM_ERROR;
    }

    if (0 == CloseHandle (child.hProcess))
        warn_last_error ("Closing child process handle");

    for (int i = 0; i < sizeof (nt_status_map) / sizeof (*nt_status_map); ++i) {
        if (nt_status_map [i].nt_status == DWORD (result->exit)) {
            result->exit = nt_status_map [i].signal;
            result->signaled = 1;
            break;
        }
    }
}

#endif  /* _WIN{32,64} */
