/************************************************************************ * * 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 /* for assert */ #include /* for tolower */ #include /* for errno */ #include /* for O_*, */ #include #include #include #include /* for str*, mem* */ #if !defined (_WIN32) && !defined (_WIN64) # include /* for close, dup, exec, fork */ # include # include /* for times - is this XSI? */ # ifdef _XOPEN_UNIX # include /* for setlimit(), RLIMIT_CORE, ... */ # endif #else # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0500 # endif # include /* 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 /* for S_* */ #include #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} */