From dc1664aabde47225763817f4c1edd662ed63d8ae Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Sat, 1 Jul 2023 13:41:23 +0800 Subject: [PATCH] added process support. --- CMakeLists.txt | 12 ++- include/boost/async/io/popen.hpp | 62 +++++++++++++++ include/boost/async/io/process.hpp | 115 ++++++++++++++++++++++++++++ include/boost/async/io/resolver.hpp | 1 - src/io/popen.cpp | 109 ++++++++++++++++++++++++++ src/io/process.cpp | 87 +++++++++++++++++++++ test/io/CMakeLists.txt | 3 +- test/io/process.cpp | 39 ++++++++++ 8 files changed, 422 insertions(+), 6 deletions(-) create mode 100644 include/boost/async/io/popen.hpp create mode 100644 include/boost/async/io/process.hpp create mode 100644 src/io/popen.cpp create mode 100644 src/io/process.cpp create mode 100644 test/io/process.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ac4ad798..08681646 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ endif() if (NOT BOOST_ASYNC_BUILD_INLINE) find_package(Threads REQUIRED) - find_package(Boost REQUIRED container system json url) + find_package(Boost REQUIRED container system json filesystem) include_directories(include) endif() @@ -97,16 +97,20 @@ add_library(boost_async src/io/read.cpp src/io/read_at.cpp src/io/read_until.cpp + src/io/popen.cpp + src/io/process.cpp src/io/buffers/register.cpp src/io/write.cpp src/io/write_at.cpp - src/io/detail/random_access_device.cpp src/io/copy.cpp) + src/io/detail/random_access_device.cpp + src/io/copy.cpp) target_include_directories(boost_async PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(boost_async PUBLIC - Boost::container Boost::system Boost::url + Boost::container Boost::system Threads::Threads) -target_compile_definitions(boost_async PRIVATE -DBOOST_ASYNC_SOURCE) +target_compile_definitions(boost_async PRIVATE BOOST_ASYNC_SOURCE=1) +target_compile_definitions(boost_async PUBLIC BOOST_PROCESS_USE_STD_FS=1) add_library(Boost::async ALIAS boost_async) diff --git a/include/boost/async/io/popen.hpp b/include/boost/async/io/popen.hpp new file mode 100644 index 00000000..59097572 --- /dev/null +++ b/include/boost/async/io/popen.hpp @@ -0,0 +1,62 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASYNC_IO_POPEN_HPP +#define BOOST_ASYNC_IO_POPEN_HPP + +#include +#include +#include +#include + +namespace boost::async::io +{ + +struct popen : stream +{ + using wait_result = system::result; + using handle_type = typename boost::process::v2::basic_process::handle_type; + using native_handle_type = typename boost::process::v2::basic_process::native_handle_type; + + BOOST_ASYNC_DECL popen(boost::process::v2::filesystem::path executable, + std::initializer_list args, + process_initializer initializer = {}); + + + BOOST_ASYNC_DECL popen(boost::process::v2::filesystem::path executable, + std::span args, + process_initializer initializer = {}); + + [[nodiscard]] BOOST_ASYNC_DECL system::result interrupt(); + [[nodiscard]] BOOST_ASYNC_DECL system::result request_exit(); + [[nodiscard]] BOOST_ASYNC_DECL system::result suspend(); + [[nodiscard]] BOOST_ASYNC_DECL system::result resume(); + [[nodiscard]] BOOST_ASYNC_DECL system::result terminate(); + [[nodiscard]] BOOST_ASYNC_DECL handle_type detach(); + [[nodiscard]] BOOST_ASYNC_DECL system::result running(); + + + [[nodiscard]] pid_type id() const; + + [[nodiscard]] system::result close() override; + [[nodiscard]] system::result cancel() override; + [[nodiscard]] bool is_open() const override; + + private: + void async_read_some_impl_(buffers::mutable_buffer_subspan buffer, async::completion_handler h)override; + void async_write_some_impl_(buffers::const_buffer_subspan buffer, async::completion_handler h) override; + public: + [[nodiscard]] process::wait_op_ wait() { return process::wait_op_{popen_}; } + process::wait_op_ operator co_await () { return wait(); } + private: + boost::process::v2::basic_popen popen_; +}; + + +} + +#endif //BOOST_ASYNC_IO_POPEN_HPP diff --git a/include/boost/async/io/process.hpp b/include/boost/async/io/process.hpp new file mode 100644 index 00000000..6c6401cb --- /dev/null +++ b/include/boost/async/io/process.hpp @@ -0,0 +1,115 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASYNC_IO_PROCESS_HPP +#define BOOST_ASYNC_IO_PROCESS_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace boost::async::io +{ + +using boost::process::v2::pid_type; + +struct process_initializer +{ + process::v2::process_stdio stdio; + process::v2::process_start_dir start_dir{process::v2::filesystem::current_path()}; + process::v2::process_environment env{process::v2::environment::current()}; +}; + +struct process +{ + using wait_result = system::result; + using handle_type = typename boost::process::v2::basic_process::handle_type; + using native_handle_type = typename boost::process::v2::basic_process::native_handle_type; + + BOOST_ASYNC_DECL process(boost::process::v2::filesystem::path executable, + std::initializer_list args, + process_initializer initializer = {}); + + + BOOST_ASYNC_DECL process(boost::process::v2::filesystem::path executable, + std::span args, + process_initializer initializer = {}); + + BOOST_ASYNC_DECL process(pid_type pid); + BOOST_ASYNC_DECL process(pid_type pid, native_handle_type native_handle); + + [[nodiscard]] BOOST_ASYNC_DECL system::result interrupt(); + [[nodiscard]] BOOST_ASYNC_DECL system::result request_exit(); + [[nodiscard]] BOOST_ASYNC_DECL system::result suspend(); + [[nodiscard]] BOOST_ASYNC_DECL system::result resume(); + [[nodiscard]] BOOST_ASYNC_DECL system::result terminate(); + [[nodiscard]] BOOST_ASYNC_DECL handle_type detach(); + [[nodiscard]] BOOST_ASYNC_DECL system::result running(); + + + [[nodiscard]] pid_type id() const; + + private: + struct wait_op_ : detail::deferred_op_resource_base + { + constexpr static bool await_ready() { return false; } + + BOOST_ASYNC_DECL void init_op(completion_handler handler); + + template + bool await_suspend(std::coroutine_handle h) + { + try + { + init_op(completion_handler{h, result_, get_resource(h)}); + return true; + } + catch(...) + { + error = std::current_exception(); + return false; + } + } + + [[nodiscard]] wait_result await_resume() + { + if (error) + std::rethrow_exception(std::exchange(error, nullptr)); + auto [ec, sig] = result_.value_or(std::make_tuple(system::error_code{}, 0)); + if (ec) + return ec; + else + return sig; + } + wait_op_(boost::process::v2::basic_process & process) : process_(process) {} + private: + boost::process::v2::basic_process & process_; + std::exception_ptr error; + std::optional> result_; + char buffer[256]; + std::optional resource; + }; + public: + [[nodiscard]] wait_op_ wait() { return wait_op_{process_}; } + wait_op_ operator co_await () { return wait(); } + private: + boost::process::v2::basic_process process_; + friend struct popen; +}; + + +} + +#endif //BOOST_ASYNC_IO_PROCESS_HPP diff --git a/include/boost/async/io/resolver.hpp b/include/boost/async/io/resolver.hpp index 76ef5716..f2be328a 100644 --- a/include/boost/async/io/resolver.hpp +++ b/include/boost/async/io/resolver.hpp @@ -79,7 +79,6 @@ struct resolver asio::ip::basic_resolver resolver_; }; -// NOTE: Doesn't need to be a promise, can be optimized. struct lookup { lookup(core::string_view host, core::string_view service) diff --git a/src/io/popen.cpp b/src/io/popen.cpp new file mode 100644 index 00000000..fadf8bfb --- /dev/null +++ b/src/io/popen.cpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +namespace boost::async::io +{ + +popen::popen(boost::process::v2::filesystem::path executable, + std::initializer_list args, + process_initializer initializer) + : popen_(this_thread::get_executor(), + executable, + args, + initializer.stdio, + initializer.start_dir, + initializer.env) {} + + +popen::popen(boost::process::v2::filesystem::path executable, + std::span args, + process_initializer initializer) + : popen_(this_thread::get_executor(), + executable, + args, + initializer.stdio, + initializer.start_dir, + initializer.env) {} + +pid_type popen::id() const {return popen_.id();} + +system::result popen::interrupt() +{ + system::error_code ec; + popen_.interrupt(ec); + return ec ? ec : system::result(); +} +system::result popen::request_exit() +{ + system::error_code ec; + popen_.request_exit(ec); + return ec ? ec : system::result(); +} +system::result popen::suspend() +{ + system::error_code ec; + popen_.suspend(ec); + return ec ? ec : system::result(); +} +system::result popen::resume() +{ + system::error_code ec; + popen_.resume(ec); + return ec ? ec : system::result(); +} +system::result popen::terminate() +{ + system::error_code ec; + popen_.terminate(ec); + return ec ? ec : system::result(); +} +popen::handle_type popen::detach() +{ + return popen_.detach(); +} +system::result popen::running() +{ + system::error_code ec; + auto res = popen_.running(ec); + return ec ? ec : system::result(res); +} + +system::result popen::close() +{ + return this->terminate(); +} + +system::result popen::cancel() +{ + system::error_code ec; + popen_.get_stdin().cancel(ec); + if (ec) + return ec; + popen_.get_stdout().cancel(ec); + if (ec) + return ec; + return {}; +} + +bool popen::is_open() const +{ + return this->popen_.is_open(); +} + +void popen::async_read_some_impl_(buffers::mutable_buffer_subspan buffer, async::completion_handler h) +{ + popen_.async_read_some(buffer, std::move(h)); +} +void popen::async_write_some_impl_(buffers::const_buffer_subspan buffer, async::completion_handler h) +{ + popen_.async_write_some(buffer, std::move(h)); +} + + +} \ No newline at end of file diff --git a/src/io/process.cpp b/src/io/process.cpp new file mode 100644 index 00000000..ed5e140a --- /dev/null +++ b/src/io/process.cpp @@ -0,0 +1,87 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +namespace boost::async::io +{ + +process::process(boost::process::v2::filesystem::path executable, + std::initializer_list args, + process_initializer initializer) + : process_(this_thread::get_executor(), + executable, + args, + initializer.stdio, + initializer.start_dir, + initializer.env) {} + + +process::process(boost::process::v2::filesystem::path executable, + std::span args, + process_initializer initializer) + : process_(this_thread::get_executor(), + executable, + args, + initializer.stdio, + initializer.start_dir, + initializer.env) {} + + +process::process(pid_type pid) : process_(this_thread::get_executor(), pid) {} +process::process(pid_type pid, native_handle_type native_handle) + : process_(this_thread::get_executor(), pid, native_handle) {} + +void process::wait_op_::init_op(completion_handler handler) +{ + process_.async_wait(std::move(handler)); +} + +pid_type process::id() const {return process_.id();} + +system::result process::interrupt() +{ + system::error_code ec; + process_.interrupt(ec); + return ec ? ec : system::result(); +} +system::result process::request_exit() +{ + system::error_code ec; + process_.request_exit(ec); + return ec ? ec : system::result(); +} +system::result process::suspend() +{ + system::error_code ec; + process_.suspend(ec); + return ec ? ec : system::result(); +} +system::result process::resume() +{ + system::error_code ec; + process_.resume(ec); + return ec ? ec : system::result(); +} +system::result process::terminate() +{ + system::error_code ec; + process_.terminate(ec); + return ec ? ec : system::result(); +} +process::handle_type process::detach() +{ + return process_.detach(); +} +system::result process::running() +{ + system::error_code ec; + auto res = process_.running(ec); + return ec ? ec : system::result(res); +} + +} \ No newline at end of file diff --git a/test/io/CMakeLists.txt b/test/io/CMakeLists.txt index 4e7aa4d9..b0978b4b 100644 --- a/test/io/CMakeLists.txt +++ b/test/io/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(boost_async_io_tests ../doctest.cpp read.cpp copy.cpp - read_until.cpp) + read_until.cpp + process.cpp) target_link_libraries(boost_async_io_tests Boost::container Boost::async OpenSSL::SSL) add_test(NAME boost_async-io COMMAND boost_async_io_tests) diff --git a/test/io/process.cpp b/test/io/process.cpp new file mode 100644 index 00000000..9acf10b9 --- /dev/null +++ b/test/io/process.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../doctest.h" +#include "../test.hpp" +#include + +#include + +using namespace boost::async; +namespace bpv = boost::process::v2; + +#if defined(BOOST_PROCESS_V2_WINDOWS) +bpv::filesystem::path shell() +{ + return bpv::environment::find_executable("cmd"); +} +#else +bpv::filesystem::path shell() +{ + return bpv::environment::find_executable("sh"); +} +#endif + +CO_TEST_CASE("process") +{ + +#if defined(BOOST_PROCESS_V2_WINDOWS) + CHECK(42 == co_await io::process(bpv::environment::find_executable("cmd"), {"/c", "exit 42"})); + +#else + CHECK(42 == co_await io::process(bpv::environment::find_executable("sh"), {"-c", "exit 42"})); +#endif +} +