untested windows code for subprocess handling
This commit is contained in:
parent
6e871c5fbf
commit
4f9007ac57
338
src/process.cc
338
src/process.cc
|
@ -13,8 +13,9 @@
|
|||
#include "ostd/process.hh"
|
||||
|
||||
#ifdef OSTD_PLATFORM_WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include "ostd/filesystem.hh"
|
||||
# include <windows.h>
|
||||
namespace fs = ostd::filesystem;
|
||||
#else
|
||||
# include <unistd.h>
|
||||
# include <fcntl.h>
|
||||
|
@ -314,17 +315,344 @@ struct data {
|
|||
HANDLE process = nullptr, thread = nullptr;
|
||||
};
|
||||
|
||||
OSTD_EXPORT void subprocess::open_impl(
|
||||
std::string const &, std::vector<std::string> const &, bool
|
||||
) {
|
||||
struct pipe {
|
||||
HANDLE p_r = nullptr, p_w = nullptr;
|
||||
|
||||
~pipe() {
|
||||
if (p_r) {
|
||||
CloseHandle(p_r);
|
||||
}
|
||||
if (p_w) {
|
||||
CloseHandle(p_w);
|
||||
}
|
||||
}
|
||||
|
||||
void open(process_stream use, SECURITY_ATTRIBUTES &sa, bool read) {
|
||||
if (use != process_stream::PIPE) {
|
||||
return;
|
||||
}
|
||||
if (!CreatePipe(&p_r, &p_w, &sa, 0)) {
|
||||
throw process_error{EPIPE, std::generic_category()};
|
||||
}
|
||||
if (!SetHandleInformation(read ? p_r : p_w, HANDLE_FLAG_INHERIT, 0)) {
|
||||
throw process_error{EPIPE, std::generic_category()};
|
||||
}
|
||||
}
|
||||
|
||||
void fdopen(file_stream &s, bool read) {
|
||||
int fd = _open_osfhandle(
|
||||
reinterpret_cast<intptr_t>(read ? p_r : p_w),
|
||||
read ? _O_RDONLY : 0
|
||||
);
|
||||
if (fd < 0) {
|
||||
throw process_error{EPIPE, std::generic_category()};
|
||||
}
|
||||
if (read) {
|
||||
p_r = nullptr;
|
||||
} else {
|
||||
p_w = nullptr;
|
||||
}
|
||||
auto p = _fdopen(fd, read ? "r" : "w");
|
||||
if (!p) {
|
||||
_close(fd);
|
||||
throw process_error{EPIPE, std::generic_category()};
|
||||
}
|
||||
s.open(p, [](FILE *f) {
|
||||
std::fclose(f);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* because there is no way to have CreateProcess do a lookup in standard
|
||||
* paths AND specify a custom separate argv[0], we need to implement the
|
||||
* path resolution ourselves; fortunately the standard filesystem API
|
||||
* makes this kind of easy, but it's still a lot of code I'd rather
|
||||
* not write... oh well
|
||||
*/
|
||||
static std::wstring resolve_file(wchar_t const *cmd) {
|
||||
/* a reused buffer, TODO: allow longer paths */
|
||||
wchar_t buf[1024];
|
||||
|
||||
auto is_maybe_exec = [](fs::path const &p) {
|
||||
auto st = fs::status(p);
|
||||
if (fs::is_regular_file(st) || fs::is_symlink(st)) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
fs::path p{cmd};
|
||||
/* deal with some easy cases */
|
||||
if ((p.filename() != p) || (p == L".") || (p == L"..")) {
|
||||
return cmd;
|
||||
}
|
||||
/* no extension appends .exe as is done normally */
|
||||
if (!p.has_extension()) {
|
||||
p.replace_extension(L".exe");
|
||||
}
|
||||
/* the directory from which the app loaded */
|
||||
if (GetModuleFileNameW(nullptr, buf, sizeof(buf))) {
|
||||
#ifdef NTDDI_WIN8
|
||||
PathCchRemoveFileSpecW(buf, sizeof(buf));
|
||||
#else
|
||||
PathRemoveFileSpecW(buf);
|
||||
#endif
|
||||
auto rp = fs::path{buf} / p;
|
||||
if (is_maybe_exec(rp)) {
|
||||
return rp.native();
|
||||
}
|
||||
}
|
||||
/* the current directory */
|
||||
{
|
||||
auto rp = fs::path{L"."} / p;
|
||||
if (is_maybe_exec(rp)) {
|
||||
return rp.native();
|
||||
}
|
||||
}
|
||||
/* the system directory */
|
||||
if (GetSystemDirectoryW(buf, sizeof(buf))) {
|
||||
auto rp = fs::path{buf} / p;
|
||||
if (is_maybe_exec(rp)) {
|
||||
return rp.native();
|
||||
}
|
||||
}
|
||||
/* the windows directory */
|
||||
if (GetWindowsDirectoryW(buf, sizeof(buf))) {
|
||||
auto rp = fs::path{path} / p;
|
||||
if (is_maybe_exec(rp)) {
|
||||
return rp.native();
|
||||
}
|
||||
}
|
||||
/* the PATH envvar */
|
||||
std::size_t req = GetEnvironmentVariableW(L"PATH", buf, sizeof(buf));
|
||||
if (req) {
|
||||
wchar_t *envp = buf;
|
||||
std::vector<wchar_t> dynbuf;
|
||||
if (req > sizeof(buf)) {
|
||||
dynbuf.reserve(req);
|
||||
for (;;) {
|
||||
req = GetEnvironmentVariable(
|
||||
"PATH", dynbuf.data(), dynbuf.capacity()
|
||||
);
|
||||
if (!req) {
|
||||
return cmd;
|
||||
}
|
||||
if (req > dynbuf.capacity()) {
|
||||
dynbuf.reserve(req);
|
||||
} else {
|
||||
envp = dynbuf.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (;;) {
|
||||
auto p = wcschr(envp, L';');
|
||||
fs::path rp;
|
||||
if (!p) {
|
||||
rp = fs::path{p} / p;
|
||||
} else {
|
||||
rp = fs::path{envp, p} / p;
|
||||
envp = p + 1;
|
||||
}
|
||||
if (is_maybe_exec(rp)) {
|
||||
return rp.native();
|
||||
}
|
||||
}
|
||||
}
|
||||
/* nothing found */
|
||||
return cmd;
|
||||
}
|
||||
|
||||
OSTD_EXPORT int subprocess::close() {
|
||||
/* windows follows a dumb set of rules for parsing command line params;
|
||||
* a single \ is normally interpreted literally, unless it precedes a ",
|
||||
* in which case it acts as an escape character for the quotation mark;
|
||||
* if multiple backslashes precedes the quotation mark, each pair is
|
||||
* treated as a single backslash
|
||||
*
|
||||
* we need to replicate this awful behavior here, hence the extra code
|
||||
*/
|
||||
static std::string concat_args(std::vector<std::string> const &args) {
|
||||
std::string ret;
|
||||
for (auto &s: args) {
|
||||
if (!ret.empty()) {
|
||||
ret += ' ';
|
||||
}
|
||||
ret += '\"';
|
||||
for (char const *sp = s.data();;) {
|
||||
char const *p = strpbrk(sp, "\"\\");
|
||||
if (!p) {
|
||||
ret += sp;
|
||||
break;
|
||||
}
|
||||
ret.append(sp, p);
|
||||
if (*p == '\"') {
|
||||
/* not preceded by \, so it's safe */
|
||||
ret += "\\\"";
|
||||
} else {
|
||||
/* handle any sequence of \ optionally followed by a " */
|
||||
std::size_t nbsl = 0;
|
||||
while (*p++ == '\\') {
|
||||
++nbsl;
|
||||
}
|
||||
if (*p == '\"') {
|
||||
/* double all the backslashes plus one for the " */
|
||||
ret.append(nbsl * 2 + 1, '\\');
|
||||
ret += '\"';
|
||||
} else {
|
||||
ret.append(nbsl, '\\');
|
||||
}
|
||||
}
|
||||
sp = p + 1;
|
||||
}
|
||||
ret += '\"';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
OSTD_EXPORT void subprocess::open_impl(
|
||||
std::string const &cmd, std::vector<std::string> const &args, bool use_path
|
||||
) {
|
||||
if (use_in == process_stream::STDOUT) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
|
||||
/* pipes */
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = true;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
pipe pipe_in, pipe_out, pipe_err;
|
||||
|
||||
pipe_in.open(use_in, false);
|
||||
pipe_out.open(use_out, true);
|
||||
pipe_err.open(use_err, true)
|
||||
|
||||
/* process creation */
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFO si;
|
||||
|
||||
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
|
||||
memset(&si, 0, sizeof(STARTUPINFO));
|
||||
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
|
||||
if (use_in == process_stream::PIPE) {
|
||||
si.hStdInput = pipe_in.p_r;
|
||||
pipe_in.fdopen(in, false);
|
||||
} else {
|
||||
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
if (si.hStdInput == INVALID_HANDLE_VALUE) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
}
|
||||
if (use_out == process_stream::PIPE) {
|
||||
si.hStdOutput = pipe_out.p_w;
|
||||
pipe_out.fdopen(out, true);
|
||||
} else {
|
||||
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (si.hStdOutput == INVALID_HANDLE_VALUE) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
}
|
||||
if (use_err == process_stream::PIPE) {
|
||||
si.hStdError = pipe_err.p_w;
|
||||
pipe_err.fdopen(err, true);
|
||||
} else if (use_err = process_stream::STDOUT) {
|
||||
si.hStdError = si.hStdOutput;
|
||||
} else {
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (si.hStdError == INVALID_HANDLE_VALUE) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
}
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
std::wstring cmdpath;
|
||||
/* convert and optionally resolve PATH and other lookup locations */
|
||||
{
|
||||
std::unique_ptr<wchar_t[]> wcmd{new wchar_t[cmd.size() + 1]};
|
||||
if (!MultiByteToWideChar(
|
||||
CP_UTF8, 0, cmd.data(), cmd.size(), wcmd.data(), cmd.size() + 1
|
||||
)) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
if (use_path) {
|
||||
cmdpath = wcmd.get();
|
||||
} else {
|
||||
cmdpath = resolve_file(wcmd.get());
|
||||
}
|
||||
}
|
||||
|
||||
/* cmdline gets an ordinary conversion... */
|
||||
auto astr = concat_args(args);
|
||||
|
||||
std::unique_ptr<wchar_t[]> cmdline{new wchar_t[astr.size() + 1]};
|
||||
if (!MultiByteToWideChar(
|
||||
CP_UTF8, 0, astr.data(), astr.size(), cmdline.data(), astr.size() + 1
|
||||
)) {
|
||||
throw process_error{EINVAL, std::generic_category()};
|
||||
}
|
||||
|
||||
/* owned by CreateProcess, do not close explicitly */
|
||||
pipe_in.p_r = nullptr;
|
||||
pipe_out.p_w = nullptr;
|
||||
pipe_err.p_w = nullptr;
|
||||
|
||||
auto success = CreateProcessW(
|
||||
cmdpath.data(),
|
||||
cmdline.data(),
|
||||
nullptr, /* process security attributes */
|
||||
nullptr, /* primary thread security attributes */
|
||||
true, /* inherit handles */
|
||||
0, /* creation flags */
|
||||
nullptr, /* use parent env */
|
||||
nullptr, /* use parent cwd */
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
p_current = ::new (reinterpret_cast<void *>(&p_data)) data{
|
||||
pi.hProcess, pi.hThread
|
||||
};
|
||||
|
||||
if (!success) {
|
||||
throw process_error{ECHILD, std::generic_category()};
|
||||
}
|
||||
}
|
||||
|
||||
OSTD_EXPORT void subprocess::reset() {
|
||||
p_current = nullptr;
|
||||
}
|
||||
|
||||
OSTD_EXPORT int subprocess::close() {
|
||||
if (!p_current) {
|
||||
throw process_error{ECHILD, std::generic_category()};
|
||||
}
|
||||
|
||||
data *pd = static_cast<data *>(p_current);
|
||||
|
||||
if (WaitForSingleObject(pd->process, INFINITE) == WAIT_FAILED) {
|
||||
CloseHandle(pd->process);
|
||||
CloseHandle(pd->thread);
|
||||
reset();
|
||||
throw process_error{ECHILD, std::generic_category()};
|
||||
}
|
||||
|
||||
int ec;
|
||||
if (!GetExitCodeProcess(pd->process, &ec)) {
|
||||
CloseHandle(pd->process);
|
||||
CloseHandle(pd->thread);
|
||||
reset();
|
||||
throw process_error{ECHILD, std::generic_category()};
|
||||
}
|
||||
|
||||
CloseHandle(pd->process);
|
||||
CloseHandle(pd->thread);
|
||||
reset();
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue