lean4-htt/src/runtime/process.cpp
vvs- b15e770231 fix: crash in IO.Process.wait on 32-bit architecture
The implemetation of unbox_uint32 is the same as unbox on 64-bit
but it is different for 32-bit platforms

Fixes #290
2021-01-24 14:37:23 +01:00

368 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>
#include <system_error>
#if defined(LEAN_WINDOWS)
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#endif
#include <lean/object.h>
#include <lean/io.h>
#include "util/array_ref.h"
#include "util/string_ref.h"
#include "util/option_ref.h"
#include "util/pair_ref.h"
#include "util/buffer.h"
namespace lean {
enum stdio {
PIPED,
INHERIT,
NUL,
};
#if defined(LEAN_WINDOWS)
static lean_external_class * g_win_handle_external_class = nullptr;
static void win_handle_finalizer(void * h) {
lean_always_assert(CloseHandle(static_cast<HANDLE>(h)));
}
static void win_handle_foreach(void * /* mod */, b_obj_arg /* fn */) {
}
lean_object * wrap_win_handle(HANDLE h) {
return lean_alloc_external(g_win_handle_external_class, static_cast<void *>(h));
}
extern "C" obj_res lean_io_process_child_wait(b_obj_arg, b_obj_arg child, obj_arg) {
HANDLE h = static_cast<HANDLE>(lean_get_external_data(cnstr_get(child, 3)));
DWORD exit_code;
WaitForSingleObject(h, INFINITE);
GetExitCodeProcess(h, &exit_code);
return lean_io_result_mk_ok(box_uint32(exit_code));
}
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 void setup_stdio(SECURITY_ATTRIBUTES * saAttr, HANDLE * theirs, object ** ours, bool in, stdio cfg) {
/* Setup stdio based on process configuration. */
switch (cfg) {
case stdio::INHERIT:
lean_always_assert(DuplicateHandle(GetCurrentProcess(), *theirs,
GetCurrentProcess(), theirs,
0, TRUE, DUPLICATE_SAME_ACCESS));
return;
case stdio::PIPED: {
HANDLE readh;
HANDLE writeh;
if (!CreatePipe(&readh, &writeh, saAttr, 0))
throw std::system_error(GetLastError(), std::system_category());
*ours = io_wrap_handle(in ? from_win_handle(writeh, "w") : from_win_handle(readh, "r"));
*theirs = in ? readh : writeh;
// Ensure the write handle to the pipe for STDIN is not inherited.
lean_always_assert(SetHandleInformation(in ? writeh : readh, HANDLE_FLAG_INHERIT, 0));
return;
}
case stdio::NUL:
HANDLE hNul = CreateFile("NUL",
in ? GENERIC_READ : GENERIC_WRITE,
0, // TODO(WN): does NUL have to be shared?
saAttr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hNul == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category());
*theirs = hNul;
return;
}
lean_unreachable();
}
// This code is adapted from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const & args, stdio stdin_mode, stdio stdout_mode,
stdio stderr_mode, option_ref<string_ref> const & cwd, array_ref<pair_ref<string_ref, option_ref<string_ref>>> const & env) {
HANDLE child_stdin = GetStdHandle(STD_INPUT_HANDLE);
HANDLE child_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE child_stderr = GetStdHandle(STD_ERROR_HANDLE);
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe/NUL handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
object * parent_stdin = box(0); setup_stdio(&saAttr, &child_stdin, &parent_stdin, true, stdin_mode);
object * parent_stdout = box(0); setup_stdio(&saAttr, &child_stdout, &parent_stdout, false, stdout_mode);
object * parent_stderr = box(0); setup_stdio(&saAttr, &child_stderr, &parent_stderr, false, stderr_mode);
std::string command = proc_name.to_std_string();
// 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.
for (auto arg : args) {
command += " \"";
for (char const * c = arg.data(); *c != 0; c++) {
if (*c == '"') {
command += '\\';
}
command += *c;
}
command += "\"";
}
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
// 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.hStdInput = child_stdin;
siStartInfo.hStdOutput = child_stdout;
siStartInfo.hStdError = child_stderr;
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.fst().data()))
old = std::string(old_val);
old_env_vars[entry.fst().to_std_string()] = old;
if (entry.snd()) {
SetEnvironmentVariable(entry.fst().data(), entry.snd().get()->data());
} else {
SetEnvironmentVariable(entry.fst().data(), nullptr);
}
}
// Create the child process.
bool 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.get()->data() : NULL, // current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
for (auto & entry : old_env_vars) {
SetEnvironmentVariable(entry.first.c_str(), entry.second ? entry.second->c_str() : nullptr);
}
if (!bSuccess) {
throw std::system_error(GetLastError(), std::system_category());
}
// Close handle to primary thread, we don't need it.
CloseHandle(piProcInfo.hThread);
if (stdin_mode == stdio::PIPED) CloseHandle(child_stdin);
if (stdout_mode == stdio::PIPED) CloseHandle(child_stdout);
if (stderr_mode == stdio::PIPED) CloseHandle(child_stderr);
object_ref r = mk_cnstr(0, parent_stdin, parent_stdout, parent_stderr, wrap_win_handle(piProcInfo.hProcess));
return lean_io_result_mk_ok(r.steal());
}
void initialize_process() {
g_win_handle_external_class = lean_register_external_class(win_handle_finalizer, win_handle_foreach);
}
void finalize_process() {}
#else
extern "C" obj_res lean_io_process_child_wait(b_obj_arg, b_obj_arg child, obj_arg) {
static_assert(sizeof(pid_t) == sizeof(uint32), "pid_t is expected to be a 32-bit type"); // NOLINT
pid_t pid = cnstr_get_uint32(child, 3 * sizeof(object *));
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
return lean_io_result_mk_ok(box_uint32(static_cast<unsigned>(WEXITSTATUS(status))));
} else {
lean_assert(WIFSIGNALED(status));
// use bash's convention
return lean_io_result_mk_ok(box_uint32(128 + static_cast<unsigned>(WTERMSIG(status))));
}
}
struct pipe { int m_read_fd; int m_write_fd; };
static optional<pipe> setup_stdio(stdio cfg) {
/* Setup stdio based on process configuration. */
switch (cfg) {
case stdio::INHERIT:
/* We should need to do nothing in this case */
return optional<pipe>();
case stdio::PIPED:
int fds[2];
if (::pipe(fds) == -1) {
throw errno;
} else {
return optional<pipe>(pipe { fds[0], fds[1] });
}
case stdio::NUL:
/* We should map /dev/null. */
return optional<pipe>();
}
lean_unreachable();
}
static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const & args, stdio stdin_mode, stdio stdout_mode,
stdio stderr_mode, option_ref<string_ref> const & cwd, array_ref<pair_ref<string_ref, option_ref<string_ref>>> const & env) {
/* Setup stdio based on process configuration. */
auto stdin_pipe = setup_stdio(stdin_mode);
auto stdout_pipe = setup_stdio(stdout_mode);
auto stderr_pipe = setup_stdio(stderr_mode);
int pid = fork();
if (pid == 0) {
for (auto & entry : env) {
if (entry.snd()) {
setenv(entry.fst().data(), entry.snd().get()->data(), true);
} else {
unsetenv(entry.fst().data());
}
}
if (stdin_pipe) {
dup2(stdin_pipe->m_read_fd, STDIN_FILENO);
close(stdin_pipe->m_write_fd);
} else if (stdin_mode == stdio::NUL) {
int fd = open("/dev/null", O_RDONLY);
dup2(fd, STDIN_FILENO);
}
if (stdout_pipe) {
dup2(stdout_pipe->m_write_fd, STDOUT_FILENO);
close(stdout_pipe->m_read_fd);
} else if (stdout_mode == stdio::NUL) {
int fd = open("/dev/null", O_WRONLY);
dup2(fd, STDOUT_FILENO);
}
if (stderr_pipe) {
dup2(stderr_pipe->m_write_fd, STDERR_FILENO);
close(stderr_pipe->m_read_fd);
} else if (stderr_mode == stdio::NUL) {
int fd = open("/dev/null", O_WRONLY);
dup2(fd, STDERR_FILENO);
}
if (cwd) {
if (chdir(cwd.get()->data()) < 0) {
std::cerr << "could not change directory to " << cwd.get()->data() << std::endl;
exit(-1);
}
}
buffer<char *> pargs;
pargs.push_back(strdup(proc_name.data()));
for (auto & arg : args)
pargs.push_back(strdup(arg.data()));
pargs.push_back(NULL);
if (execvp(pargs[0], pargs.data()) < 0) {
std::cerr << "could not execute external process '" << pargs[0] << "'" << std::endl;
exit(-1);
}
} else if (pid == -1) {
throw errno;
}
object * parent_stdin = box(0);
object * parent_stdout = box(0);
object * parent_stderr = box(0);
if (stdin_pipe) {
close(stdin_pipe->m_read_fd);
parent_stdin = io_wrap_handle(fdopen(stdin_pipe->m_write_fd, "w"));
}
if (stdout_pipe) {
close(stdout_pipe->m_write_fd);
parent_stdout = io_wrap_handle(fdopen(stdout_pipe->m_read_fd, "r"));
}
if (stderr_pipe) {
close(stderr_pipe->m_write_fd);
parent_stderr = io_wrap_handle(fdopen(stderr_pipe->m_read_fd, "r"));
}
object_ref r = mk_cnstr(0, parent_stdin, parent_stdout, parent_stderr, sizeof(pid_t));
static_assert(sizeof(pid_t) == sizeof(uint32), "pid_t is expected to be a 32-bit type"); // NOLINT
cnstr_set_uint32(r.raw(), 3 * sizeof(object *), pid);
return lean_io_result_mk_ok(r.steal());
}
void initialize_process() {}
void finalize_process() {}
#endif
extern "C" lean_object* lean_mk_io_error_other_error(uint32_t, lean_object*);
extern "C" obj_res lean_io_process_spawn(obj_arg args_, obj_arg) {
object_ref args(args_);
object_ref stdio_cfg = cnstr_get_ref(args, 0);
stdio stdin_mode = static_cast<stdio>(cnstr_get_uint8(stdio_cfg.raw(), 0));
stdio stdout_mode = static_cast<stdio>(cnstr_get_uint8(stdio_cfg.raw(), 1));
stdio stderr_mode = static_cast<stdio>(cnstr_get_uint8(stdio_cfg.raw(), 2));
if (stdin_mode == stdio::INHERIT) {
std::cout.flush();
}
try {
return spawn(
cnstr_get_ref_t<string_ref>(args, 1),
cnstr_get_ref_t<array_ref<string_ref>>(args, 2),
stdin_mode,
stdout_mode,
stderr_mode,
cnstr_get_ref_t<option_ref<string_ref>>(args, 3),
cnstr_get_ref_t<array_ref<pair_ref<string_ref, option_ref<string_ref>>>>(args, 4));
} catch (int err) {
return lean_io_result_mk_error(decode_io_error(err, nullptr));
} catch (std::system_error const & err) {
// TODO: decode
return lean_io_result_mk_error(lean_mk_io_error_other_error(err.code().value(), mk_string(err.code().message())));
}
}
}