From 0893147cc59251c7ed8cc5de6c2e6e630adbad3c Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Jun 2025 16:17:32 +0800 Subject: [PATCH 1/4] [windows] fixed escaping of path without args. Closes #501. Credit to @melak47 for the solution. --- include/boost/process/v2/windows/default_launcher.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/boost/process/v2/windows/default_launcher.hpp b/include/boost/process/v2/windows/default_launcher.hpp index 29483b8c8..52d863a25 100644 --- a/include/boost/process/v2/windows/default_launcher.hpp +++ b/include/boost/process/v2/windows/default_launcher.hpp @@ -403,7 +403,12 @@ struct default_launcher static std::wstring build_command_line(const filesystem::path & pt, const Args & args) { if (std::begin(args) == std::end(args)) - return pt.native(); + { + std::wstring buffer; + buffer.resize(escaped_argv_length(pt.native())); + escape_argv_string(&buffer.front(), buffer.size(), pt.native()); + return buffer; + } return build_command_line_impl(pt, args, *std::begin(args)); } From 3db1804d4faa1c046e4c4ec63d4a0ba1feb0ff8d Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Jun 2025 16:26:41 +0800 Subject: [PATCH 2/4] wait checks the error code first. See #499. --- include/boost/process/v2/process.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/boost/process/v2/process.hpp b/include/boost/process/v2/process.hpp index 5643e3927..f30bc4699 100644 --- a/include/boost/process/v2/process.hpp +++ b/include/boost/process/v2/process.hpp @@ -300,6 +300,8 @@ struct basic_process */ bool running() { + if (!process_is_running(exit_status_)) + return false; error_code ec; native_exit_code_type exit_code{}; auto r = process_handle_.running(exit_code, ec); @@ -314,6 +316,8 @@ struct basic_process /// Throwing @overload bool running(error_code & ec) bool running(error_code & ec) noexcept { + if (!process_is_running(exit_status_)) + return false; native_exit_code_type exit_code{}; auto r = process_handle_.running(exit_code, ec); if (!ec && !r) From 0b04977dc0207e6ad8af46e9097cee0b0c279e9a Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 1 Jul 2025 11:09:13 +0800 Subject: [PATCH 3/4] process_handle.async_wait accepts ref to exit_code Closes #503. --- doc/reference/process_handle.adoc | 7 ++-- .../process/v2/detail/process_handle_fd.hpp | 34 +++++++++---------- .../v2/detail/process_handle_fd_or_signal.hpp | 19 ++++++----- .../v2/detail/process_handle_signal.hpp | 25 +++++++------- .../v2/detail/process_handle_windows.hpp | 20 +++++------ include/boost/process/v2/process.hpp | 19 ++++++----- test/v2/process.cpp | 20 +++++++++++ 7 files changed, 83 insertions(+), 61 deletions(-) diff --git a/doc/reference/process_handle.adoc b/doc/reference/process_handle.adoc index f6905a2a0..603c8309f 100644 --- a/doc/reference/process_handle.adoc +++ b/doc/reference/process_handle.adoc @@ -100,9 +100,10 @@ struct basic_process_handle // Check if the process handle is referring to an existing process. bool is_open() const; - // Asynchronously wait for the process to exit and deliver the native exit-code in the completion handler. - template> - auto async_wait(WaitHandler &&handler = net::default_completion_token_t()); + auto async_wait(native_exit_code_type &exit_status, WaitHandler &&handler = net::default_completion_token_t()); }; ---- \ No newline at end of file diff --git a/include/boost/process/v2/detail/process_handle_fd.hpp b/include/boost/process/v2/detail/process_handle_fd.hpp index 8af291d96..bb2eb0d74 100644 --- a/include/boost/process/v2/detail/process_handle_fd.hpp +++ b/include/boost/process/v2/detail/process_handle_fd.hpp @@ -286,24 +286,21 @@ struct basic_process_handle_fd { net::posix::basic_descriptor &descriptor; pid_type pid_; - + native_exit_code_type & exit_code; template void operator()(Self &&self) { self.reset_cancellation_state(asio::enable_total_cancellation()); error_code ec; - native_exit_code_type exit_code{}; int wait_res = -1; if (pid_ <= 0) // error, complete early - ec = net::error::bad_descriptor; - else + BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::bad_descriptor); + else if (process_is_running(exit_code)) { wait_res = ::waitpid(pid_, &exit_code, WNOHANG); if (wait_res == -1) ec = get_last_error(); } - - if (!ec && (wait_res == 0)) { descriptor.async_wait(net::posix::descriptor_base::wait_read, std::move(self)); @@ -313,38 +310,39 @@ struct basic_process_handle_fd struct completer { error_code ec; - native_exit_code_type code; typename std::decay::type self; void operator()() { - self.complete(ec, code); + self.complete(ec); } }; - net::post(descriptor.get_executor(), completer{ec, exit_code, std::move(self)}); + net::dispatch( + net::get_associated_immediate_executor(self, descriptor.get_executor()), + completer{ec, std::move(self)}); } template void operator()(Self &&self, error_code ec, int = 0) { - native_exit_code_type exit_code{}; - if (!ec) + if (!ec && process_is_running(exit_code)) if (::waitpid(pid_, &exit_code, 0) == -1) ec = get_last_error(); - std::move(self).complete(ec, exit_code); + std::move(self).complete(ec); } }; public: - template> - auto async_wait(WaitHandler &&handler = net::default_completion_token_t()) - -> decltype(net::async_compose( - async_wait_op_{descriptor_, pid_}, handler, descriptor_)) + auto async_wait(native_exit_code_type & exit_code, + WaitHandler &&handler = net::default_completion_token_t()) + -> decltype(net::async_compose( + async_wait_op_{descriptor_, pid_, exit_code}, handler, descriptor_)) { - return net::async_compose( - async_wait_op_{descriptor_, pid_}, handler, descriptor_); + return net::async_compose( + async_wait_op_{descriptor_, pid_, exit_code}, handler, descriptor_); } }; diff --git a/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp b/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp index 73c0e2be0..a2112b8a8 100644 --- a/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp +++ b/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp @@ -332,6 +332,7 @@ struct basic_process_handle_fd_or_signal int dummy; #endif pid_type pid_; + native_exit_code_type & exit_code; bool needs_post = true; template @@ -344,11 +345,10 @@ struct basic_process_handle_fd_or_signal template void operator()(Self &&self, error_code ec, int = 0) { - native_exit_code_type exit_code{}; int wait_res = -1; if (pid_ <= 0) // error, complete early ec = net::error::bad_descriptor; - else + else if (process_is_running(exit_code)) { wait_res = ::waitpid(pid_, &exit_code, WNOHANG); if (wait_res == -1) @@ -391,18 +391,19 @@ struct basic_process_handle_fd_or_signal template void operator()(Self &&self, native_exit_code_type code, error_code ec) { - self.complete(ec, code); + self.complete(ec); } }; public: - template> - auto async_wait(WaitHandler &&handler = net::default_completion_token_t()) - -> decltype(net::async_compose( - async_wait_op_{descriptor_, signal_set_, pid_}, handler, descriptor_)) + auto async_wait(native_exit_code_type & exit_code, + WaitHandler &&handler = net::default_completion_token_t()) + -> decltype(net::async_compose( + async_wait_op_{descriptor_, signal_set_, pid_, exit_code}, handler, descriptor_)) { - return net::async_compose( - async_wait_op_{descriptor_, signal_set_, pid_}, handler, descriptor_); + return net::async_compose( + async_wait_op_{descriptor_, signal_set_, pid_, exit_code}, handler, descriptor_); } }; } diff --git a/include/boost/process/v2/detail/process_handle_signal.hpp b/include/boost/process/v2/detail/process_handle_signal.hpp index 098ebb7ba..3f8a26052 100644 --- a/include/boost/process/v2/detail/process_handle_signal.hpp +++ b/include/boost/process/v2/detail/process_handle_signal.hpp @@ -308,7 +308,8 @@ struct basic_process_handle_signal net::basic_signal_set &handle; pid_type pid_; - + native_exit_code_type & exit_code; + template void operator()(Self &&self) { @@ -326,12 +327,11 @@ struct basic_process_handle_signal == net::cancellation_type::none) ec.clear(); - native_exit_code_type exit_code = -1; int wait_res = -1; if (pid_ <= 0) // error, complete early ec = net::error::bad_descriptor; - else if (!ec) + else if (!ec && process_is_running(exit_code)) { wait_res = ::waitpid(pid_, &exit_code, WNOHANG); if (wait_res == -1) @@ -345,7 +345,7 @@ struct basic_process_handle_signal } const auto exec = self.get_executor(); - net::dispatch(exec, net::append(std::move(self), exit_code, ec)); + net::dispatch(exec, net::append(std::move(self), ec)); } #else signal_set_dummy_ dummy_; @@ -360,20 +360,21 @@ struct basic_process_handle_signal } #endif template - void operator()(Self &&self, native_exit_code_type code, error_code ec) + void operator()(Self &&self, error_code ec) { - self.complete(ec, code); + self.complete(ec); } }; public: - template> - auto async_wait(WaitHandler &&handler = net::default_completion_token_t()) - -> decltype(net::async_compose( - async_wait_op_{signal_set_, pid_}, handler, signal_set_)) + auto async_wait(native_exit_code_type & exit_code, + WaitHandler &&handler = net::default_completion_token_t()) + -> decltype(net::async_compose( + async_wait_op_{signal_set_, pid_, exit_code}, handler, signal_set_)) { - return net::async_compose( - async_wait_op_{signal_set_, pid_}, handler, signal_set_); + return net::async_compose( + async_wait_op_{signal_set_, pid_, exit_code}, handler, signal_set_); } }; diff --git a/include/boost/process/v2/detail/process_handle_windows.hpp b/include/boost/process/v2/detail/process_handle_windows.hpp index 3aa4142b1..9e1518a85 100644 --- a/include/boost/process/v2/detail/process_handle_windows.hpp +++ b/include/boost/process/v2/detail/process_handle_windows.hpp @@ -275,7 +275,7 @@ struct basic_process_handle_win struct async_wait_op_ { handle_type &handle; - + native_exit_code_type & exit_code; template void operator()(Self &&self) { @@ -296,24 +296,24 @@ struct basic_process_handle_win template void operator()(Self &&self, error_code ec) { - native_exit_code_type exit_code{}; if (ec == asio::error::operation_aborted && !self.get_cancellation_state().cancelled()) return handle.async_wait(std::move(self)); - if (!ec) + if (!ec && process_is_running(exit_code)) // exit_code could be set by another call to wait. detail::get_exit_code_(handle.native_handle(), exit_code, ec); - std::move(self).complete(ec, exit_code); + std::move(self).complete(ec); } }; public: - template> - auto async_wait(WaitHandler &&handler = net::default_completion_token_t()) - -> decltype(net::async_compose( - async_wait_op_{handle_}, handler, handle_)) + auto async_wait(native_exit_code_type & exit_code, + WaitHandler &&handler = net::default_completion_token_t()) + -> decltype(net::async_compose( + async_wait_op_{handle_, exit_code}, handler, handle_)) { - return net::async_compose( - async_wait_op_{handle_}, handler, handle_ + return net::async_compose( + async_wait_op_{handle_, exit_code}, handler, handle_ ); } }; diff --git a/include/boost/process/v2/process.hpp b/include/boost/process/v2/process.hpp index f30bc4699..3ef167aa0 100644 --- a/include/boost/process/v2/process.hpp +++ b/include/boost/process/v2/process.hpp @@ -20,10 +20,12 @@ #if defined(BOOST_PROCESS_V2_STANDALONE) #include +#include #include #include #else #include +#include #include #include #endif @@ -359,23 +361,22 @@ struct basic_process } }; - net::post(handle.get_executor(), - completer{static_cast(res), std::move(self)}); + net::dispatch( + net::get_associated_immediate_executor(handle, handle.get_executor()), + completer{static_cast(res), std::move(self)}); } else - handle.async_wait(std::move(self)); + handle.async_wait(res, std::move(self)); } template - void operator()(Self && self, error_code ec, native_exit_code_type code) + void operator()(Self && self, error_code ec) { - if (!ec && process_is_running(code)) - handle.async_wait(std::move(self)); + if (!ec && process_is_running(res)) + handle.async_wait(res, std::move(self)); else { - if (!ec) - res = code; - std::move(self).complete(ec, evaluate_exit_code(code)); + std::move(self).complete(ec, evaluate_exit_code(res)); } } }; diff --git a/test/v2/process.cpp b/test/v2/process.cpp index ea47eef7f..cd814ae4e 100644 --- a/test/v2/process.cpp +++ b/test/v2/process.cpp @@ -768,6 +768,26 @@ BOOST_AUTO_TEST_CASE(no_zombie) BOOST_CHECK_EQUAL(errno, ECHILD); } +BOOST_AUTO_TEST_CASE(async_terminate_code) +{ + asio::io_context ctx; + using boost::unit_test::framework::master_test_suite; + const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]); + + bpv::process proc(ctx, pth, {"sleep", "1000"}); + + proc.async_wait([&](boost::system::error_code ec, int code) + { + BOOST_CHECK_MESSAGE(!ec, ec.what()); + BOOST_CHECK_EQUAL(code, SIGKILL); + BOOST_CHECK(!proc.running()); + }); + + asio::post(ctx, [&]{proc.terminate();}); + + ctx.run(); +} + #endif From ef8d08a86308453862ebcf309fb424a57d0dd1c6 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 1 Jul 2025 18:04:11 +0800 Subject: [PATCH 4/4] changed env example for windows wchar_t. --- example/env.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/env.cpp b/example/env.cpp index d0974efbb..9b751124a 100644 --- a/example/env.cpp +++ b/example/env.cpp @@ -47,8 +47,8 @@ int main() // tag::vector_env[] asio::io_context ctx; auto c = environment::current(); - // a view is fine since the value is our value is static. - std::vector my_env{c.begin(), c.end()}; + // we need to use a value, since windows needs wchar_t. + std::vector my_env{c.begin(), c.end()}; my_env.push_back("SECRET=THIS_IS_A_TEST"); auto exe = environment::find_executable("g++", my_env); process proc(ctx, exe, {"main.cpp"}, process_environment(my_env)); @@ -61,7 +61,7 @@ int main() asio::io_context ctx; std::unordered_map my_env; for (const auto & kv : environment::current()) - if (kv.key() != "SECRET") + if (kv.key().string() != "SECRET") my_env[kv.key()] = kv.value(); auto exe = environment::find_executable("g++", my_env);