Add an ExecuteNoWait interface to support asynchronous process spawning.

llvm-svn: 75055
This commit is contained in:
David Greene 2009-07-08 21:46:40 +00:00
parent 0b675f5ad5
commit cdde1bb0a6
3 changed files with 274 additions and 0 deletions

View File

@ -87,6 +87,43 @@ namespace sys {
static bool ChangeStdinToBinary();
static bool ChangeStdoutToBinary();
/// @}
/// This function executes the program using the \p arguments provided and
/// waits for the program to exit. This function will block the current
/// program until the invoked program exits. The invoked program will
/// inherit the stdin, stdout, and stderr file descriptors, the
/// environment and other configuration settings of the invoking program.
/// If Path::executable() does not return true when this function is
/// called then a std::string is thrown.
/// @returns an integer result code indicating the status of the program.
/// A zero or positive value indicates the result code of the program. A
/// negative value is the signal number on which it terminated.
/// @see FindProgrambyName
/// @brief Executes the program with the given set of \p args.
static void ExecuteNoWait(
const Path& path, ///< sys::Path object providing the path of the
///< program to be executed. It is presumed this is the result of
///< the FindProgramByName method.
const char** args, ///< A vector of strings that are passed to the
///< program. The first element should be the name of the program.
///< The list *must* be terminated by a null char* entry.
const char ** env = 0, ///< An optional vector of strings to use for
///< the program's environment. If not provided, the current program's
///< environment will be used.
const sys::Path** redirects = 0, ///< An optional array of pointers to
///< Paths. If the array is null, no redirection is done. The array
///< should have a size of at least three. If the pointer in the array
///< are not null, then the inferior process's stdin(0), stdout(1),
///< and stderr(2) will be redirected to the corresponding Paths.
unsigned memoryLimit = 0, ///< If non-zero, this specifies max. amount
///< of memory can be allocated by process. If memory usage will be
///< higher limit, the child is killed and this call returns. If zero -
///< no memory limit.
std::string* ErrMsg = 0 ///< If non-zero, provides a pointer to a string
///< instance in which error messages will be returned. If the string
///< is non-empty upon return an error occurred while invoking the
///< program.
);
};
}
}

View File

