blob: a6667a728fc871ec57319362a1806a8121f9661f [file] [log] [blame]
/*===-- timeit.c - LLVM Test Suite Timing Tool ------------------*- C++ -*-===*\
|* *|
|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *|
|* Exceptions. *|
|* See https://llvm.org/LICENSE.txt for license information. *|
|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *|
|* *|
\*===----------------------------------------------------------------------===*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#if _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/wait.h>
#endif
/* Enumeration for our exit status codes. */
enum ExitCode {
/* \brief Indicates a failure monitoring the target. */
EXITCODE_MONITORING_FAILURE = 66,
/* \brief Indicates a failure in exec() which usually means an invalid program
* name. */
EXITCODE_EXEC_FAILURE = 67,
EXITCODE_EXEC_NOENTRY = 127,
EXITCODE_EXEC_NOPERMISSION = 126,
/* \brief Indicates that we were unexpectedly signalled(). */
EXITCODE_SIGNALLED = 68,
/* \brief Indicates the child was signalled. */
EXITCODE_CHILD_SIGNALLED = 69
};
/* \brief Record our own program name, for error messages. */
static const char *g_program_name = 0;
/* \brief Record the child command name, for error messages. */
static const char *g_target_program = 0;
/* \brief If given, report output in POSIX mode format. */
static int g_posix_mode = 0;
/* \brief If non-zero, execute the program with a timeout of the given number
* of seconds.
*/
static int g_timeout_in_seconds = 0;
/* \brief If non-zero, the PID of the process being monitored. */
#ifndef _WIN32
static pid_t g_monitored_pid = 0;
#else
static DWORD g_monitored_pid = 0;
static HANDLE g_monitored_phandle = INVALID_HANDLE_VALUE;
#endif
/* \brief If non-zero, the path to attempt to chdir() to before executing the
* target. */
static const char *g_target_exec_directory = 0;
/* \brief If non-zero, the path to write the summary information to (exit status
* and timing). */
static const char *g_summary_file = 0;
/* \brief If non-zero, the path to redirect the target standard input to. */
static const char *g_target_redirect_input = 0;
/* \brief If non-zero, the path to redirect the target stdout to. */
static const char *g_target_redirect_stdout = 0;
/* \brief If non-zero, the path to redirect the target stderr to. */
static const char *g_target_redirect_stderr = 0;
/* \brief If non-zero, append exit status at end of output file. */
static int g_append_exitstats = 0;
#ifdef _WIN32
/* \brief If platform does not define rlim_t type, then define it as int. */
typedef int rlim_t;
#endif
/* @name Resource Limit Variables */
/* @{ */
/* \brief If non-sentinel, the CPU time limit to set for the target. */
static rlim_t g_target_cpu_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the stack size limit to set for the target. */
static rlim_t g_target_stack_size_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the data size limit to set for the target. */
static rlim_t g_target_data_size_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the RSS size limit to set for the target. */
static rlim_t g_target_rss_size_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the file size limit to set for the target. */
static rlim_t g_target_file_size_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the core limit to set for the target. */
static rlim_t g_target_core_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the file count limit to set for the target. */
static rlim_t g_target_file_count_limit = ~(rlim_t) 0;
/* \brief If non-sentinel, the subprocess count limit to set for the target. */
static rlim_t g_target_subprocess_count_limit = ~(rlim_t) 0;
/* @} */
static double sample_wall_time(void) {
#ifndef _WIN32
struct timeval t;
gettimeofday(&t, NULL);
return (double)t.tv_sec + t.tv_usec * 1.e-6;
#else
/* frequency init below is not thread-safe. Consider using a mutex if
* sample_wall_time is required to be thread-safe in future. */
static LARGE_INTEGER frequency = {0};
if (frequency.QuadPart == 0)
QueryPerformanceFrequency(&frequency);
LARGE_INTEGER timeval;
QueryPerformanceCounter(&timeval);
return (double)timeval.QuadPart / frequency.QuadPart;
#endif
}
static int streq(const char *a, const char *b) { return strcmp(a, b) == 0; }
#ifndef _WIN32
static void terminate_handler(int signal) {
/* If we are monitoring a process, kill its process group and assume we will
* complete normally.
*/
if (g_monitored_pid) {
fprintf(stderr, ("%s: error: received signal %d. "
"killing monitored process(es): %s\n"),
g_program_name, signal, g_target_program);
/* Kill the process group of monitored_pid. */
kill(-g_monitored_pid, SIGKILL);
return;
}
fprintf(stderr, "%s: error: received signal %d. exiting.\n",
g_program_name, signal);
/* Otherwise, we received a signal we should treat as for ourselves, and exit
* quickly. */
_exit(EXITCODE_SIGNALLED);
}
static void timeout_handler(int signal) {
(void)signal;
fprintf(stderr, "%s: TIMING OUT PROCESS: %s\n", g_program_name,
g_target_program);
/* We should always be monitoring a process when we receive an alarm. Kill its
* process group and assume we will terminate normally.
*/
kill(-g_monitored_pid, SIGKILL);
}
#define set_resource_limit(resource, value) \
set_resource_limit_actual(#resource, resource, value)
static void set_resource_limit_actual(const char *resource_name, int resource,
rlim_t value) {
/* Get the current limit. */
struct rlimit current;
getrlimit(resource, &current);
/* Set the limits to as close as requested, assuming we are not super-user. */
struct rlimit requested;
requested.rlim_cur = requested.rlim_max =
(value < current.rlim_max) ? value : current.rlim_max;
if (setrlimit(resource, &requested) < 0) {
fprintf(stderr, "%s: warning: unable to set limit for %s (to {%lu, %lu})\n",
g_program_name, resource_name, (unsigned long)requested.rlim_cur,
(unsigned long)requested.rlim_max);
}
}
#else
/* Windows-specific implementation of fopen. */
static FILE *windows_fopen(const char *filename, const char *mode) {
FILE *file;
if (fopen_s(&file, filename, mode))
return NULL;
return file;
}
#define fopen(filename, mode) windows_fopen(filename, mode)
static HANDLE g_outfile_handle = INVALID_HANDLE_VALUE;
static HANDLE g_job_handle = INVALID_HANDLE_VALUE;
/* Exit status global variable. */
static int exit_status = 0;
/* Kill process group function. */
void kill_process_group(DWORD groupId) {
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, groupId);
}
/* Control handler function. */
BOOL WINAPI ctrl_handler(DWORD fdw_ctrl_type) {
switch (fdw_ctrl_type) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
exit_status = 1;
kill_process_group(g_monitored_pid);
return FALSE;
default:
return FALSE;
}
}
/* Timer callback function. */
VOID CALLBACK timer_callback(PVOID lpParam, BOOLEAN TimerOrWaitFired) {
exit_status = 2;
kill_process_group(g_monitored_pid);
}
LPSTR create_process_commandline(char *const argv[]) {
/* Convert argv to CreateProcess style commandline. */
char *command_line = malloc(1);
command_line[0] = '\0';
int argc = 0;
while (argv[argc] != NULL) {
int size = strlen(command_line) + strlen(argv[argc]) + 2;
command_line = realloc(command_line, size);
strcat_s(command_line, size, argv[argc]);
strcat_s(command_line, size, " ");
argc++;
}
command_line[strlen(command_line) - 1] = '\0';
return command_line;
}
#endif
static int monitor_child_process(double start_time) {
double real_time, user_time, sys_time;
int res, status;
#ifndef _WIN32
struct rusage usage;
/* If we are running with a timeout, set up an alarm now. */
if (g_timeout_in_seconds) {
sigset_t masked;
sigemptyset(&masked);
sigaddset(&masked, SIGALRM);
alarm(g_timeout_in_seconds);
}
/* Wait for the process to terminate. */
do {
res = waitpid(g_monitored_pid, &status, 0);
} while (res < 0 && errno == EINTR);
if (res < 0) {
perror("waitpid");
return EXITCODE_MONITORING_FAILURE;
}
/* Record the real elapsed time as soon as we can. */
real_time = sample_wall_time() - start_time;
/* Just in case, kill the child process group. */
kill(-g_monitored_pid, SIGKILL);
/* Collect the other resource data on the children. */
if (getrusage(RUSAGE_CHILDREN, &usage) < 0) {
perror("getrusage");
return EXITCODE_MONITORING_FAILURE;
}
user_time = (double) usage.ru_utime.tv_sec + usage.ru_utime.tv_usec/1000000.0;
sys_time = (double) usage.ru_stime.tv_sec + usage.ru_stime.tv_usec/1000000.0;
/* If the process was signalled, report a more interesting status. */
int exit_status;
if (WIFSIGNALED(status)) {
fprintf(stderr, "%s: error: child terminated by signal %d\n",
g_program_name, WTERMSIG(status));
/* Propagate the signalled status to the caller. */
exit_status = 128 + WTERMSIG(status);
} else if (WIFEXITED(status)) {
exit_status = WEXITSTATUS(status);
} else {
/* This should never happen, but if it does assume some kind of failure. */
exit_status = EXITCODE_MONITORING_FAILURE;
}
#else
/* If we are running with a timeout, set up an alarm now. */
HANDLE timer_handle = INVALID_HANDLE_VALUE;
HANDLE timer_queue_handle = INVALID_HANDLE_VALUE;
if (g_timeout_in_seconds) {
/* Create a timer queue and then create a timer that fires after
* g_timeout_in_seconds seconds. */
timer_queue_handle = CreateTimerQueue();
if (timer_queue_handle == NULL) {
fprintf(stderr, "Failed to create timer queue!\n");
return 1;
}
if (!CreateTimerQueueTimer(&timer_handle, timer_queue_handle,
timer_callback, NULL,
g_timeout_in_seconds * 1000, 0, 0)) {
fprintf(stderr, "Failed to create timer!\n");
return 1;
}
}
/* Wait for the child process to finish, or the timer to fire. */
WaitForSingleObject(g_monitored_phandle, INFINITE);
/* Just in case, kill the child process group. */
kill_process_group(g_monitored_pid);
/* Record the real elapsed time as soon as we can. */
real_time = sample_wall_time() - start_time;
/* Check if process finished cleanly */
DWORD exit_code;
if (!GetExitCodeProcess(g_monitored_phandle, &exit_code)) {
fprintf(stderr, "Failed to get exit code!\n");
}
/* If the process was signalled, report a more interesting status. */
if (!exit_status) {
if (exit_status == 1) {
fprintf(stderr, "%s: error: child terminated by signal %d\n",
g_program_name, exit_status);
/* Propagate the signalled status to the caller. */
exit_status = EXITCODE_CHILD_SIGNALLED;
} else if (exit_status == 2) {
fprintf(stderr, "%s: error: child terminated by signal %d\n",
g_program_name, exit_status);
exit_status = EXITCODE_CHILD_SIGNALLED;
}
}
/* Set exit_status to 1 if process was not signalled, but it returned
* non-zero exit code. */
if(!exit_status && exit_code == 1)
exit_status = 1;
/* Clean up the timer and timer queue. */
if (g_timeout_in_seconds) {
if (timer_handle)
DeleteTimerQueueTimer(timer_queue_handle, timer_handle, NULL);
if (timer_queue_handle)
DeleteTimerQueue(timer_queue_handle);
}
/* Query the job object information. */
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accountingInfo;
if (QueryInformationJobObject(
g_job_handle, JobObjectBasicAccountingInformation, &accountingInfo,
sizeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), NULL)) {
user_time = (double)accountingInfo.TotalUserTime.QuadPart / 10000.0;
sys_time = (double)accountingInfo.TotalKernelTime.QuadPart / 10000.0;
} else {
fprintf(stderr, "Failed to query job object information! Error code: %u\n",
GetLastError());
exit_status = EXITCODE_MONITORING_FAILURE;
return exit_status;
}
#endif
/* If we are not using a summary file, report the information as
* /usr/bin/time would. */
if (!g_summary_file) {
if (g_posix_mode) {
fprintf(stderr, "real %12.4f\nuser %12.4f\nsys %12.4f\n",
real_time, user_time, sys_time);
} else {
fprintf(stderr, "%12.4f real %12.4f user %12.4f sys\n",
real_time, user_time, sys_time);
}
} else {
/* Otherwise, write the summary data in a simple parsable format. */
FILE *fp = fopen(g_summary_file, "w");
if (!fp) {
perror("fopen");
return EXITCODE_MONITORING_FAILURE;
}
fprintf(fp, "exit %d\n", exit_status);
fprintf(fp, "%-10s %.4f\n", "real", real_time);
fprintf(fp, "%-10s %.4f\n", "user", user_time);
fprintf(fp, "%-10s %.4f\n", "sys", sys_time);
fclose(fp);
}
#ifdef _WIN32
if (g_outfile_handle != INVALID_HANDLE_VALUE)
CloseHandle(g_outfile_handle);
#endif
if (g_append_exitstats && g_target_program) {
FILE *fp_stdout = fopen(g_target_redirect_stdout, "a");
if (!fp_stdout) {
perror("fopen");
return EXITCODE_MONITORING_FAILURE;
}
fprintf(fp_stdout, "exit %d\n", exit_status);
fclose(fp_stdout);
/* let timeit itself report success */
exit_status = 0;
}
return exit_status;
}
static int execute_target_process(char *const argv[]) {
#if _WIN32
SECURITY_ATTRIBUTES sec_attr;
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = NULL;
STARTUPINFO sec_info;
memset(&sec_info, 0, sizeof(STARTUPINFO));
sec_info.cb = sizeof(STARTUPINFO);
sec_info.dwFlags |= STARTF_USESTDHANDLES;
sec_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
sec_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
sec_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
/* Set up the input file handle for the child process. */
if (g_target_redirect_input) {
HANDLE input_file_handle =
CreateFile(g_target_redirect_input, GENERIC_READ, 0, &sec_attr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (input_file_handle == INVALID_HANDLE_VALUE) {
perror("CreateFile failed for redirected input");
return EXITCODE_MONITORING_FAILURE;
}
sec_info.hStdInput = input_file_handle;
}
/* Set up the output file handle for the child process. */
if (g_target_redirect_stdout) {
HANDLE output_file_handle =
CreateFile(g_target_redirect_stdout, GENERIC_WRITE, 0, &sec_attr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (output_file_handle == INVALID_HANDLE_VALUE) {
perror("CreateFile failed for redirected output");
return EXITCODE_MONITORING_FAILURE;
}
sec_info.hStdOutput = output_file_handle;
g_outfile_handle = output_file_handle;
}
/* Set up the error file handle for the child process. */
if (g_target_redirect_stderr) {
if (!streq(g_target_redirect_stdout, g_target_redirect_stderr)) {
HANDLE error_file_handle =
CreateFile(g_target_redirect_stderr, GENERIC_WRITE, 0, &sec_attr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (error_file_handle == INVALID_HANDLE_VALUE) {
perror("CreateFile failed for redirected error");
return EXITCODE_MONITORING_FAILURE;
}
sec_info.hStdError = error_file_handle;
} else
sec_info.hStdError = sec_info.hStdOutput;
}
/* Create a job object to manage the child process resources. */
g_job_handle = CreateJobObject(NULL, NULL);
if (g_job_handle == NULL) {
printf("Failed to create the Job Object. Error code: %lu\n",
GetLastError());
return 1;
}
/* Configure the job object to set a CPU time limit. */
if (g_target_cpu_limit != ~(rlim_t)0) {
JOBOBJECT_BASIC_LIMIT_INFORMATION basicInfo;
ZeroMemory(&basicInfo, sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION));
basicInfo.LimitFlags = JOB_OBJECT_LIMIT_JOB_TIME;
/* Convert the seconds to 100-nanosecond intervals. */
basicInfo.PerJobUserTimeLimit.QuadPart = g_target_cpu_limit * 10000000;
if (!SetInformationJobObject(g_job_handle, JobObjectBasicLimitInformation,
&basicInfo,
sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION))) {
fprintf(stderr,
"Failed to set basic limit information for the job object! Error "
"code: %u\n",
GetLastError());
CloseHandle(g_job_handle);
return EXITCODE_EXEC_FAILURE;
}
}
/* Configure the job object to set a active process limit. */
if (g_target_subprocess_count_limit != ~(rlim_t)0) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_limit_info;
ZeroMemory(&ext_limit_info, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
ext_limit_info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
ext_limit_info.BasicLimitInformation.ActiveProcessLimit =
g_target_subprocess_count_limit;
if (!SetInformationJobObject(
g_job_handle, JobObjectExtendedLimitInformation, &ext_limit_info,
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) {
fprintf(stderr,
"Failed to set the Job Object information. Error code: %lu\n",
GetLastError());
CloseHandle(g_job_handle);
return EXITCODE_EXEC_FAILURE;
}
}
if (g_target_stack_size_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-stack-size unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
if (g_target_data_size_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-data-size unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
if (g_target_rss_size_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-rss-size unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
if (g_target_file_size_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-file-size unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
if (g_target_core_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-core unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
if (g_target_file_count_limit != ~(rlim_t)0) {
fprintf(stderr, "Setting --limit-file-count unsupported on Windows \n");
return EXITCODE_EXEC_FAILURE;
}
/* Honor the desired target execute directory. */
if (g_target_exec_directory) {
if (SetCurrentDirectory(g_target_exec_directory) == 0) {
perror("chdir");
return EXITCODE_EXEC_FAILURE;
}
}
/* Set up the control handler to terminate the child process. */
if (!SetConsoleCtrlHandler(ctrl_handler, TRUE)) {
perror("chdir");
return EXITCODE_EXEC_FAILURE;
}
PROCESS_INFORMATION proc_info = {0};
/* Create the child process. */
if (!CreateProcess(NULL, create_process_commandline(argv), NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, NULL, NULL, &sec_info,
&proc_info)) {
fprintf(stderr, "Failed to create process. Error code: %lu\n",
GetLastError());
return EXITCODE_EXEC_FAILURE;
}
/* Assign the child process to the job object. */
if (!AssignProcessToJobObject(g_job_handle, proc_info.hProcess)) {
fprintf(
stderr,
"Failed to assign child process to the job object. Error code: %lu\n",
GetLastError());
TerminateProcess(proc_info.hProcess, 1);
CloseHandle(proc_info.hProcess);
CloseHandle(proc_info.hThread);
return EXITCODE_EXEC_FAILURE;
}
/* Record the ID and HANDLE of the process we are monitoring. */
g_monitored_pid = proc_info.dwProcessId;
g_monitored_phandle = proc_info.hProcess;
#else
/* Create a new process group for pid, and the process tree it may spawn. We
* do this, because later on we might want to kill pid _and_ all processes
* spawned by it and its descendants.
*/
setpgid(0, 0);
/* Redirect the standard input, if requested. */
if (g_target_redirect_input) {
FILE *fp = fopen(g_target_redirect_input, "r");
if (!fp) {
perror("fopen");
return EXITCODE_MONITORING_FAILURE;
}
int fd = fileno(fp);
if (dup2(fd, 0) < 0) {
perror("dup2");
return EXITCODE_MONITORING_FAILURE;
}
fclose(fp);
}
/* Redirect the standard output, if requested. */
FILE *fp_stdout = NULL;
if (g_target_redirect_stdout) {
fp_stdout = fopen(g_target_redirect_stdout, "w");
if (!fp_stdout) {
perror("fopen");
return EXITCODE_MONITORING_FAILURE;
}
int fd = fileno(fp_stdout);
if (dup2(fd, STDOUT_FILENO) < 0) {
perror("dup2");
return EXITCODE_MONITORING_FAILURE;
}
}
if (g_target_redirect_stderr) {
FILE *fp_stderr = NULL;
int fd;
if (streq(g_target_redirect_stdout, g_target_redirect_stderr))
fd = fileno(fp_stdout);
else {
fp_stderr = fopen(g_target_redirect_stderr, "w");
if (!fp_stderr) {
perror("fopen");
return EXITCODE_MONITORING_FAILURE;
}
fd = fileno(fp_stderr);
}
if (dup2(fd, STDERR_FILENO) < 0) {
perror("dup2");
return EXITCODE_MONITORING_FAILURE;
}
if (fp_stderr != NULL)
fclose(fp_stderr);
}
if (fp_stdout != NULL)
fclose(fp_stdout);
/* Honor any requested resource limits. */
if (g_target_cpu_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_CPU, g_target_cpu_limit);
}
if (g_target_stack_size_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_STACK, g_target_stack_size_limit);
}
if (g_target_data_size_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_DATA, g_target_data_size_limit);
}
#if defined(RLIMIT_RSS) && !defined(__APPLE__)
// On Apple platforms, RLIMIT_RSS is mapped to RLIMIT_AS and setting RLIMIT_AS
// to a value smaller than the current virtual memory size will fail, This is
// incompatible with the current usage in timeit and can cause issues on
// platforms enforcing strict virtual memory size limits. Ignore RLIMIT_RSS on
// Apple platforms for now.
if (g_target_rss_size_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_RSS, g_target_rss_size_limit);
}
#endif
if (g_target_file_size_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_FSIZE, g_target_file_size_limit);
}
if (g_target_core_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_CORE, g_target_core_limit);
}
if (g_target_file_count_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_NOFILE, g_target_file_count_limit);
}
#ifdef RLIMIT_NPROC
if (g_target_subprocess_count_limit != ~(rlim_t) 0) {
set_resource_limit(RLIMIT_NPROC, g_target_subprocess_count_limit);
}
#endif
/* Honor the desired target execute directory. */
if (g_target_exec_directory) {
if (chdir(g_target_exec_directory) < 0) {
perror("chdir");
return EXITCODE_MONITORING_FAILURE;
}
}
execvp(argv[0], argv);
perror("execv");
if (errno == ENOENT) {
return EXITCODE_EXEC_NOENTRY;
} else if (errno == EACCES) {
return EXITCODE_EXEC_NOPERMISSION;
}
return EXITCODE_EXEC_FAILURE;
#endif
}
static int execute(char * const argv[]) {
double start_time;
#ifndef _WIN32
/* Set up signal handlers so we can terminate the monitored process(es) on
* SIGINT or SIGTERM. */
signal(SIGINT, terminate_handler);
signal(SIGTERM, terminate_handler);
/* Set up a signal handler to terminate the process on timeout. */
signal(SIGALRM, timeout_handler);
#endif
start_time = sample_wall_time();
#ifndef _WIN32
pid_t pid;
/* Fork the child process. */
pid = fork();
if (pid < 0) {
perror("fork");
return EXITCODE_MONITORING_FAILURE;
}
/* If we are in the context of the child process, spawn it. */
if (pid == 0) {
/* Setup and execute the target process. This never returns except on
* failure. */
return execute_target_process(argv);
}
/* Record the PID we are monitoring. */
g_monitored_pid = pid;
#else
/* If running on windows, do not return after creating process. */
execute_target_process(argv);
#endif
/* Otherwise, we are in the context of the monitoring process. */
return monitor_child_process(start_time);
}
static void usage(int is_error) {
#define WRAPPED "\n "
fprintf(stderr, "usage: %s [options] command ... arguments ...\n",
g_program_name);
fprintf(stderr, "Options:\n");
fprintf(stderr, " %-20s %s", "-h, --help",
"Show this help text.\n");
fprintf(stderr, " %-20s %s", "-p, --posix",
"Report time in /usr/bin/time POSIX format.\n");
fprintf(stderr, " %-20s %s", "-t, --timeout <N>",
"Execute the subprocess with a timeout of N seconds.\n");
fprintf(stderr, " %-20s %s", "-c, --chdir <PATH>",
"Execute the subprocess in the given working directory.\n");
fprintf(stderr, " %-20s %s", "--summary <PATH>",
"Write monitored process summary (exit code and time) to PATH.\n");
fprintf(stderr, " %-20s %s", "--redirect-output <PATH>",
WRAPPED "Redirect stdout and stderr for the target to PATH.\n");
fprintf(stderr, " %-20s %s", "--redirect-stdout <PATH>",
WRAPPED "Redirect stdout for the target to PATH.\n");
fprintf(stderr, " %-20s %s", "--redirect-stderr <PATH>",
WRAPPED "Redirect stderr for the target to PATH.\n");
fprintf(stderr, " %-20s %s", "--redirect-input <PATH>",
WRAPPED "Redirect stdin for the target to PATH.\n");
fprintf(stderr, " %-20s %s", "--limit-cpu <N>",
WRAPPED "Limit the target to N seconds of CPU time.\n");
fprintf(stderr, " %-20s %s", "--limit-stack-size <N>",
WRAPPED "Limit the target to N bytes of stack space.\n");
fprintf(stderr, " %-20s %s", "--limit-data-size <N>",
WRAPPED "Limit the target to N bytes of data.\n");
fprintf(stderr, " %-20s %s", "--limit-rss-size <N>",
WRAPPED "Limit the target to N bytes of resident memory.\n");
fprintf(stderr, " %-20s %s", "--limit-file-size <N>",
WRAPPED "Limit the target to creating files no more than N bytes.\n");
fprintf(stderr, " %-20s %s", "--limit-core <N>",
WRAPPED "Limit the size for which core files will be generated.\n");
fprintf(stderr, " %-20s %s", "--limit-file-count <N>",
(WRAPPED
"Limit the maximum number of open files the target can have.\n"));
fprintf(stderr, " %-20s %s", "--limit-subprocess-count <N>",
(WRAPPED
"Limit the maximum number of simultaneous processes "
"the target can use.\n"));
_exit(is_error);
}
int main(int argc, char * const argv[]) {
int i;
g_program_name = argv[0];
for (i = 1; i != argc; ++i) {
const char *arg = argv[i];
if (arg[0] != '-')
break;
if (streq(arg, "-h") || streq(arg, "--help")) {
usage(/*is_error=*/0);
}
if (streq(arg, "-p") || streq(arg, "--posix")) {
g_posix_mode = 1;
continue;
}
if (streq(arg, "-t") || streq(arg, "--timeout")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_timeout_in_seconds = atoi(argv[++i]);
continue;
}
if (streq(arg, "--summary")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_summary_file = argv[++i];
continue;
}
if (streq(arg, "--redirect-input")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_target_redirect_input = argv[++i];
continue;
}
if (streq(arg, "--redirect-output")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_target_redirect_stdout = argv[++i];
g_target_redirect_stderr = g_target_redirect_stdout;
continue;
}
if (streq(arg, "--redirect-stdout")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_target_redirect_stdout = argv[++i];
continue;
}
if (streq(arg, "--redirect-stderr")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_target_redirect_stderr = argv[++i];
continue;
}
if (streq(arg, "--append-exitstatus")) {
g_append_exitstats = 1;
continue;
}
if (streq(arg, "-c") || streq(arg, "--chdir")) {
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
g_target_exec_directory = argv[++i];
continue;
}
if (strncmp(arg, "--limit-", 8) == 0) {
rlim_t value;
if (i + 1 == argc) {
fprintf(stderr, "error: %s argument requires an option\n", arg);
usage(/*is_error=*/1);
}
value = atoi(argv[++i]);
if (streq(arg, "--limit-cpu")) {
g_target_cpu_limit = value;
} else if (streq(arg, "--limit-stack-size")) {
g_target_stack_size_limit = value;
} else if (streq(arg, "--limit-data-size")) {
g_target_data_size_limit = value;
} else if (streq(arg, "--limit-rss-size")) {
g_target_rss_size_limit = value;
} else if (streq(arg, "--limit-file-size")) {
g_target_file_size_limit = value;
} else if (streq(arg, "--limit-core")) {
g_target_core_limit = value;
} else if (streq(arg, "--limit-file-count")) {
g_target_file_count_limit = value;
} else if (streq(arg, "--limit-subprocess-count")) {
g_target_subprocess_count_limit = value;
} else {
fprintf(stderr, "error: invalid limit argument '%s'\n", arg);
usage(/*is_error=*/1);
}
continue;
}
fprintf(stderr, "error: invalid argument '%s'\n", arg);
usage(/*is_error=*/1);
}
if (i == argc) {
fprintf(stderr, "error: no command (or arguments) was given\n");
usage(/*is_error=*/1);
}
g_target_program = argv[i];
return execute(&argv[i]);
}