diff --git a/CMakeLists.txt b/CMakeLists.txt index 348670195..16a2073a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ # Distributed under the Boost Software License, Version 1.0. # https://www.boost.org/LICENSE_1_0.txt -cmake_minimum_required(VERSION 3.5...3.16) +cmake_minimum_required(VERSION 3.8...3.31) project(boost_process VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) diff --git a/README.md b/README.md index cc1de7cc0..4076442a7 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Boost.process is a library for comfortable management of processes, released wit | Branches | Linux / Windows | Code coverage | Matrix | |----------|----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| Develop: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/develop/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-develop-lightgray.svg)](http://www.boost.org/development/tests/develop/developer/process.html) | -| Master: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg?ref=refs/heads/develop)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/master/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-master-lightgray.svg)](http://www.boost.org/development/tests/master/developer/process.html) | +| Develop: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/develop/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-develop-lightgray.svg)](https://regression.boost.io/develop/developer/process.html) | +| Master: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg?ref=refs/heads/develop)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/master/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-master-lightgray.svg)](https://regression.boost.io/master/developer/process.html) | diff --git a/doc/reference/stdio.adoc b/doc/reference/stdio.adoc index 79603f6d6..b34aa7173 100644 --- a/doc/reference/stdio.adoc +++ b/doc/reference/stdio.adoc @@ -35,12 +35,57 @@ Valid initializers for any stdio are: - `std::nullptr_t` assigning a null-device - `FILE*` any open file, including `stdin`, `stdout` and `stderr` - - a filesystem::path, which will open a readable or writable depending on the direction of the stream - `native_handle` any native file handle (`HANDLE` on windows) or file descriptor (`int` on posix) - - any io-object with a .native_handle() function that is compatible with the above. E.g. a asio::ip::tcp::socket - - an asio::basic_writeable_pipe for stdin or asio::basic_readable_pipe for stderr/stdout. + - any io-object with a `.native_handle()` function that is compatible with the above. E.g. a `asio::ip::tcp::socket`, or a pipe object. + - a filesystem::path, which will open a readable or writable depending on the direction of the stream + - an `asio::basic_writeable_pipe` for stdin or `asio::basic_readable_pipe` for stderr/stdout. + +When passing a `FILE*`, a `native_handle` or an io-object with a `native_handle`, +the initializer will assign the handle as is to the child process. +That is the file descriptor/handle gets cloned into the subprocess and used without modification. + +When passing a filesystem::path, the initializer will attempt to open the file and then pass the handle +to the subprocess. + +When passing a `readable_pipe` to stdout/stderr or a `writable_pipe` to stdin by reference, +the initializer to create the other side of the pipe (`writable_pipe` for stdout/stderr, `readable_pipe` for `stdin`), +connect the pair and pass the native_handle to the child process. + +That is, these two are equivalent: + +.Implicit construction of the readable pipe. +[source,cpp] +---- +asio::io_context ctx; +asio::writable_pipe wp{ctx}; +// create a readable pipe internally and connect it to wp +process proc{ctx, "/bin/bash", {}, process_stdio{.in=wp}}; +// create it explicitly +{ + // the pipe the child process reads from + asio::readable_pipe rp{ctx}; + asio::connect_pipe(rp, wp); + // `rp.native_handle()` will be assigned to the child processes stdin + process proc{ctx, "/bin/bash", {}, process_stdio{.in=rp}}; + rp.close(); // close it so the pipe closes when the `proc exits. +} +---- +The explicit version allows you to assign the same `writable_pipe` to `stdout` and `stderr`: + +[source,cpp] +---- +// the pipe the parent process reads from and both +// stderr & stdout of the child process write to +asio::readable_pipe rp{ctx}; +asio::writable_pipe wp{ctx}; +asio::connect_pipe(rp, wp); +process proc{ctx, "/bin/bash", {}, process_stdio{.out=wp, .err=wp}}; +wp.close(); // close it so the pipe closes when the `proc exits. +---- + +NOTE: If the child writes to a pipe, the parent reads from it et vice versa. [source,cpp] @@ -52,4 +97,4 @@ struct process_stdio __implementation_defined__ out; __implementation_defined__ err; }; ----- \ No newline at end of file +---- diff --git a/include/boost/process/v1/detail/windows/basic_cmd.hpp b/include/boost/process/v1/detail/windows/basic_cmd.hpp index 87a0e85d8..757a89bc7 100644 --- a/include/boost/process/v1/detail/windows/basic_cmd.hpp +++ b/include/boost/process/v1/detail/windows/basic_cmd.hpp @@ -162,7 +162,9 @@ struct exe_cmd_init : handler_base_ext } static exe_cmd_init exe_args_shell(string_type && exe, std::vector && args) { - std::vector args_ = {c_arg(Char()), std::move(exe)}; + std::vector args_ = {c_arg(Char())}; + if (!exe.empty()) + args_.emplace_back(std::move(exe)); args_.insert(args_.end(), std::make_move_iterator(args.begin()), std::make_move_iterator(args.end())); string_type sh = get_shell(Char()); diff --git a/include/boost/process/v2/detail/process_handle_fd.hpp b/include/boost/process/v2/detail/process_handle_fd.hpp index bb2eb0d74..34a54669a 100644 --- a/include/boost/process/v2/detail/process_handle_fd.hpp +++ b/include/boost/process/v2/detail/process_handle_fd.hpp @@ -69,6 +69,8 @@ struct basic_process_handle_fd basic_process_handle_fd(executor_type executor, pid_type pid) : pid_(pid), descriptor_(executor, syscall(SYS_pidfd_open, pid, 0)) { + if (descriptor_.native_handle() == -1) + detail::throw_error(detail::get_last_error(), "wait(pid)"); } basic_process_handle_fd(executor_type executor, pid_type pid, native_handle_type process_handle) diff --git a/include/boost/process/v2/detail/utf8.hpp b/include/boost/process/v2/detail/utf8.hpp index a31cdff4d..25414299e 100644 --- a/include/boost/process/v2/detail/utf8.hpp +++ b/include/boost/process/v2/detail/utf8.hpp @@ -44,9 +44,14 @@ std::basic_string conv_string( if (ec) detail::throw_error(ec, "size_as_utf8"); + std::basic_string res(allocator); res.resize(req_size); + if (req_size == 0) + return res; + + auto res_size = convert_to_utf8(data, size, &res.front(), req_size, ec); if (ec) detail::throw_error(ec, "convert_to_utf8"); @@ -70,6 +75,9 @@ std::basic_string conv_string( std::basic_string res(allocator); res.resize(req_size); + if (req_size == 0) + return res; + auto res_size = convert_to_wide(data, size, &res.front(), req_size, ec); if (ec) detail::throw_error(ec, "convert_to_wide"); diff --git a/include/boost/process/v2/environment.hpp b/include/boost/process/v2/environment.hpp index c38064c65..0d97092d1 100644 --- a/include/boost/process/v2/environment.hpp +++ b/include/boost/process/v2/environment.hpp @@ -1762,9 +1762,12 @@ struct process_environment std::vector env_buffer; std::vector unicode_env; - BOOST_PROCESS_V2_DECL error_code on_setup(windows::default_launcher & launcher, - const filesystem::path &, const std::wstring &); + const filesystem::path &, const std::wstring &) + { + return do_setup(launcher); + } + BOOST_PROCESS_V2_DECL error_code do_setup(windows::default_launcher & launcher); #else @@ -1813,7 +1816,13 @@ struct process_environment BOOST_PROCESS_V2_DECL error_code on_setup(posix::default_launcher & launcher, - const filesystem::path &, const char * const *); + const filesystem::path &, const char * const *) + { + return do_setup(launcher); + } + + BOOST_PROCESS_V2_DECL error_code do_setup(posix::default_launcher & launcher); + std::vector env_buffer; std::vector env; diff --git a/include/boost/process/v2/posix/default_launcher.hpp b/include/boost/process/v2/posix/default_launcher.hpp index fc826fea2..c1abc0d3f 100644 --- a/include/boost/process/v2/posix/default_launcher.hpp +++ b/include/boost/process/v2/posix/default_launcher.hpp @@ -352,9 +352,9 @@ struct default_launcher } fd_whitelist.push_back(pg.p[1]); +#if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) auto & ctx = net::query( exec, net::execution::context); -#if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) ctx.notify_fork(net::execution_context::fork_prepare); #endif pid = ::fork(); @@ -386,7 +386,7 @@ struct default_launcher ignore_unused(::write(pg.p[1], &errno, sizeof(int))); BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()); detail::on_exec_error(*this, executable, argv, ec, inits...); - ::exit(EXIT_FAILURE); + ::_exit(EXIT_FAILURE); return basic_process{exec}; } #if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) diff --git a/include/boost/process/v2/posix/fork_and_forget_launcher.hpp b/include/boost/process/v2/posix/fork_and_forget_launcher.hpp index f86314dee..ca32690c1 100644 --- a/include/boost/process/v2/posix/fork_and_forget_launcher.hpp +++ b/include/boost/process/v2/posix/fork_and_forget_launcher.hpp @@ -115,7 +115,7 @@ struct fork_and_forget_launcher : default_launcher BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()); detail::on_exec_error(*this, executable, argv, ec, inits...); - ::exit(EXIT_FAILURE); + ::_exit(EXIT_FAILURE); return basic_process{exec}; } #if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) diff --git a/include/boost/process/v2/posix/pdfork_launcher.hpp b/include/boost/process/v2/posix/pdfork_launcher.hpp index 0a082f562..3e11d33e0 100644 --- a/include/boost/process/v2/posix/pdfork_launcher.hpp +++ b/include/boost/process/v2/posix/pdfork_launcher.hpp @@ -136,7 +136,7 @@ struct pdfork_launcher : default_launcher default_launcher::ignore_unused(::write(pg.p[1], &errno, sizeof(int))); BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()); detail::on_exec_error(*this, executable, argv, ec, inits...); - ::exit(EXIT_FAILURE); + ::_exit(EXIT_FAILURE); return basic_process{exec}; } #if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) diff --git a/include/boost/process/v2/posix/pipe_fork_launcher.hpp b/include/boost/process/v2/posix/pipe_fork_launcher.hpp index 415c529f0..4d90a9c8d 100644 --- a/include/boost/process/v2/posix/pipe_fork_launcher.hpp +++ b/include/boost/process/v2/posix/pipe_fork_launcher.hpp @@ -140,7 +140,7 @@ struct pipe_fork_launcher : default_launcher default_launcher::ignore_unused(::write(pg.p[1], &errno, sizeof(int))); BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()); detail::on_exec_error(*this, executable, argv, ec, inits...); - ::exit(EXIT_FAILURE); + ::_exit(EXIT_FAILURE); return basic_process{exec}; } ctx.notify_fork(net::execution_context::fork_parent); diff --git a/include/boost/process/v2/stdio.hpp b/include/boost/process/v2/stdio.hpp index 01d021613..4084e46f2 100644 --- a/include/boost/process/v2/stdio.hpp +++ b/include/boost/process/v2/stdio.hpp @@ -184,7 +184,7 @@ struct process_io_binding process_io_binding & operator=(const process_io_binding &) = delete; process_io_binding(process_io_binding && other) noexcept - : fd(other.fd), fd_needs_closing(other.fd), ec(other.ec) + : fd(other.fd), fd_needs_closing(other.fd_needs_closing), ec(other.ec) { other.fd = target; other.fd_needs_closing = false; diff --git a/include/boost/process/v2/windows/default_launcher.hpp b/include/boost/process/v2/windows/default_launcher.hpp index 52d863a25..16ff12d46 100644 --- a/include/boost/process/v2/windows/default_launcher.hpp +++ b/include/boost/process/v2/windows/default_launcher.hpp @@ -225,6 +225,9 @@ struct default_launcher INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}, nullptr}; + /// Allow batch files to be executed, which might pose a security threat. + bool allow_batch_files = false; + /// The process_information that gets assigned after a call to CreateProcess PROCESS_INFORMATION process_information{nullptr, nullptr, 0,0}; @@ -293,6 +296,12 @@ struct default_launcher Args && args, Inits && ... inits ) -> enable_init { + if (!allow_batch_files && ((executable.extension() == ".bat") || (executable.extension() == ".cmd"))) + { + BOOST_PROCESS_V2_ASSIGN_EC(ec, ERROR_ACCESS_DENIED, system_category()); + return basic_process(exec); + } + auto command_line = this->build_command_line(executable, std::forward(args)); ec = detail::on_setup(*this, executable, command_line, inits...); @@ -352,7 +361,6 @@ struct default_launcher BOOST_PROCESS_V2_DECL static std::size_t escape_argv_string(wchar_t * itr, std::size_t max_size, basic_string_view ws); - @@ -395,6 +403,7 @@ struct default_launcher { return detail::conv_string(arg.data(), arg.size()); }); + return build_command_line_impl(pt, argw, L""); } @@ -406,10 +415,11 @@ struct default_launcher { std::wstring buffer; buffer.resize(escaped_argv_length(pt.native())); - escape_argv_string(&buffer.front(), buffer.size(), pt.native()); + + if (!buffer.empty()) + escape_argv_string(&buffer.front(), buffer.size(), pt.native()); return buffer; } - return build_command_line_impl(pt, args, *std::begin(args)); } @@ -438,4 +448,4 @@ BOOST_PROCESS_V2_END_NAMESPACE -#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP \ No newline at end of file +#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP diff --git a/src/environment.cpp b/src/environment.cpp index d35e335cc..d739c3b8a 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -17,7 +17,7 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE #if defined(BOOST_PROCESS_V2_WINDOWS) -error_code process_environment::on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &) +error_code process_environment::do_setup(windows::default_launcher & launcher) { if (!unicode_env.empty() && !ec) { @@ -30,7 +30,7 @@ error_code process_environment::on_setup(windows::default_launcher & launcher, c #else -error_code process_environment::on_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) +error_code process_environment::do_setup(posix::default_launcher & launcher) { launcher.env = env.data(); return error_code{}; diff --git a/src/windows/default_launcher.cpp b/src/windows/default_launcher.cpp index f52c86b9e..6df368fcd 100644 --- a/src/windows/default_launcher.cpp +++ b/src/windows/default_launcher.cpp @@ -24,54 +24,72 @@ namespace windows if (ws.empty()) return 2u; // just quotes - constexpr static auto space = L' '; constexpr static auto quote = L'"'; + const auto needs_quotes = ws.find_first_of(L" \t") != basic_string_view::npos; - const auto has_space = ws.find(space) != basic_string_view::npos; - const auto quoted = (ws.front() == quote) && (ws.back() == quote); - const auto needs_escape = has_space && !quoted ; - - if (!needs_escape) - return ws.size(); - else - return ws.size() + std::count(ws.begin(), ws.end(), quote) + 2u; + std::size_t needed_escapes = 0u; + for (auto itr = ws.begin(); itr != ws.end(); itr ++) + { + if (*itr == quote) + needed_escapes++; + else if (*itr == L'\\') + { + auto nx = std::next(itr); + if (nx != ws.end() && *nx == L'"') + needed_escapes ++; + else if (nx == ws.end()) + needed_escapes ++; + } + } + + return ws.size() + needed_escapes + (needs_quotes ? 2u : 0u); } std::size_t default_launcher::escape_argv_string(wchar_t * itr, std::size_t max_size, basic_string_view ws) { - const auto sz = escaped_argv_length(ws); + constexpr static auto quote = L'"'; + const auto needs_quotes = ws.find_first_of(L" \t") != basic_string_view::npos; + const auto needed_escapes = std::count(ws.begin(), ws.end(), quote); + + const auto sz = ws.size() + needed_escapes + (needs_quotes ? 2u : 0u); + if (sz > max_size) return 0u; + if (ws.empty()) { - itr[0] = L'"'; - itr[1] = L'"'; + itr[0] = quote; + itr[1] = quote; return 2u; } - const auto has_space = ws.find(L' ') != basic_string_view::npos; - const auto quoted = (ws.front() == L'"') && (ws.back() == L'"'); - const auto needs_escape = has_space && !quoted; - - if (!needs_escape) - return std::copy(ws.begin(), ws.end(), itr) - itr; - - if (sz < (2u + ws.size())) - return 0u; - const auto end = itr + sz; const auto begin = itr; - *(itr ++) = L'"'; - for (auto wc : ws) + if (needs_quotes) + *(itr++) = quote; + + for (auto it = ws.begin(); it != ws.end(); it ++) { - if (wc == L'"') + if (*it == quote) // makes it \" *(itr++) = L'\\'; - *(itr++) = wc; + + if (*it == L'\\') // \" needs to become \\\" + { + auto nx = std::next(it); + if (nx != ws.end() && *nx == L'"') + *(itr++) = L'\\'; + else if (nx == ws.end()) + *(itr++) = L'\\'; + + } + + *(itr++) = *it; } - *(itr ++) = L'"'; + if (needs_quotes) + *(itr++) = quote; return itr - begin; } @@ -108,13 +126,18 @@ namespace windows auto tl = get_thread_attribute_list(ec); if (ec) return; + + auto itr = std::unique(inherited_handles.begin(), inherited_handles.end()); + auto size = std::distance(inherited_handles.begin(), itr); + if (!::UpdateProcThreadAttribute( tl, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, - inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), nullptr, nullptr)) + inherited_handles.data(), size * sizeof(HANDLE), nullptr, nullptr)) BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); } } BOOST_PROCESS_V2_END_NAMESPACE -#endif \ No newline at end of file +#endif + diff --git a/test/v2/CMakeLists.txt b/test/v2/CMakeLists.txt index 7d6a5c655..001e77e64 100644 --- a/test/v2/CMakeLists.txt +++ b/test/v2/CMakeLists.txt @@ -1,18 +1,12 @@ enable_testing() -add_library(boost_process_v2_test_impl test_impl.cpp) -target_compile_definitions(boost_process_v2_test_impl PUBLIC -DBOOST_PROCESS_V2_SEPARATE_COMPILATION=1) - -if (WIN32) - target_compile_definitions(boost_process_v2_test_impl PUBLIC WIN32_LEAN_AND_MEAN=1) - target_link_libraries(boost_process_v2_test_impl Boost::process Boost::unit_test_framework Boost::process Ntdll) -else() - target_link_libraries(boost_process_v2_test_impl Boost::process Boost::unit_test_framework Boost::process) -endif() - function(boost_process_v2_standalone_test name) - add_executable(boost_process_v2_${name} ${name}.cpp) - target_link_libraries(boost_process_v2_${name} Boost::process Boost::system Boost::filesystem boost_process_v2_test_impl) + add_executable(boost_process_v2_${name} ${name}.cpp test_impl.cpp) + target_link_libraries(boost_process_v2_${name} PUBLIC Boost::process Boost::system Boost::filesystem Boost::unit_test_framework ) + if (WIN32) + target_compile_definitions(boost_process_v2_${name} PUBLIC WIN32_LEAN_AND_MEAN=1) + target_link_libraries(boost_process_v2_${name} PUBLIC Ntdll) + endif() add_test(NAME boost_process_v2_${name} COMMAND $ ) endfunction() @@ -27,13 +21,16 @@ target_link_libraries(boost_process_v2_test_target PUBLIC Boost::process Boost:: function(boost_process_v2_test_with_target name) add_executable(boost_process_v2_${name} ${name}.cpp) - target_link_libraries(boost_process_v2_${name} Boost::process Boost::system Boost::filesystem boost_process_v2_test_impl) + target_link_libraries(boost_process_v2_${name} PUBLIC Boost::process Boost::system Boost::filesystem boost_process_v2_test_impl) + if (WIN32) + target_compile_definitions(boost_process_v2_${name} PUBLIC WIN32_LEAN_AND_MEAN=1) + target_link_libraries(boost_process_v2_${name} PUBLIC Ntdll) + endif() add_dependencies(boost_process_v2_${name} boost_process_v2_test_target) add_test(NAME boost_process_v2_${name} COMMAND $ -- $) - endfunction() boost_process_v2_test_with_target(process) -boost_process_v2_test_with_target(ext) \ No newline at end of file +boost_process_v2_test_with_target(ext) diff --git a/test/v2/Jamfile.jam b/test/v2/Jamfile.jam index 20aaf06fd..e38452ce6 100644 --- a/test/v2/Jamfile.jam +++ b/test/v2/Jamfile.jam @@ -33,7 +33,6 @@ project : requirements NT,cw:ws2_32 NT,gcc:ws2_32 NT,gcc:Bcrypt - BOOST_PROCESS_V2_SEPARATE_COMPILATION=1 /boost/test//included ; diff --git a/test/v2/process.cpp b/test/v2/process.cpp index cd814ae4e..14d3ec801 100644 --- a/test/v2/process.cpp +++ b/test/v2/process.cpp @@ -245,6 +245,83 @@ BOOST_AUTO_TEST_CASE(print_args_out) } +BOOST_AUTO_TEST_CASE(print_args_spec_out) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + fprintf(stderr, "print_args_spec_out\n"); + + bpv::process proc(ctx, pth, {"print-args", "&foo", "&", "", "\"\"", "\\\"", "|bar", "\"", "#foobar"}, + bpv::process_stdio{/*in*/{},/*out*/wp, /*err*/ nullptr}); + BOOST_CHECK(proc.running()); + + wp.close(); + asio::streambuf st; + std::istream is{&st}; + bpv::error_code ec; + + auto sz = asio::read(rp, st, ec); + while (ec == asio::error::interrupted) + sz += asio::read(rp, st, ec); + + BOOST_CHECK_NE(sz, 0u); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + + std::string line; + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("&foo", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("&", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("\"\"", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("\\\"", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("|bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("\"", line); + + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("#foobar", line); + + + proc.wait(); + BOOST_CHECK(proc.exit_code() == 0); +} + + BOOST_AUTO_TEST_CASE(print_args_err) { using boost::unit_test::framework::master_test_suite; @@ -791,5 +868,70 @@ BOOST_AUTO_TEST_CASE(async_terminate_code) #endif +BOOST_AUTO_TEST_CASE(print_args_combined) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + bpv::process proc(ctx, pth, {"print-args", "bar", "foo"}, bpv::process_stdio{/*in*/{}, /*.out= */ wp, /* .err=*/ wp}); + wp.close(); + + asio::streambuf st; + std::istream is{&st}; + bpv::error_code ec; + + auto sz = asio::read(rp, st, ec); + while (ec == asio::error::interrupted) + sz += asio::read(rp, st, ec); + + BOOST_CHECK_NE(sz , 0u); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + + std::string line; + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line ); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line ); + + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + + proc.wait(); + BOOST_CHECK_EQUAL(proc.exit_code(), 0); +} + BOOST_AUTO_TEST_SUITE_END();