@ -274,6 +274,78 @@ Program::ExecuteAndWait(const Path& path,
}
void
Program::ExecuteNoWait(const Path& path,
const char** args,
const char** envp,
const Path** redirects,
unsigned memoryLimit,
std::string* ErrMsg)
{
if (!path.canExecute()) {
if (ErrMsg)
*ErrMsg = path.toString() + " is not executable";
return;
}
// Create a child process.
int child = fork();
switch (child) {
// An error occured: Return to the caller.
case -1:
MakeErrMsg(ErrMsg, "Couldn't fork");
return;
// Child process: Execute the program.
case 0: {
// Redirect file descriptors...
if (redirects) {
// Redirect stdin
if (RedirectIO(redirects[0], 0, ErrMsg)) { return; }
// Redirect stdout
if (RedirectIO(redirects[1], 1, ErrMsg)) { return; }
if (redirects[1] && redirects[2] &&
*(redirects[1]) == *(redirects[2])) {
// If stdout and stderr should go to the same place, redirect stderr
// to the FD already open for stdout.
if (-1 == dup2(1,2)) {
MakeErrMsg(ErrMsg, "Can't redirect stderr to stdout");
return;
}
} else {
// Just redirect stderr
if (RedirectIO(redirects[2], 2, ErrMsg)) { return; }
}
}
// Set memory limits
if (memoryLimit!=0) {
SetMemoryLimits(memoryLimit);
}
// Execute!
if (envp != 0)
execve (path.c_str(), (char**)args, (char**)envp);
else
execv (path.c_str(), (char**)args);
// If the execve() failed, we should exit and let the parent pick up
// our non-zero exit status.
exit (errno);
}
// Parent process: Break out of the switch to do our processing.
default:
break;
}
// Make sure stderr and stdout have been flushed
std::cerr << std::flush;
std::cout << std::flush;
fsync(1);
fsync(2);
}
bool Program::ChangeStdinToBinary(){
// Do nothing, as Unix doesn't differentiate between text and binary.
return false;

View File

@ -303,6 +303,171 @@ Program::ExecuteAndWait(const Path& path,
return status;
}
void
Program::ExecuteNoWait(const Path& path,
const char** args,
const char** envp,
const Path** redirects,
unsigned memoryLimit,
std::string* ErrMsg) {
if (!path.canExecute()) {
if (ErrMsg)
*ErrMsg = "program not executable";
return;
}
// Windows wants a command line, not an array of args, to pass to the new
// process. We have to concatenate them all, while quoting the args that
// have embedded spaces.
// First, determine the length of the command line.
unsigned len = 0;
for (unsigned i = 0; args[i]; i++) {
len += strlen(args[i]) + 1;
if (strchr(args[i], ' '))
len += 2;
}
// Now build the command line.
char *command = reinterpret_cast<char *>(_alloca(len+1));
char *p = command;
for (unsigned i = 0; args[i]; i++) {
const char *arg = args[i];
size_t len = strlen(arg);
bool needsQuoting = strchr(arg, ' ') != 0;
if (needsQuoting)
*p++ = '"';
memcpy(p, arg, len);
p += len;
if (needsQuoting)
*p++ = '"';
*p++ = ' ';
}
*p = 0;
// The pointer to the environment block for the new process.
char *envblock = 0;
if (envp) {
// An environment block consists of a null-terminated block of
// null-terminated strings. Convert the array of environment variables to
// an environment block by concatenating them.
// First, determine the length of the environment block.
len = 0;
for (unsigned i = 0; envp[i]; i++)
len += strlen(envp[i]) + 1;
// Now build the environment block.
envblock = reinterpret_cast<char *>(_alloca(len+1));
p = envblock;
for (unsigned i = 0; envp[i]; i++) {
const char *ev = envp[i];
size_t len = strlen(ev) + 1;
memcpy(p, ev, len);
p += len;
}
*p = 0;
}
// Create a child process.
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = INVALID_HANDLE_VALUE;
si.hStdOutput = INVALID_HANDLE_VALUE;
si.hStdError = INVALID_HANDLE_VALUE;
if (redirects) {
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = RedirectIO(redirects[0], 0, ErrMsg);
if (si.hStdInput == INVALID_HANDLE_VALUE) {
MakeErrMsg(ErrMsg, "can't redirect stdin");
return;
}
si.hStdOutput = RedirectIO(redirects[1], 1, ErrMsg);
if (si.hStdOutput == INVALID_HANDLE_VALUE) {
CloseHandle(si.hStdInput);
MakeErrMsg(ErrMsg, "can't redirect stdout");
return;
}
if (redirects[1] && redirects[2] && *(redirects[1]) == *(redirects[2])) {
// If stdout and stderr should go to the same place, redirect stderr
// to the handle already open for stdout.
DuplicateHandle(GetCurrentProcess(), si.hStdOutput,
GetCurrentProcess(), &si.hStdError,
0, TRUE, DUPLICATE_SAME_ACCESS);
} else {
// Just redirect stderr
si.hStdError = RedirectIO(redirects[2], 2, ErrMsg);
if (si.hStdError == INVALID_HANDLE_VALUE) {
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
MakeErrMsg(ErrMsg, "can't redirect stderr");
return;
}
}
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
fflush(stdout);
fflush(stderr);
BOOL rc = CreateProcess(path.c_str(), command, NULL, NULL, TRUE, 0,
envblock, NULL, &si, &pi);
DWORD err = GetLastError();
// Regardless of whether the process got created or not, we are done with
// the handles we created for it to inherit.
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdError);
// Now return an error if the process didn't get created.
if (!rc)
{
SetLastError(err);
MakeErrMsg(ErrMsg, std::string("Couldn't execute program '") +
path.toString() + "'");
return;
}
// Make sure these get closed no matter what.
AutoHandle hProcess(pi.hProcess);
AutoHandle hThread(pi.hThread);
// Assign the process to a job if a memory limit is defined.
AutoHandle hJob(0);
if (memoryLimit != 0) {
hJob = CreateJobObject(0, 0);
bool success = false;
if (hJob != 0) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;
memset(&jeli, 0, sizeof(jeli));
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
jeli.ProcessMemoryLimit = uintptr_t(memoryLimit) * 1048576;
if (SetInformationJobObject(hJob, JobObjectExtendedLimitInformation,
&jeli, sizeof(jeli))) {
if (AssignProcessToJobObject(hJob, pi.hProcess))
success = true;
}
}
if (!success) {
SetLastError(GetLastError());
MakeErrMsg(ErrMsg, std::string("Unable to set memory limit"));
TerminateProcess(pi.hProcess, 1);
WaitForSingleObject(pi.hProcess, INFINITE);
return;
}
}
}
bool Program::ChangeStdinToBinary(){
int result = _setmode( _fileno(stdin), _O_BINARY );
return result == -1;