From 10caf39244acdd3b82c3ed9c51614e53fc79ccff Mon Sep 17 00:00:00 2001 From: q66 Date: Wed, 10 May 2017 00:28:40 +0200 Subject: [PATCH] split win32 and posix code of context_stack and process into separate files --- ostd/process.hh | 10 +- src/context_stack.cc | 180 +--------- src/posix/context_stack.cc | 133 ++++++++ src/posix/process.cc | 279 +++++++++++++++ src/process.cc | 677 +------------------------------------ src/win32/context_stack.cc | 89 +++++ src/win32/process.cc | 451 ++++++++++++++++++++++++ 7 files changed, 972 insertions(+), 847 deletions(-) create mode 100644 src/posix/context_stack.cc create mode 100644 src/posix/process.cc create mode 100644 src/win32/context_stack.cc create mode 100644 src/win32/process.cc diff --git a/ostd/process.hh b/ostd/process.hh index fdd06a8..c8aa4dd 100644 --- a/ostd/process.hh +++ b/ostd/process.hh @@ -227,7 +227,15 @@ struct OSTD_EXPORT subprocess { * If a child process assigned to this structure currently * exists, it will wait for it to finish first by calling close(). */ - ~subprocess(); + ~subprocess() { + if (!p_current) { + return; + } + try { + close(); + } catch (process_error const &) {} + reset(); + } /** @brief Waits for a currently running child process to be done. * diff --git a/src/context_stack.cc b/src/context_stack.cc index 4a76408..9cbb3e4 100644 --- a/src/context_stack.cc +++ b/src/context_stack.cc @@ -1,186 +1,16 @@ -/* Stack allocation implementation for coroutine contexts. +/* Decides between POSIX and Windows for context_stack. * * This file is part of libostd. See COPYING.md for futher information. */ -#include -#include -#include -#include - #include "ostd/platform.hh" -#include "ostd/context_stack.hh" -#ifdef OSTD_PLATFORM_POSIX -# include -# include -# include -# include -# include +#ifdef OSTD_PLATFORM_WIN32 +# include "src/win32/context_stack.cc" #else -# define WIN32_LEAN_AND_MEAN -# include -#endif - -namespace ostd { - -namespace detail { #ifdef OSTD_PLATFORM_POSIX -# if defined(MAP_ANON) || defined(MAP_ANONYMOUS) - constexpr bool CONTEXT_USE_MMAP = true; -# ifdef MAP_ANON - constexpr auto CONTEXT_MAP_ANON = MAP_ANON; -# else - constexpr auto CONTEXT_MAP_ANON = MAP_ANONYMOUS; -# endif -# else - constexpr bool CONTEXT_USE_MMAP = false; -# endif -#endif - - OSTD_EXPORT void *stack_alloc(std::size_t sz) { -#if defined(OSTD_PLATFORM_WIN32) - void *p = VirtualAlloc(0, sz, MEM_COMMIT, PAGE_READWRITE); - if (!p) { - throw std::bad_alloc{}; - } - return p; -#elif defined(OSTD_PLATFORM_POSIX) - if constexpr(CONTEXT_USE_MMAP) { - void *p = mmap( - 0, sz, PROT_READ | PROT_WRITE, - MAP_PRIVATE | CONTEXT_MAP_ANON, -1, 0 - ); - if (p == MAP_FAILED) { - throw std::bad_alloc{}; - } - return p; - } - void *p = std::malloc(sz); - if (!p) { - throw std::bad_alloc{}; - } - return p; -#endif - } - - OSTD_EXPORT void stack_free(void *p, std::size_t sz) noexcept { -#if defined(OSTD_PLATFORM_WIN32) - (void)sz; - VirtualFree(p, 0, MEM_RELEASE); -#elif defined(OSTD_PLATFORM_POSIX) - if constexpr(CONTEXT_USE_MMAP) { - munmap(p, sz); - } else { - std::free(p); - } -#endif - } - - OSTD_EXPORT std::size_t stack_main_size() noexcept { -#if defined(OSTD_PLATFORM_WIN32) - /* 4 MiB for windows... */ - return 4 * 1024 * 1024; +# include "src/posix/context_stack.cc" #else - struct rlimit l; - getrlimit(RLIMIT_STACK, &l); - return std::size_t(l.rlim_cur); +# error "Unsupported platform" #endif - } - - OSTD_EXPORT void stack_protect(void *p, std::size_t sz) noexcept { -#if defined(OSTD_PLATFORM_WIN32) - DWORD oo; - VirtualProtect(p, sz, PAGE_READWRITE | PAGE_GUARD, &oo); -#elif defined(OSTD_PLATFORM_POSIX) - mprotect(p, sz, PROT_NONE); #endif - } - - /* used by stack traits */ - inline void ctx_pagesize(std::size_t *s) noexcept { -#if defined(OSTD_PLATFORM_WIN32) - SYSTEM_INFO si; - GetSystemInfo(&si); - *s = std::size_t(si.dwPageSize); -#elif defined(OSTD_PLATFORM_POSIX) - *s = std::size_t(sysconf(_SC_PAGESIZE)); -#endif - } - -#ifdef OSTD_PLATFORM_POSIX - inline void ctx_rlimit_get(rlimit *l) noexcept { - getrlimit(RLIMIT_STACK, l); - } - - inline rlimit ctx_rlimit() noexcept { - static rlimit l; - static std::once_flag fl; - std::call_once(fl, ctx_rlimit_get, &l); - return l; - } -#endif -} /* namespace detail */ - -OSTD_EXPORT bool stack_traits::is_unbounded() noexcept { -#if defined(OSTD_PLATFORM_WIN32) - return true; -#elif defined(OSTD_PLATFORM_POSIX) - return detail::ctx_rlimit().rlim_max == RLIM_INFINITY; -#endif -} - -OSTD_EXPORT std::size_t stack_traits::page_size() noexcept { - static std::size_t size = 0; - static std::once_flag fl; - std::call_once(fl, detail::ctx_pagesize, &size); - return size; -} - -OSTD_EXPORT std::size_t stack_traits::minimum_size() noexcept { -#if defined(OSTD_PLATFORM_WIN32) - /* no func on windows, sane default of 8 KiB */ - return 8 * 1024; -#elif defined(OSTD_PLATFORM_POSIX) - /* typically 8 KiB but can be much larger on some platforms */ - return SIGSTKSZ; -#endif -} - -OSTD_EXPORT std::size_t stack_traits::maximum_size() noexcept { -#if defined(OSTD_PLATFORM_WIN32) - /* value is technically undefined when is_unbounded() is - * true, just default to 1 GiB so we actually return something - */ - return 1024 * 1024 * 1024; -#elif defined(OSTD_PLATFORM_POSIX) - /* can be RLIM_INFINITY, but that's ok, see above */ - return std::size_t(detail::ctx_rlimit().rlim_max); -#endif -} - -OSTD_EXPORT std::size_t stack_traits::default_size() noexcept { -#if defined(OSTD_PLATFORM_WIN32) - /* no func on windows either, default to 64 KiB */ - return 8 * 8 * 1024; -#elif defined(OSTD_PLATFORM_POSIX) - /* default to at least 64 KiB (see minimum_size comment) */ - constexpr std::size_t r = std::max(8 * 8 * 1024, SIGSTKSZ); - if (is_unbounded()) { - return r; - } - std::size_t m = maximum_size(); - if (r > m) { - return m; - } - return r; -#endif -} - -struct coroutine_context; - -namespace detail { - OSTD_EXPORT thread_local coroutine_context *coro_current = nullptr; -} - -} /* namespace ostd */ diff --git a/src/posix/context_stack.cc b/src/posix/context_stack.cc new file mode 100644 index 0000000..8cd59b2 --- /dev/null +++ b/src/posix/context_stack.cc @@ -0,0 +1,133 @@ +/* Stack allocation implementation for coroutine contexts. + * For POSIX systems only, other implementations are stored elsewhere. + * + * This file is part of libostd. See COPYING.md for futher information. + */ + +#include "ostd/platform.hh" + +#ifndef OSTD_PLATFORM_POSIX +# error "Incorrect platform" +#endif + +#include +#include +#include +#include + +#include "ostd/context_stack.hh" + +#include +#include +#include +#include +#include + +namespace ostd { + +namespace detail { +#if defined(MAP_ANON) || defined(MAP_ANONYMOUS) + constexpr bool CONTEXT_USE_MMAP = true; +# ifdef MAP_ANON + constexpr auto CONTEXT_MAP_ANON = MAP_ANON; +# else + constexpr auto CONTEXT_MAP_ANON = MAP_ANONYMOUS; +# endif +#else + constexpr bool CONTEXT_USE_MMAP = false; +#endif + + OSTD_EXPORT void *stack_alloc(std::size_t sz) { + if constexpr(CONTEXT_USE_MMAP) { + void *p = mmap( + 0, sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | CONTEXT_MAP_ANON, -1, 0 + ); + if (p == MAP_FAILED) { + throw std::bad_alloc{}; + } + return p; + } + void *p = std::malloc(sz); + if (!p) { + throw std::bad_alloc{}; + } + return p; + } + + OSTD_EXPORT void stack_free(void *p, std::size_t sz) noexcept { + if constexpr(CONTEXT_USE_MMAP) { + munmap(p, sz); + } else { + std::free(p); + } + } + + OSTD_EXPORT std::size_t stack_main_size() noexcept { + struct rlimit l; + getrlimit(RLIMIT_STACK, &l); + return std::size_t(l.rlim_cur); + } + + OSTD_EXPORT void stack_protect(void *p, std::size_t sz) noexcept { + mprotect(p, sz, PROT_NONE); + } + + /* used by stack traits */ + inline void ctx_pagesize(std::size_t *s) noexcept { + *s = std::size_t(sysconf(_SC_PAGESIZE)); + } + + inline void ctx_rlimit_get(rlimit *l) noexcept { + getrlimit(RLIMIT_STACK, l); + } + + inline rlimit ctx_rlimit() noexcept { + static rlimit l; + static std::once_flag fl; + std::call_once(fl, ctx_rlimit_get, &l); + return l; + } +} /* namespace detail */ + +OSTD_EXPORT bool stack_traits::is_unbounded() noexcept { + return detail::ctx_rlimit().rlim_max == RLIM_INFINITY; +} + +OSTD_EXPORT std::size_t stack_traits::page_size() noexcept { + static std::size_t size = 0; + static std::once_flag fl; + std::call_once(fl, detail::ctx_pagesize, &size); + return size; +} + +OSTD_EXPORT std::size_t stack_traits::minimum_size() noexcept { + /* typically 8 KiB but can be much larger on some platforms */ + return SIGSTKSZ; +} + +OSTD_EXPORT std::size_t stack_traits::maximum_size() noexcept { + /* can be RLIM_INFINITY, but that's ok, see above */ + return std::size_t(detail::ctx_rlimit().rlim_max); +} + +OSTD_EXPORT std::size_t stack_traits::default_size() noexcept { + /* default to at least 64 KiB (see minimum_size comment) */ + constexpr std::size_t r = std::max(8 * 8 * 1024, SIGSTKSZ); + if (is_unbounded()) { + return r; + } + std::size_t m = maximum_size(); + if (r > m) { + return m; + } + return r; +} + +struct coroutine_context; + +namespace detail { + OSTD_EXPORT thread_local coroutine_context *coro_current = nullptr; +} + +} /* namespace ostd */ diff --git a/src/posix/process.cc b/src/posix/process.cc new file mode 100644 index 0000000..ebffc2f --- /dev/null +++ b/src/posix/process.cc @@ -0,0 +1,279 @@ +/* Process handling implementation bits. + * For POSIX systems only, other implementations are stored elsewhere. + * + * This file is part of libostd. See COPYING.md for futher information. + */ + +#include "ostd/platform.hh" + +#ifndef OSTD_PLATFORM_POSIX +# error "Incorrect platform" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ostd/process.hh" +#include "ostd/format.hh" + +namespace ostd { +namespace detail { + +OSTD_EXPORT void split_args_impl( + string_range const &str, void (*func)(string_range, void *), void *data +) { + if (!str.size()) { + return; + } + std::string strs{str}; + + wordexp_t p; + if (int err; (err = wordexp(strs.data(), &p, 0))) { + switch (err) { + case WRDE_BADCHAR: + throw word_error{"illegal character"}; + case WRDE_BADVAL: + /* no WRDE_UNDEF flag, won't happen */ + throw word_error{"undefined shell variable"}; + case WRDE_CMDSUB: + /* no WRDE_NOCMD flag, won't happen */ + throw word_error{"invalid command substitution"}; + case WRDE_NOSPACE: + throw std::bad_alloc{}; + case WRDE_SYNTAX: + throw word_error{"syntax error"}; + default: + throw word_error{"unknown error"}; + } + } + + /* if the output range throws, make sure stuff gets freed */ + struct wordexp_guard { + void operator()(wordexp_t *fp) const { + wordfree(fp); + } + }; + std::unique_ptr guard{&p}; + + for (std::size_t i = 0; i < p.we_wordc; ++i) { + func(p.we_wordv[i], data); + } +} + +} /* namespace detail */ + +struct data { + int pid = -1, errno_fd = -1; +}; + +struct pipe { + int fd[2] = { -1, -1 }; + + ~pipe() { + if (fd[0] >= 0) { + ::close(fd[0]); + } + if (fd[1] >= 0) { + ::close(fd[1]); + } + } + + void open(process_stream use) { + if (use != process_stream::PIPE) { + return; + } + if (::pipe(fd) < 0) { + throw process_error{"could not open pipe"}; + } + } + + int &operator[](std::size_t idx) { + return fd[idx]; + } + + void fdopen(file_stream &s, bool write) { + FILE *p = ::fdopen(fd[std::size_t(write)], write ? "w" : "r"); + if (!p) { + throw process_error{"could not open redirected stream"}; + } + /* do not close twice, the stream will close it */ + fd[std::size_t(write)] = -1; + s.open(p, [](FILE *f) { + std::fclose(f); + }); + } + + void close(bool write) { + ::close(std::exchange(fd[std::size_t(write)], -1)); + } + + bool dup2(int target, pipe &err, bool write) { + if (::dup2(fd[std::size_t(write)], target) < 0) { + err.write_errno(); + return false; + } + close(write); + return true; + } + + void write_errno() { + write(fd[1], int(errno), sizeof(int)); + } +}; + +OSTD_EXPORT void subprocess::open_impl( + std::string const &cmd, std::vector const &args, + bool use_path +) { + if (use_in == process_stream::STDOUT) { + throw process_error{"could not redirect stdin to stdout"}; + } + + auto argp = std::make_unique(args.size() + 1); + for (std::size_t i = 0; i < args.size(); ++i) { + argp[i] = const_cast(args[i].data()); + } + argp[args.size()] = nullptr; + + /* fd_errno used to detect if exec failed */ + pipe fd_errno, fd_stdin, fd_stdout, fd_stderr; + + fd_errno.open(process_stream::PIPE); + fd_stdin.open(use_in); + fd_stdout.open(use_out); + fd_stderr.open(use_err); + + auto cpid = fork(); + if (cpid == -1) { + throw process_error{"fork failed"}; + } else if (!cpid) { + /* child process */ + fd_errno.close(false); + /* fcntl fails, write the errno to be read from parent */ + if (fcntl(fd_errno[1], F_SETFD, FD_CLOEXEC) < 0) { + fd_errno.write_errno(); + std::exit(1); + } + /* prepare standard streams */ + if (use_in == process_stream::PIPE) { + fd_stdin.close(true); + if (!fd_stdin.dup2(STDIN_FILENO, fd_errno, false)) { + std::exit(1); + } + } + if (use_out == process_stream::PIPE) { + fd_stdout.close(false); + if (!fd_stdout.dup2(STDOUT_FILENO, fd_errno, true)) { + std::exit(1); + } + } + if (use_err == process_stream::PIPE) { + fd_stderr.close(false); + if (!fd_stderr.dup2(STDERR_FILENO, fd_errno, true)) { + std::exit(1); + } + } else if (use_err == process_stream::STDOUT) { + if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) { + fd_errno.write_errno(); + std::exit(1); + } + } + if (use_path) { + execvp(cmd.data(), argp.get()); + } else { + execv(cmd.data(), argp.get()); + } + /* exec has returned, so error has occured */ + fd_errno.write_errno(); + std::exit(1); + } else { + /* parent process */ + fd_errno.close(true); + if (use_in == process_stream::PIPE) { + fd_stdin.close(false); + fd_stdin.fdopen(in, true); + } + if (use_out == process_stream::PIPE) { + fd_stdout.close(true); + fd_stdout.fdopen(out, false); + } + if (use_err == process_stream::PIPE) { + fd_stderr.close(true); + fd_stderr.fdopen(err, false); + } + p_current = ::new (reinterpret_cast(&p_data)) data{ + int(cpid), std::exchange(fd_errno[1], -1) + }; + } +} + +OSTD_EXPORT void subprocess::reset() { + if (!p_current) { + return; + } + data *pd = static_cast(p_current); + if (pd->errno_fd >= 0) { + ::close(pd->errno_fd); + } + p_current = nullptr; +} + +OSTD_EXPORT int subprocess::close() { + if (!p_current) { + throw process_error{"no child process"}; + } + data *pd = static_cast(p_current); + int retc = 0; + if (pid_t wp; (wp = waitpid(pd->pid, &retc, 0)) < 0) { + reset(); + throw process_error{"child process wait failed"}; + } + if (retc) { + int eno; + auto r = read(pd->errno_fd, &eno, sizeof(int)); + reset(); + if (r < 0) { + throw process_error{"could not read from pipe"}; + } else if (r == sizeof(int)) { + auto ec = std::system_category().default_error_condition(eno); + auto app = appender(); + format(app, "could not execute subprocess (%s)", ec.message()); + throw process_error{std::move(app.get())}; + } + } + reset(); + return retc; +} + +OSTD_EXPORT void subprocess::move_data(subprocess &i) { + data *od = static_cast(i.p_current); + if (!od) { + return; + } + p_current = ::new (reinterpret_cast(&p_data)) data{*od}; + i.p_current = nullptr; +} + +OSTD_EXPORT void subprocess::swap_data(subprocess &i) { + if (!p_current) { + move_data(i); + } else if (!i.p_current) { + i.move_data(*this); + } else { + std::swap( + *static_cast(p_current), *static_cast(i.p_current) + ); + } +} + +} /* namespace ostd */ diff --git a/src/process.cc b/src/process.cc index 8657229..d89701a 100644 --- a/src/process.cc +++ b/src/process.cc @@ -1,681 +1,16 @@ -/* Process handling implementation bits. +/* Decides between POSIX and Windows for process. * * This file is part of libostd. See COPYING.md for futher information. */ -#include -#include -#include -#include -#include -#include -#include - -#include "ostd/process.hh" -#include "ostd/format.hh" - -#include +#include "ostd/platform.hh" #ifdef OSTD_PLATFORM_WIN32 -# include "ostd/filesystem.hh" -# include -namespace fs = ostd::filesystem; +# include "src/win32/process.cc" #else -# include -# include -# include -#endif - -namespace ostd { -namespace detail { - -OSTD_EXPORT void split_args_impl( - string_range const &str, void (*func)(string_range, void *), void *data -) { - if (!str.size()) { - return; - } - -#ifndef OSTD_PLATFORM_WIN32 - std::string strs{str}; - - wordexp_t p; - if (int err; (err = wordexp(strs.data(), &p, 0))) { - switch (err) { - case WRDE_BADCHAR: - throw word_error{"illegal character"}; - case WRDE_BADVAL: - /* no WRDE_UNDEF flag, won't happen */ - throw word_error{"undefined shell variable"}; - case WRDE_CMDSUB: - /* no WRDE_NOCMD flag, won't happen */ - throw word_error{"invalid command substitution"}; - case WRDE_NOSPACE: - throw std::bad_alloc{}; - case WRDE_SYNTAX: - throw word_error{"syntax error"}; - default: - throw word_error{"unknown error"}; - } - } - - /* if the output range throws, make sure stuff gets freed */ - struct wordexp_guard { - void operator()(wordexp_t *fp) const { - wordfree(fp); - } - }; - std::unique_ptr guard{&p}; - - for (std::size_t i = 0; i < p.we_wordc; ++i) { - func(p.we_wordv[i], data); - } +#ifdef OSTD_PLATFORM_POSIX +# include "src/posix/process.cc" #else - std::unique_ptr wstr{new wchar_t[str.size() + 1]}; - memset(wstr.get(), 0, (str.size() + 1) * sizeof(wchar_t)); - - if (!MultiByteToWideChar( - CP_UTF8, 0, str.data(), str.size(), wstr.get(), str.size() + 1 - )) { - throw word_error{"unicode conversion failed"}; - } - - int argc = 0; - wchar_t **pwargs = CommandLineToArgvW(wstr.get(), &argc); - - if (!pwargs) { - throw word_error{"command line parsing failed"}; - } - - /* if anything throws, make sure stuff gets freed */ - struct wchar_guard { - void operator()(wchar_t **p) const { - LocalFree(p); - } - }; - std::unique_ptr wguard{pwargs}; - - for (int i = 0; i < argc; ++i) { - wchar_t *arg = pwargs[i]; - std::size_t arglen = wcslen(arg); - - std::size_t req = 0; - if (!(req = WideCharToMultiByte( - CP_UTF8, 0, arg, arglen + 1, nullptr, 0, nullptr, nullptr - ))) { - throw word_error{"unicode conversion failed"}; - } - - std::unique_ptr buf{new char[req]}; - if (!WideCharToMultiByte( - CP_UTF8, 0, arg, arglen + 1, buf.get(), req, nullptr, nullptr - )) { - throw word_error{"unicode conversion failed"}; - } - - func(string_range{buf.get(), buf.get() + req - 1}, data); - } +# error "Unsupported platform" #endif -} - -} /* namespace detail */ - -#ifndef OSTD_PLATFORM_WIN32 -struct data { - int pid = -1, errno_fd = -1; -}; - -struct pipe { - int fd[2] = { -1, -1 }; - - ~pipe() { - if (fd[0] >= 0) { - ::close(fd[0]); - } - if (fd[1] >= 0) { - ::close(fd[1]); - } - } - - void open(process_stream use) { - if (use != process_stream::PIPE) { - return; - } - if (::pipe(fd) < 0) { - throw process_error{"could not open pipe"}; - } - } - - int &operator[](std::size_t idx) { - return fd[idx]; - } - - void fdopen(file_stream &s, bool write) { - FILE *p = ::fdopen(fd[std::size_t(write)], write ? "w" : "r"); - if (!p) { - throw process_error{"could not open redirected stream"}; - } - /* do not close twice, the stream will close it */ - fd[std::size_t(write)] = -1; - s.open(p, [](FILE *f) { - std::fclose(f); - }); - } - - void close(bool write) { - ::close(std::exchange(fd[std::size_t(write)], -1)); - } - - bool dup2(int target, pipe &err, bool write) { - if (::dup2(fd[std::size_t(write)], target) < 0) { - err.write_errno(); - return false; - } - close(write); - return true; - } - - void write_errno() { - write(fd[1], int(errno), sizeof(int)); - } -}; - -OSTD_EXPORT void subprocess::open_impl( - std::string const &cmd, std::vector const &args, - bool use_path -) { - if (use_in == process_stream::STDOUT) { - throw process_error{"could not redirect stdin to stdout"}; - } - - auto argp = std::make_unique(args.size() + 1); - for (std::size_t i = 0; i < args.size(); ++i) { - argp[i] = const_cast(args[i].data()); - } - argp[args.size()] = nullptr; - - /* fd_errno used to detect if exec failed */ - pipe fd_errno, fd_stdin, fd_stdout, fd_stderr; - - fd_errno.open(process_stream::PIPE); - fd_stdin.open(use_in); - fd_stdout.open(use_out); - fd_stderr.open(use_err); - - auto cpid = fork(); - if (cpid == -1) { - throw process_error{"fork failed"}; - } else if (!cpid) { - /* child process */ - fd_errno.close(false); - /* fcntl fails, write the errno to be read from parent */ - if (fcntl(fd_errno[1], F_SETFD, FD_CLOEXEC) < 0) { - fd_errno.write_errno(); - std::exit(1); - } - /* prepare standard streams */ - if (use_in == process_stream::PIPE) { - fd_stdin.close(true); - if (!fd_stdin.dup2(STDIN_FILENO, fd_errno, false)) { - std::exit(1); - } - } - if (use_out == process_stream::PIPE) { - fd_stdout.close(false); - if (!fd_stdout.dup2(STDOUT_FILENO, fd_errno, true)) { - std::exit(1); - } - } - if (use_err == process_stream::PIPE) { - fd_stderr.close(false); - if (!fd_stderr.dup2(STDERR_FILENO, fd_errno, true)) { - std::exit(1); - } - } else if (use_err == process_stream::STDOUT) { - if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) { - fd_errno.write_errno(); - std::exit(1); - } - } - if (use_path) { - execvp(cmd.data(), argp.get()); - } else { - execv(cmd.data(), argp.get()); - } - /* exec has returned, so error has occured */ - fd_errno.write_errno(); - std::exit(1); - } else { - /* parent process */ - fd_errno.close(true); - if (use_in == process_stream::PIPE) { - fd_stdin.close(false); - fd_stdin.fdopen(in, true); - } - if (use_out == process_stream::PIPE) { - fd_stdout.close(true); - fd_stdout.fdopen(out, false); - } - if (use_err == process_stream::PIPE) { - fd_stderr.close(true); - fd_stderr.fdopen(err, false); - } - p_current = ::new (reinterpret_cast(&p_data)) data{ - int(cpid), std::exchange(fd_errno[1], -1) - }; - } -} - -OSTD_EXPORT void subprocess::reset() { - if (!p_current) { - return; - } - data *pd = static_cast(p_current); - if (pd->errno_fd >= 0) { - ::close(pd->errno_fd); - } - p_current = nullptr; -} - -OSTD_EXPORT int subprocess::close() { - if (!p_current) { - throw process_error{"no child process"}; - } - data *pd = static_cast(p_current); - int retc = 0; - if (pid_t wp; (wp = waitpid(pd->pid, &retc, 0)) < 0) { - reset(); - throw process_error{"child process wait failed"}; - } - if (retc) { - int eno; - auto r = read(pd->errno_fd, &eno, sizeof(int)); - reset(); - if (r < 0) { - throw process_error{"could not read from pipe"}; - } else if (r == sizeof(int)) { - auto ec = std::system_category().default_error_condition(eno); - auto app = appender(); - format(app, "could not execute subprocess (%s)", ec.message()); - throw process_error{std::move(app.get())}; - } - } - reset(); - return retc; -} - -#else /* OSTD_PLATFORM_WIN32 */ - -struct data { - HANDLE process = nullptr, thread = nullptr; -}; - -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{"could not open pipe"}; - } - if (!SetHandleInformation(read ? p_r : p_w, HANDLE_FLAG_INHERIT, 0)) { - throw process_error{"could not set pipe parameters"}; - } - } - - void fdopen(file_stream &s, bool read) { - int fd = _open_osfhandle( - reinterpret_cast(read ? p_r : p_w), - read ? _O_RDONLY : 0 - ); - if (fd < 0) { - throw process_error{"could not open redirected stream"}; - } - if (read) { - p_r = nullptr; - } else { - p_w = nullptr; - } - auto p = _fdopen(fd, read ? "r" : "w"); - if (!p) { - _close(fd); - throw process_error{"could not open redirected stream"}; - } - 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); - return (fs::is_regular_file(st) || fs::is_symlink(st)); - }; - - 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))) { - fs::path rp{buf}; - rp.replace_filename(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{buf} / 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 dynbuf; - if (req > sizeof(buf)) { - dynbuf.reserve(req); - for (;;) { - req = GetEnvironmentVariableW( - L"PATH", dynbuf.data(), dynbuf.capacity() - ); - if (!req) { - return cmd; - } - if (req > dynbuf.capacity()) { - dynbuf.reserve(req); - } else { - envp = dynbuf.data(); - break; - } - } - } - for (;;) { - auto sp = wcschr(envp, L';'); - fs::path rp; - if (!sp) { - rp = fs::path{envp} / p; - } else if (sp == envp) { - envp = sp + 1; - continue; - } else { - rp = fs::path{envp, sp} / p; - envp = sp + 1; - } - if (is_maybe_exec(rp)) { - return rp.native(); - } - } - } - /* nothing found */ - return cmd; -} - -/* 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 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 += "\\\""; - ++p; - } else { - /* handle any sequence of \ optionally followed by a " */ - char const *op = p; - while (*p == '\\') { - ++p; - } - if (*p == '\"') { - /* double all the backslashes plus one for the " */ - ret.append((p - op) * 2 + 1, '\\'); - ret += '\"'; - } else { - ret.append(p - op, '\\'); - } - } - sp = p; - } - ret += '\"'; - } - return ret; -} - -OSTD_EXPORT void subprocess::open_impl( - std::string const &cmd, std::vector const &args, bool use_path -) { - if (use_in == process_stream::STDOUT) { - throw process_error{"could not redirect stdin to stdout"}; - } - - /* 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, sa, false); - pipe_out.open(use_out, sa, true); - pipe_err.open(use_err, sa, true); - - /* process creation */ - - PROCESS_INFORMATION pi; - STARTUPINFOW si; - - memset(&pi, 0, sizeof(PROCESS_INFORMATION)); - memset(&si, 0, sizeof(STARTUPINFOW)); - - si.cb = sizeof(STARTUPINFOW); - - 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{"could not get standard input handle"}; - } - } - 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{"could not get standard output handle"}; - } - } - 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{"could not get standard error handle"}; - } - } - si.dwFlags |= STARTF_USESTDHANDLES; - - std::wstring cmdpath; - /* convert and optionally resolve PATH and other lookup locations */ - { - std::unique_ptr wcmd{new wchar_t[cmd.size() + 1]}; - if (!MultiByteToWideChar( - CP_UTF8, 0, cmd.data(), cmd.size() + 1, wcmd.get(), cmd.size() + 1 - )) { - throw process_error{"unicode conversion failed"}; - } - 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 cmdline{new wchar_t[astr.size() + 1]}; - if (!MultiByteToWideChar( - CP_UTF8, 0, astr.data(), astr.size() + 1, cmdline.get(), astr.size() + 1 - )) { - throw process_error{"unicode conversion failed"}; - } - - /* 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.get(), - 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(&p_data)) data{ - pi.hProcess, pi.hThread - }; - - if (!success) { - throw process_error{"could not execute subprocess"}; - } -} - -OSTD_EXPORT void subprocess::reset() { - p_current = nullptr; -} - -OSTD_EXPORT int subprocess::close() { - if (!p_current) { - throw process_error{"no child process"}; - } - - data *pd = static_cast(p_current); - - if (WaitForSingleObject(pd->process, INFINITE) == WAIT_FAILED) { - CloseHandle(pd->process); - CloseHandle(pd->thread); - reset(); - throw process_error{"child process wait failed"}; - } - - DWORD ec = 0; - if (!GetExitCodeProcess(pd->process, &ec)) { - CloseHandle(pd->process); - CloseHandle(pd->thread); - reset(); - throw process_error{"could not retrieve exit code"}; - } - - CloseHandle(pd->process); - CloseHandle(pd->thread); - reset(); - - return int(ec); -} - #endif - -OSTD_EXPORT void subprocess::move_data(subprocess &i) { - data *od = static_cast(i.p_current); - if (!od) { - return; - } - p_current = ::new (reinterpret_cast(&p_data)) data{*od}; - i.p_current = nullptr; -} - -OSTD_EXPORT void subprocess::swap_data(subprocess &i) { - if (!p_current) { - move_data(i); - } else if (!i.p_current) { - i.move_data(*this); - } else { - std::swap( - *static_cast(p_current), *static_cast(i.p_current) - ); - } -} - -OSTD_EXPORT subprocess::~subprocess() { - try { - close(); - } catch (process_error const &) {} - reset(); -} - -} /* namespace ostd */ diff --git a/src/win32/context_stack.cc b/src/win32/context_stack.cc new file mode 100644 index 0000000..a4d4311 --- /dev/null +++ b/src/win32/context_stack.cc @@ -0,0 +1,89 @@ +/* Stack allocation implementation for coroutine contexts. + * For Windows systems only, other implementations are stored elsewhere. + * + * This file is part of libostd. See COPYING.md for futher information. + */ + +#include "ostd/platform.hh" + +#ifndef OSTD_PLATFORM_WIN32 +# error "Incorrect platform" +#endif + +#include +#include +#include +#include + +#include "ostd/context_stack.hh" + +#define WIN32_LEAN_AND_MEAN +#include + +namespace ostd { + +namespace detail { + OSTD_EXPORT void *stack_alloc(std::size_t sz) { + void *p = VirtualAlloc(0, sz, MEM_COMMIT, PAGE_READWRITE); + if (!p) { + throw std::bad_alloc{}; + } + return p; + } + + OSTD_EXPORT void stack_free(void *p, std::size_t) noexcept { + VirtualFree(p, 0, MEM_RELEASE); + } + + OSTD_EXPORT std::size_t stack_main_size() noexcept { + /* 4 MiB for Windows... */ + } + + OSTD_EXPORT void stack_protect(void *p, std::size_t sz) noexcept { + DWORD oo; + VirtualProtect(p, sz, PAGE_READWRITE | PAGE_GUARD, &oo); + } + + /* used by stack traits */ + inline void ctx_pagesize(std::size_t *s) noexcept { + SYSTEM_INFO si; + GetSystemInfo(&si); + *s = std::size_t(si.dwPageSize); + } +} /* namespace detail */ + +OSTD_EXPORT bool stack_traits::is_unbounded() noexcept { + return true; +} + +OSTD_EXPORT std::size_t stack_traits::page_size() noexcept { + static std::size_t size = 0; + static std::once_flag fl; + std::call_once(fl, detail::ctx_pagesize, &size); + return size; +} + +OSTD_EXPORT std::size_t stack_traits::minimum_size() noexcept { + /* no func on Windows, sane default of 8 KiB */ + return 8 * 1024; +} + +OSTD_EXPORT std::size_t stack_traits::maximum_size() noexcept { + /* value is technically undefined when is_unbounded() is + * true, just default to 1 GiB so we actually return something + */ + return 1024 * 1024 * 1024; +} + +OSTD_EXPORT std::size_t stack_traits::default_size() noexcept { + /* no func on Windows either, default to 64 KiB */ + return 8 * 8 * 1024; +} + +struct coroutine_context; + +namespace detail { + OSTD_EXPORT thread_local coroutine_context *coro_current = nullptr; +} + +} /* namespace ostd */ diff --git a/src/win32/process.cc b/src/win32/process.cc new file mode 100644 index 0000000..e06acae --- /dev/null +++ b/src/win32/process.cc @@ -0,0 +1,451 @@ +/* Process handling implementation bits. + * For Windows systems only, other implementations are stored elsewhere. + * + * This file is part of libostd. See COPYING.md for futher information. + */ + +#include "ostd/platform.hh" + +#ifndef OSTD_PLATFORM_WIN32 +# error "Incorrect platform" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ostd/process.hh" +#include "ostd/format.hh" + +#include "ostd/filesystem.hh" + +namespace fs = ostd::filesystem; + +namespace ostd { +namespace detail { + +OSTD_EXPORT void split_args_impl( + string_range const &str, void (*func)(string_range, void *), void *data +) { + if (!str.size()) { + return; + } + std::unique_ptr wstr{new wchar_t[str.size() + 1]}; + memset(wstr.get(), 0, (str.size() + 1) * sizeof(wchar_t)); + + if (!MultiByteToWideChar( + CP_UTF8, 0, str.data(), str.size(), wstr.get(), str.size() + 1 + )) { + throw word_error{"unicode conversion failed"}; + } + + int argc = 0; + wchar_t **pwargs = CommandLineToArgvW(wstr.get(), &argc); + + if (!pwargs) { + throw word_error{"command line parsing failed"}; + } + + /* if anything throws, make sure stuff gets freed */ + struct wchar_guard { + void operator()(wchar_t **p) const { + LocalFree(p); + } + }; + std::unique_ptr wguard{pwargs}; + + for (int i = 0; i < argc; ++i) { + wchar_t *arg = pwargs[i]; + std::size_t arglen = wcslen(arg); + + std::size_t req = 0; + if (!(req = WideCharToMultiByte( + CP_UTF8, 0, arg, arglen + 1, nullptr, 0, nullptr, nullptr + ))) { + throw word_error{"unicode conversion failed"}; + } + + std::unique_ptr buf{new char[req]}; + if (!WideCharToMultiByte( + CP_UTF8, 0, arg, arglen + 1, buf.get(), req, nullptr, nullptr + )) { + throw word_error{"unicode conversion failed"}; + } + + func(string_range{buf.get(), buf.get() + req - 1}, data); + } +} + +} /* namespace detail */ + +struct data { + HANDLE process = nullptr, thread = nullptr; +}; + +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{"could not open pipe"}; + } + if (!SetHandleInformation(read ? p_r : p_w, HANDLE_FLAG_INHERIT, 0)) { + throw process_error{"could not set pipe parameters"}; + } + } + + void fdopen(file_stream &s, bool read) { + int fd = _open_osfhandle( + reinterpret_cast(read ? p_r : p_w), + read ? _O_RDONLY : 0 + ); + if (fd < 0) { + throw process_error{"could not open redirected stream"}; + } + if (read) { + p_r = nullptr; + } else { + p_w = nullptr; + } + auto p = _fdopen(fd, read ? "r" : "w"); + if (!p) { + _close(fd); + throw process_error{"could not open redirected stream"}; + } + 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); + return (fs::is_regular_file(st) || fs::is_symlink(st)); + }; + + 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))) { + fs::path rp{buf}; + rp.replace_filename(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{buf} / 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 dynbuf; + if (req > sizeof(buf)) { + dynbuf.reserve(req); + for (;;) { + req = GetEnvironmentVariableW( + L"PATH", dynbuf.data(), dynbuf.capacity() + ); + if (!req) { + return cmd; + } + if (req > dynbuf.capacity()) { + dynbuf.reserve(req); + } else { + envp = dynbuf.data(); + break; + } + } + } + for (;;) { + auto sp = wcschr(envp, L';'); + fs::path rp; + if (!sp) { + rp = fs::path{envp} / p; + } else if (sp == envp) { + envp = sp + 1; + continue; + } else { + rp = fs::path{envp, sp} / p; + envp = sp + 1; + } + if (is_maybe_exec(rp)) { + return rp.native(); + } + } + } + /* nothing found */ + return cmd; +} + +/* 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 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 += "\\\""; + ++p; + } else { + /* handle any sequence of \ optionally followed by a " */ + char const *op = p; + while (*p == '\\') { + ++p; + } + if (*p == '\"') { + /* double all the backslashes plus one for the " */ + ret.append((p - op) * 2 + 1, '\\'); + ret += '\"'; + } else { + ret.append(p - op, '\\'); + } + } + sp = p; + } + ret += '\"'; + } + return ret; +} + +OSTD_EXPORT void subprocess::open_impl( + std::string const &cmd, std::vector const &args, bool use_path +) { + if (use_in == process_stream::STDOUT) { + throw process_error{"could not redirect stdin to stdout"}; + } + + /* 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, sa, false); + pipe_out.open(use_out, sa, true); + pipe_err.open(use_err, sa, true); + + /* process creation */ + + PROCESS_INFORMATION pi; + STARTUPINFOW si; + + memset(&pi, 0, sizeof(PROCESS_INFORMATION)); + memset(&si, 0, sizeof(STARTUPINFOW)); + + si.cb = sizeof(STARTUPINFOW); + + 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{"could not get standard input handle"}; + } + } + 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{"could not get standard output handle"}; + } + } + 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{"could not get standard error handle"}; + } + } + si.dwFlags |= STARTF_USESTDHANDLES; + + std::wstring cmdpath; + /* convert and optionally resolve PATH and other lookup locations */ + { + std::unique_ptr wcmd{new wchar_t[cmd.size() + 1]}; + if (!MultiByteToWideChar( + CP_UTF8, 0, cmd.data(), cmd.size() + 1, wcmd.get(), cmd.size() + 1 + )) { + throw process_error{"unicode conversion failed"}; + } + 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 cmdline{new wchar_t[astr.size() + 1]}; + if (!MultiByteToWideChar( + CP_UTF8, 0, astr.data(), astr.size() + 1, cmdline.get(), astr.size() + 1 + )) { + throw process_error{"unicode conversion failed"}; + } + + /* 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.get(), + 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(&p_data)) data{ + pi.hProcess, pi.hThread + }; + + if (!success) { + throw process_error{"could not execute subprocess"}; + } +} + +OSTD_EXPORT void subprocess::reset() { + p_current = nullptr; +} + +OSTD_EXPORT int subprocess::close() { + if (!p_current) { + throw process_error{"no child process"}; + } + + data *pd = static_cast(p_current); + + if (WaitForSingleObject(pd->process, INFINITE) == WAIT_FAILED) { + CloseHandle(pd->process); + CloseHandle(pd->thread); + reset(); + throw process_error{"child process wait failed"}; + } + + DWORD ec = 0; + if (!GetExitCodeProcess(pd->process, &ec)) { + CloseHandle(pd->process); + CloseHandle(pd->thread); + reset(); + throw process_error{"could not retrieve exit code"}; + } + + CloseHandle(pd->process); + CloseHandle(pd->thread); + reset(); + + return int(ec); +} + +OSTD_EXPORT void subprocess::move_data(subprocess &i) { + data *od = static_cast(i.p_current); + if (!od) { + return; + } + p_current = ::new (reinterpret_cast(&p_data)) data{*od}; + i.p_current = nullptr; +} + +OSTD_EXPORT void subprocess::swap_data(subprocess &i) { + if (!p_current) { + move_data(i); + } else if (!i.p_current) { + i.move_data(*this); + } else { + std::swap( + *static_cast(p_current), *static_cast(i.p_current) + ); + } +} + +} /* namespace ostd */