lean4-htt/src/library/process.cpp

413 lines
13 KiB
C++

/*
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jared Roesch
*/
#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <utility>
#if defined(LEAN_WINDOWS) && !defined(LEAN_CYGWIN)
#include <windows.h>
#include <Fcntl.h>
#include <io.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#else
#include <sys/wait.h>
#endif
#include "library/process.h"
#include "util/buffer.h"
#include "library/pipe.h"
namespace lean {
process::process(std::string n): m_proc_name(n), m_args() {
m_args.push_back(m_proc_name);
}
process & process::arg(std::string a) {
m_args.push_back(a);
return *this;
}
process & process::set_stdin(stdio cfg) {
m_stdin = optional<stdio>(cfg);
return *this;
}
process & process::set_stdout(stdio cfg) {
m_stdout = optional<stdio>(cfg);
return *this;
}
process & process::set_stderr(stdio cfg) {
m_stderr = optional<stdio>(cfg);
return *this;
}
process & process::set_cwd(std::string const &cwd) {
m_cwd = cwd;
return *this;
}
process & process::set_env(std::string const & var, optional<std::string> const & val) {
m_env[var] = val;
return *this;
}
#if defined(LEAN_WINDOWS) && !defined(LEAN_CYGWIN)
struct windows_child : public child {
handle_ref m_stdin;
handle_ref m_stdout;
handle_ref m_stderr;
HANDLE m_process;
windows_child(HANDLE p, handle_ref hstdin, handle_ref hstdout, handle_ref hstderr) :
m_stdin(hstdin), m_stdout(hstdout), m_stderr(hstderr), m_process(p) {}
~windows_child() {
CloseHandle(m_process);
}
handle_ref get_stdin() override { return m_stdin; }
handle_ref get_stdout() override { return m_stdout; }
handle_ref get_stderr() override { return m_stderr; }
unsigned wait() override {
DWORD exit_code;
WaitForSingleObject(m_process, INFINITE);
GetExitCodeProcess(m_process, &exit_code);
return static_cast<unsigned>(exit_code);
}
};
// static HANDLE to_win_handle(FILE * file) {
// intptr_t handle = _get_osfhandle(fileno(file));
// return reinterpret_cast<HANDLE>(handle);
// }
static FILE * from_win_handle(HANDLE handle, char const * mode) {
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), _O_APPEND);
return fdopen(fd, mode);
}
static HANDLE create_child_process(std::string cmd_name, optional<std::string> const & cwd,
std::unordered_map<std::string, optional<std::string>> const & env,
HANDLE hstdin, HANDLE hstdout, HANDLE hstderr);
// TODO(@jroesch): unify this code between platforms better.
static optional<pipe> setup_stdio(SECURITY_ATTRIBUTES * saAttr, HANDLE * handle, optional<stdio> cfg) {
/* Setup stdio based on process configuration. */
if (cfg) {
switch (*cfg) {
case stdio::INHERIT:
lean_always_assert(DuplicateHandle(GetCurrentProcess(), *handle,
GetCurrentProcess(), handle,
0, TRUE, DUPLICATE_SAME_ACCESS));
return optional<pipe>();
case stdio::PIPED: {
HANDLE readh;
HANDLE writeh;
if (!CreatePipe(&readh, &writeh, saAttr, 0))
throw new exception("unable to create pipe");
auto pipe = lean::pipe(readh, writeh);
auto ours = *handle == GetStdHandle(STD_INPUT_HANDLE) ? pipe.m_write_fd : pipe.m_read_fd;
auto theirs = *handle == GetStdHandle(STD_INPUT_HANDLE) ? pipe.m_read_fd : pipe.m_write_fd;
lean_always_assert(SetHandleInformation(ours, HANDLE_FLAG_INHERIT, 0));
*handle = theirs;
return optional<lean::pipe>(pipe);
}
case stdio::NUL: {
/* We should map /dev/null. */
return optional<pipe>();
}
default:
lean_unreachable();
}
} else {
return optional<pipe>();
}
}
// This code is adapted from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
std::shared_ptr<child> process::spawn_core() {
HANDLE child_stdin = GetStdHandle(STD_INPUT_HANDLE);
HANDLE child_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE child_stderr = GetStdHandle(STD_ERROR_HANDLE);
HANDLE parent_stdin = stdin;
HANDLE parent_stdout = stdout;
HANDLE parent_stderr = stderr;
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
auto stdin_pipe = setup_stdio(&saAttr, &child_stdin, m_stdin);
auto stdout_pipe = setup_stdio(&saAttr, &child_stdout, m_stdout);
auto stderr_pipe = setup_stdio(&saAttr, &child_stderr, m_stderr);
std::string command;
// This needs some thought, on Windows we must pass a command string
// which is a valid command, that is a fully assembled command to be executed.
//
// We must escape the arguments to preseving spacing and other characters,
// we might need to revisit escaping here.
bool once_through = false;
for (auto arg : m_args) {
if (once_through) {
command += " \"";
}
command += arg;
if (once_through) {
command += "\"";
}
once_through = true;
}
// Create the child process.
auto proc_handle =
create_child_process(command, m_cwd, m_env, child_stdin, child_stdout, child_stderr);
if (stdin_pipe) {
CloseHandle(stdin_pipe->m_read_fd);
parent_stdin = stdin_pipe->m_write_fd;
}
if (stdout_pipe) {
CloseHandle(stdout_pipe->m_write_fd);
parent_stdout = stdout_pipe->m_read_fd;
}
if (stderr_pipe) {
CloseHandle(stderr_pipe->m_write_fd);
parent_stderr = stderr_pipe->m_read_fd;
}
return std::make_shared<windows_child>(proc_handle,
std::make_shared<handle>(from_win_handle(parent_stdin, "w")),
std::make_shared<handle>(from_win_handle(parent_stdout, "r")),
std::make_shared<handle>(from_win_handle(parent_stderr, "r")));
}
static void set_env(std::string const & var, optional<std::string> const & val) {
SetEnvironmentVariable(var.c_str(), val ? val->c_str() : NULL);
}
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
static HANDLE create_child_process(std::string command, optional<std::string> const & cwd,
std::unordered_map<std::string, optional<std::string>> const & env,
HANDLE hstdin, HANDLE hstdout, HANDLE hstderr) {
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = hstderr;
siStartInfo.hStdOutput = hstdout;
siStartInfo.hStdInput = hstdin;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// TODO(gabriel): this is thread-unsafe
std::unordered_map<std::string, optional<std::string>> old_env_vars;
for (auto & entry : env) {
optional<std::string> old;
if (auto old_val = getenv(entry.first.c_str()))
old = std::string(old_val);
old_env_vars[entry.first] = old;
set_env(entry.first, entry.second);
}
// Create the child process.
// std::cout << command << std::endl;
bSuccess = CreateProcess(
NULL,
const_cast<char *>(command.c_str()), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
cwd ? cwd->c_str() : NULL, // current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
for (auto & entry : old_env_vars) {
set_env(entry.first, entry.second);
}
// If an error occurs, exit the application.
if (!bSuccess) {
throw exception("failed to start child process");
} else {
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hThread);
return piProcInfo.hProcess;
}
}
#else
static optional<pipe> setup_stdio(optional<stdio> cfg) {
/* Setup stdio based on process configuration. */
if (cfg) {
switch (*cfg) {
/* We should need to do nothing in this case */
case stdio::INHERIT:
return optional<pipe>();
case stdio::PIPED: {
return optional<pipe>(lean::pipe());
}
case stdio::NUL: {
/* We should map /dev/null. */
return optional<pipe>();
}
default:
lean_unreachable();
}
} else {
return optional<pipe>();
}
}
struct unix_child : public child {
handle_ref m_stdin;
handle_ref m_stdout;
handle_ref m_stderr;
int m_pid;
unix_child(int pid, handle_ref hstdin, handle_ref hstdout, handle_ref hstderr) :
m_stdin(hstdin), m_stdout(hstdout), m_stderr(hstderr), m_pid(pid) {}
handle_ref get_stdin() override { return m_stdin; }
handle_ref get_stdout() override { return m_stdout; }
handle_ref get_stderr() override { return m_stderr; }
unsigned wait() override {
int status;
waitpid(m_pid, &status, 0);
if (WIFEXITED(status)) {
return static_cast<unsigned>(WEXITSTATUS(status));
} else {
lean_assert(WIFSIGNALED(status));
// use bash's convention
return 128 + static_cast<unsigned>(WTERMSIG(status));
}
}
};
std::shared_ptr<child> process::spawn_core() {
/* Setup stdio based on process configuration. */
auto stdin_pipe = setup_stdio(m_stdin);
auto stdout_pipe = setup_stdio(m_stdout);
auto stderr_pipe = setup_stdio(m_stderr);
int pid = fork();
if (pid == 0) {
for (auto & entry : m_env) {
if (auto val = entry.second) {
setenv(entry.first.c_str(), val->c_str(), true);
} else {
unsetenv(entry.first.c_str());
}
}
if (stdin_pipe) {
dup2(stdin_pipe->m_read_fd, STDIN_FILENO);
close(stdin_pipe->m_write_fd);
}
if (stdout_pipe) {
dup2(stdout_pipe->m_write_fd, STDOUT_FILENO);
close(stdout_pipe->m_read_fd);
}
if (stderr_pipe) {
dup2(stderr_pipe->m_write_fd, STDERR_FILENO);
close(stderr_pipe->m_read_fd);
}
if (m_cwd) {
if (chdir(m_cwd->c_str()) < 0) {
std::cerr << "could not change directory to " << *m_cwd << std::endl;
exit(-1);
}
}
buffer<char *> pargs;
for (auto & arg : m_args)
pargs.push_back(strdup(arg.c_str()));
pargs.push_back(NULL);
if (execvp(pargs[0], pargs.data()) < 0) {
std::cerr << "could not execute external process" << std::endl;
exit(-1);
}
} else if (pid == -1) {
throw std::runtime_error("forking process failed: ...");
}
/* We want to setup the parent's view of the file descriptors. */
FILE * parent_stdin = nullptr, * parent_stdout = nullptr, * parent_stderr = nullptr;
if (stdin_pipe) {
close(stdin_pipe->m_read_fd);
parent_stdin = fdopen(stdin_pipe->m_write_fd, "w");
}
if (stdout_pipe) {
close(stdout_pipe->m_write_fd);
parent_stdout = fdopen(stdout_pipe->m_read_fd, "r");
}
if (stderr_pipe) {
close(stderr_pipe->m_write_fd);
parent_stderr = fdopen(stderr_pipe->m_read_fd, "r");
}
return std::make_shared<unix_child>(pid,
std::make_shared<handle>(parent_stdin),
std::make_shared<handle>(parent_stdout),
std::make_shared<handle>(parent_stderr));
}
#endif
std::shared_ptr<child> process::spawn() {
if (m_stdout && *m_stdout == stdio::INHERIT) {
std::cout.flush();
}
return spawn_core();
}
void process::run() {
spawn()->wait();
}
}