From a632bb8e796be52929f1541d305910d704e55076 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Fri, 16 Oct 2020 10:09:55 +0300 Subject: Process: support pipe redirection --- src/file.cpp | 75 +++++++++++++++++++++++++++ src/handle.cpp | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pipe.cpp | 46 +++++++++++++++++ src/process.cpp | 44 +++++++++++----- src/stream.cpp | 41 +++++++++++++++ 5 files changed, 347 insertions(+), 12 deletions(-) create mode 100644 src/file.cpp create mode 100644 src/handle.cpp create mode 100644 src/pipe.cpp create mode 100644 src/stream.cpp (limited to 'src') diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 0000000..cb9be81 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include +#include +#include +#include + +#include +#include + +namespace winapi { +namespace { + +struct CreateFileParams { + static CreateFileParams read() { + CreateFileParams params; + params.dwDesiredAccess = GENERIC_READ; + params.dwShareMode = FILE_SHARE_READ; + params.dwCreationDisposition = OPEN_EXISTING; + return params; + } + + static CreateFileParams write() { + CreateFileParams params; + params.dwDesiredAccess = GENERIC_WRITE; + params.dwShareMode = FILE_SHARE_READ; + params.dwCreationDisposition = OPEN_ALWAYS; + return params; + } + + DWORD dwDesiredAccess = 0; + DWORD dwShareMode = 0; + DWORD dwCreationDisposition = 0; + +private: + CreateFileParams() = default; +}; + +Handle open_file(const std::string& path, const CreateFileParams& params) { + const auto unicode_path = LR"(\\?\)" + widen(path); + + SECURITY_ATTRIBUTES attributes; + std::memset(&attributes, 0, sizeof(attributes)); + attributes.nLength = sizeof(attributes); + attributes.bInheritHandle = TRUE; + + const auto handle = ::CreateFileW(unicode_path.c_str(), + params.dwDesiredAccess, + params.dwShareMode, + &attributes, + params.dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (handle == INVALID_HANDLE_VALUE) { + throw error::windows(GetLastError(), "CreateFileW"); + } + + return Handle{handle}; +} + +} // namespace + +Handle File::open_for_reading(const std::string& path) { + return open_file(path, CreateFileParams::read()); +} + +Handle File::open_for_writing(const std::string& path) { + return open_file(path, CreateFileParams::write()); +} + +} // namespace winapi diff --git a/src/handle.cpp b/src/handle.cpp new file mode 100644 index 0000000..a6ab3a5 --- /dev/null +++ b/src/handle.cpp @@ -0,0 +1,153 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace winapi { +namespace { + +std::runtime_error write_file_incomplete(std::size_t expected, std::size_t actual) { + std::ostringstream oss; + oss << "WriteFile could only write " << actual << " bytes instead of " << expected; + return std::runtime_error{oss.str()}; +} + +bool is_invalid_handle(HANDLE handle) { + return handle == NULL || handle == INVALID_HANDLE_VALUE; +} + +bool is_std_handle(HANDLE handle) { + return handle == ::GetStdHandle(STD_INPUT_HANDLE) || + handle == ::GetStdHandle(STD_OUTPUT_HANDLE) || + handle == ::GetStdHandle(STD_ERROR_HANDLE); +} + +} // namespace + +Handle::Handle(HANDLE impl) : m_impl{impl} {} + +Handle::Handle(Handle&& other) BOOST_NOEXCEPT_OR_NOTHROW { + swap(other); +} + +Handle& Handle::operator=(Handle other) BOOST_NOEXCEPT_OR_NOTHROW { + swap(other); + return *this; +} + +void Handle::swap(Handle& other) BOOST_NOEXCEPT_OR_NOTHROW { + using std::swap; + swap(m_impl, other.m_impl); +} + +bool Handle::is_invalid() const { + return !m_impl || is_invalid_handle(m_impl.get()); +} + +void Handle::close() { + m_impl.reset(); +} + +bool Handle::is_std() const { + return is_std_handle(m_impl.get()); +} + +Handle Handle::std_in() { + return Handle{::GetStdHandle(STD_INPUT_HANDLE)}; +} + +Handle Handle::std_out() { + return Handle{::GetStdHandle(STD_OUTPUT_HANDLE)}; +} + +Handle Handle::std_err() { + return Handle{::GetStdHandle(STD_ERROR_HANDLE)}; +} + +bool Handle::read_chunk(Buffer& buffer) const { + buffer.resize(max_chunk_size); + DWORD nb_read = 0; + + const auto ret = ::ReadFile(m_impl.get(), buffer.data(), buffer.size(), &nb_read, NULL); + + buffer.resize(nb_read); + + if (ret) { + return nb_read != 0; + } + + const auto ec = GetLastError(); + + switch (ec) { + case ERROR_BROKEN_PIPE: + // We've been reading from an anonymous pipe, and it's been closed. + return false; + default: + throw error::windows(ec, "ReadFile"); + } +} + +Handle::Buffer Handle::read() const { + Buffer buffer; + Buffer chunk; + + while (true) { + const auto next = read_chunk(chunk); + + buffer.reserve(buffer.size() + chunk.size()); + buffer.insert(buffer.cend(), chunk.cbegin(), chunk.cend()); + + if (!next) { + break; + } + } + + return buffer; +} + +void Handle::write(const void* data, std::size_t nb) const { + DWORD nb_written = 0; + + const auto ret = ::WriteFile(m_impl.get(), data, nb, &nb_written, NULL); + + if (!ret) { + throw error::windows(GetLastError(), "WriteFile"); + } + + if (nb != nb_written) { + throw write_file_incomplete(nb, nb_written); + } +} + +void Handle::write(const Buffer& buffer) const { + write(buffer.data(), buffer.size()); +} + +void Handle::inherit(bool yes) const { + if (!::SetHandleInformation(m_impl.get(), HANDLE_FLAG_INHERIT, yes ? 1 : 0)) { + throw error::windows(GetLastError(), "SetHandleInformation"); + } +} + +void Handle::Close::operator()(HANDLE impl) const { + if (is_invalid_handle(impl) || is_std_handle(impl)) + return; + const auto ret = ::CloseHandle(impl); + assert(ret); + WINAPI_UNUSED_PARAMETER(ret); +} + +} // namespace winapi diff --git a/src/pipe.cpp b/src/pipe.cpp new file mode 100644 index 0000000..a00a8f6 --- /dev/null +++ b/src/pipe.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include +#include +#include + +#include + +#include + +#include + +namespace winapi { +namespace { + +void create_pipe(Handle& read_end, Handle& write_end) { + HANDLE h_read_end = INVALID_HANDLE_VALUE; + HANDLE h_write_end = INVALID_HANDLE_VALUE; + + SECURITY_ATTRIBUTES attributes; + std::memset(&attributes, 0, sizeof(attributes)); + attributes.nLength = sizeof(attributes); + attributes.bInheritHandle = TRUE; + + BOOST_STATIC_CONSTEXPR DWORD buffer_size = 16 * 1024; + + const auto ret = ::CreatePipe(&h_read_end, &h_write_end, &attributes, buffer_size); + + if (!ret) { + throw error::windows(GetLastError(), "CreatePipe"); + } + + read_end = Handle{h_read_end}; + write_end = Handle{h_write_end}; +} + +} // namespace + +Pipe::Pipe() { + create_pipe(m_read_end, m_write_end); +} + +} // namespace winapi diff --git a/src/process.cpp b/src/process.cpp index 1771328..5887b05 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -21,42 +21,62 @@ namespace { typedef std::vector EscapedCommandLine; -Handle create_process(EscapedCommandLine cmd_line) { - BOOST_STATIC_CONSTEXPR DWORD flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT; +Handle create_process(EscapedCommandLine cmd_line, Process::IO& io) { + BOOST_STATIC_CONSTEXPR DWORD flags = /*CREATE_NO_WINDOW | */ CREATE_UNICODE_ENVIRONMENT; STARTUPINFOW startup_info; std::memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = static_cast(io.std_in.handle); + startup_info.hStdOutput = static_cast(io.std_out.handle); + startup_info.hStdError = static_cast(io.std_err.handle); PROCESS_INFORMATION child_info; std::memset(&child_info, 0, sizeof(child_info)); const auto ret = ::CreateProcessW( - NULL, cmd_line.data(), NULL, NULL, FALSE, flags, NULL, NULL, &startup_info, &child_info); + NULL, cmd_line.data(), NULL, NULL, TRUE, flags, NULL, NULL, &startup_info, &child_info); if (!ret) { throw error::windows(GetLastError(), "CreateProcessW"); } - Handle h_process{child_info.hProcess}; - Handle h_thread{child_info.hThread}; + io.close(); - return std::move(h_process); + Handle process{child_info.hProcess}; + Handle thread{child_info.hThread}; + + return std::move(process); } EscapedCommandLine escape_command_line(const CommandLine& cmd_line) { - const auto whole = widen(cmd_line.join()); - return {whole.cbegin(), whole.cend()}; + const auto unicode_cmd_line = widen(cmd_line.join()); + EscapedCommandLine buffer; + buffer.reserve(unicode_cmd_line.size() + 1); + buffer.assign(unicode_cmd_line.cbegin(), unicode_cmd_line.cend()); + buffer.emplace_back(L'\0'); + return buffer; } -Handle create_process(const CommandLine& cmd_line) { - return create_process(escape_command_line(cmd_line)); +Handle create_process(const CommandLine& cmd_line, Process::IO& io) { + return create_process(escape_command_line(cmd_line), io); } } // namespace +void Process::IO::close() { + std_in.handle.close(); + std_out.handle.close(); + std_err.handle.close(); +} + Process Process::create(const CommandLine& cmd_line) { - return Process{create_process(cmd_line)}; + return create(cmd_line, {}); +} + +Process Process::create(const CommandLine& cmd_line, IO io) { + return Process{create_process(cmd_line, io)}; } void Process::wait() { @@ -64,7 +84,7 @@ void Process::wait() { switch (ret) { case WAIT_OBJECT_0: - m_handle = Handle{}; + m_handle.close(); return; case WAIT_FAILED: throw error::windows(GetLastError(), "WaitForSingleObject"); diff --git a/src/stream.cpp b/src/stream.cpp new file mode 100644 index 0000000..b31635e --- /dev/null +++ b/src/stream.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include +#include +#include + +#include +#include + +namespace winapi { +namespace process { + +Stdin::Stdin() : Stream{Handle::std_in()} {} + +Stdout::Stdout() : Stream{Handle::std_out()} {} + +Stderr::Stderr() : Stream{Handle::std_err()} {} + +Stdin::Stdin(const std::string& path) : Stream{File::open_for_reading(path)} {} + +Stdout::Stdout(const std::string& path) : Stream{File::open_for_writing(path)} {} + +Stderr::Stderr(const std::string& path) : Stream{File::open_for_writing(path)} {} + +Stdin::Stdin(Pipe& pipe) : Stream{std::move(pipe.read_end())} { + pipe.write_end().dont_inherit(); +} + +Stdout::Stdout(Pipe& pipe) : Stream{std::move(pipe.write_end())} { + pipe.read_end().dont_inherit(); +} + +Stderr::Stderr(Pipe& pipe) : Stream{std::move(pipe.write_end())} { + pipe.read_end().dont_inherit(); +} + +} // namespace process +} // namespace winapi -- cgit v1.2.3