2
0
mirror of https://github.com/boostorg/process.git synced 2026-01-20 04:42:24 +00:00

Compare commits

..

27 Commits

Author SHA1 Message Date
Klemens Morgenstern
2a41d0a0dc batch file execution is forbidden by default 2025-10-21 21:42:14 -07:00
Klemens Morgenstern
dc00bf81d6 Fixes args & inherited handles. 2025-10-21 21:41:11 -07:00
Klemens Morgenstern
47b5c3c191 [win] Added escaping for \ followed by space 2025-10-21 21:41:11 -07:00
Klemens Morgenstern
60affa362c Reworked arg handling on windows (v2) 2025-10-21 21:41:11 -07:00
Klemens Morgenstern
aa40c138ed Windows arg escape is handling internal quotes. 2025-10-21 21:41:11 -07:00
Klemens Morgenstern
c5986d7f57 Added test for combined stdout/stderr
Addresses #522.
2025-10-21 21:41:11 -07:00
Klemens Morgenstern
1e572e1756 Added more docs about pipes & process_stdio.
Closes #522.
2025-10-21 15:30:13 +08:00
Konvicka Filip
635c226066 Fix: corrected empty double quotes being added to cmd.exe /c on Windows with bp::shell and bp::args (caused by PR #256) 2025-10-21 14:46:50 +08:00
EelisVP
773ac747d5 Fix 'unused variable' warning 2025-10-21 10:58:45 +08:00
Klemens Morgenstern
322f581d1f Increased version range to 3.31 2025-10-09 23:38:18 +08:00
Klemens Morgenstern
9df0ee099b target_link_Libraries signature fix 2025-10-09 21:25:32 +08:00
Klemens Morgenstern
01c9a5b60f removed v2/test_impl target 2025-10-09 09:29:37 +08:00
Klemens Morgenstern
ed7099687a Removed filesystem::path from ABI
Closes #516.
2025-10-06 13:17:13 +08:00
chn
7fb5049feb fix typo in stdio move constructor 2025-10-06 12:49:38 +08:00
Klemens Morgenstern
02e14e8fff Fixed cmake for tests
Closes #515
2025-10-06 12:44:33 +08:00
Klemens Morgenstern
484d6e7a90 added test for special args to tests 2025-10-06 12:44:33 +08:00
Alexander Grund
878a9e6ee9 Fix required CMake version 2025-10-03 10:12:02 +08:00
Klemens Morgenstern
1bfe21baa3 Failed pidfd_open causes an exception
Closes #513.
2025-09-07 17:44:47 +08:00
Klemens Morgenstern
1765cd57bb Launchers use _exit instead of exit on error.
Closes #514
2025-09-07 17:44:47 +08:00
Alexander Grund
7212471b57 Update Link to regression test matrix in README 2025-09-07 17:30:30 +08:00
Klemens Morgenstern
5597aa0055 changed env example for windows wchar_t. 2025-07-01 18:59:19 +08:00
Klemens Morgenstern
31d6b5c9f8 process_handle.async_wait accepts ref to exit_code
Closes #503.
2025-07-01 18:59:19 +08:00
Klemens Morgenstern
f5c83eb9c5 wait checks the error code first.
See #499.
2025-07-01 18:59:19 +08:00
Klemens Morgenstern
224e3cf9aa [windows] fixed escaping of path without args.
Closes #501. Credit to @melak47 for the solution.
2025-07-01 18:59:19 +08:00
Klemens Morgenstern
bd450f9831 fixed wrong comment/doc using .stderr
Closes #500
2025-06-26 23:17:08 +08:00
Klemens Morgenstern
c72650df30 added example for modifying inherited environment. 2025-06-26 22:48:16 +08:00
Klemens Morgenstern
0c3c79672f added v1.hpp. 2025-06-26 22:14:16 +08:00
26 changed files with 474 additions and 133 deletions

View File

@@ -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)

View File

@@ -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) |

View File

@@ -29,3 +29,24 @@ The subprocess environment assignment follows the same constraints:
----
include::../example/env.cpp[tag=subprocess_env]
----
== Inheriting an environment
The current environment can be obtained by calling `environment::current` which returns
a forward range of `environment::key_value_pair_view`.
.example/env.cpp:48-54
[source,cpp,ident=0]
----
include::../example/env.cpp[tag=vector_env]
----
Alternatively you can use a map container for the environment.
.example/env.cpp:61-68
[source,cpp,ident=0]
----
include::../example/env.cpp[tag=map_env]
----

View File

@@ -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<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, native_exit_code_type))
// Asynchronously wait for the process to exit and assign the native exit-code to the reference.
// The exit_status can indicate that a process has already be waited for, e.g. when terminate is called.
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler = net::default_completion_token_t<executor_type>>
auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>());
auto async_wait(native_exit_code_type &exit_status, WaitHandler &&handler = net::default_completion_token_t<executor_type>());
};
----

View File

@@ -26,7 +26,7 @@ in later version of C++.
asio::io_context ctx;
/// C++17
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr});
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.err=nullptr});
/// C++11 & C++14
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});
----
@@ -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;
};
----
----

View File

@@ -42,4 +42,30 @@ int main()
process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env));
// end::subprocess_env[]
}
{
// tag::vector_env[]
asio::io_context ctx;
auto c = environment::current();
// we need to use a value, since windows needs wchar_t.
std::vector<environment::key_value_pair> 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));
// end::vector_env[]
}
{
// tag::map_env[]
asio::io_context ctx;
std::unordered_map<environment::key, environment::value> my_env;
for (const auto & kv : environment::current())
if (kv.key().string() != "SECRET")
my_env[kv.key()] = kv.value();
auto exe = environment::find_executable("g++", my_env);
process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));
// end::map_env[]
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2024 Klemens D. Morgenstern
//
// 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_PROCESS_V1_HPP
#define BOOST_PROCESS_V1_HPP
#include <boost/process/v1/args.hpp>
#include <boost/process/v1/async.hpp>
#include <boost/process/v1/async_system.hpp>
#include <boost/process/v1/group.hpp>
#include <boost/process/v1/child.hpp>
#include <boost/process/v1/cmd.hpp>
#include <boost/process/v1/env.hpp>
#include <boost/process/v1/environment.hpp>
#include <boost/process/v1/error.hpp>
#include <boost/process/v1/exe.hpp>
#include <boost/process/v1/group.hpp>
#include <boost/process/v1/handles.hpp>
#include <boost/process/v1/io.hpp>
#include <boost/process/v1/pipe.hpp>
#include <boost/process/v1/shell.hpp>
#include <boost/process/v1/search_path.hpp>
#include <boost/process/v1/spawn.hpp>
#include <boost/process/v1/system.hpp>
#include <boost/process/v1/start_dir.hpp>
#endif //BOOST_PROCESS_V1_HPP

View File

@@ -162,7 +162,9 @@ struct exe_cmd_init : handler_base_ext
}
static exe_cmd_init<Char> exe_args_shell(string_type && exe, std::vector<string_type> && args)
{
std::vector<string_type> args_ = {c_arg(Char()), std::move(exe)};
std::vector<string_type> 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());

View File

@@ -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)
@@ -286,24 +288,21 @@ struct basic_process_handle_fd
{
net::posix::basic_descriptor<Executor> &descriptor;
pid_type pid_;
native_exit_code_type & exit_code;
template<typename Self>
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 +312,39 @@ struct basic_process_handle_fd
struct completer
{
error_code ec;
native_exit_code_type code;
typename std::decay<Self>::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<typename Self>
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<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, native_exit_code_type))
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler = net::default_completion_token_t<executor_type>>
auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{descriptor_, pid_}, handler, descriptor_))
auto async_wait(native_exit_code_type & exit_code,
WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{descriptor_, pid_, exit_code}, handler, descriptor_))
{
return net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{descriptor_, pid_}, handler, descriptor_);
return net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{descriptor_, pid_, exit_code}, handler, descriptor_);
}
};

View File

@@ -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<typename Self>
@@ -344,11 +345,10 @@ struct basic_process_handle_fd_or_signal
template<typename Self>
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<typename Self>
void operator()(Self &&self, native_exit_code_type code, error_code ec)
{
self.complete(ec, code);
self.complete(ec);
}
};
public:
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, int))
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler = net::default_completion_token_t<executor_type>>
auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
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<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{descriptor_, signal_set_, pid_, exit_code}, handler, descriptor_))
{
return net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{descriptor_, signal_set_, pid_}, handler, descriptor_);
return net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{descriptor_, signal_set_, pid_, exit_code}, handler, descriptor_);
}
};
}

View File

@@ -308,7 +308,8 @@ struct basic_process_handle_signal
net::basic_signal_set<Executor> &handle;
pid_type pid_;
native_exit_code_type & exit_code;
template<typename Self>
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<typename Self>
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<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, int))
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler = net::default_completion_token_t<executor_type>>
auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
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<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{signal_set_, pid_, exit_code}, handler, signal_set_))
{
return net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{signal_set_, pid_}, handler, signal_set_);
return net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{signal_set_, pid_, exit_code}, handler, signal_set_);
}
};

View File

@@ -275,7 +275,7 @@ struct basic_process_handle_win
struct async_wait_op_
{
handle_type &handle;
native_exit_code_type & exit_code;
template<typename Self>
void operator()(Self &&self)
{
@@ -296,24 +296,24 @@ struct basic_process_handle_win
template<typename Self>
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<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, native_exit_code_type))
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler = net::default_completion_token_t<executor_type>>
auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{handle_}, handler, handle_))
auto async_wait(native_exit_code_type & exit_code,
WaitHandler &&handler = net::default_completion_token_t<executor_type>())
-> decltype(net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{handle_, exit_code}, handler, handle_))
{
return net::async_compose<WaitHandler, void(error_code, native_exit_code_type)>(
async_wait_op_{handle_}, handler, handle_
return net::async_compose<WaitHandler, void(error_code)>(
async_wait_op_{handle_, exit_code}, handler, handle_
);
}
};

View File

@@ -44,9 +44,14 @@ std::basic_string<CharOut, Traits, Allocator> conv_string(
if (ec)
detail::throw_error(ec, "size_as_utf8");
std::basic_string<CharOut, Traits, Allocator> 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<CharOut, Traits, Allocator> conv_string(
std::basic_string<CharOut, Traits, Allocator> 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");

View File

@@ -1762,9 +1762,12 @@ struct process_environment
std::vector<environment::key_value_pair> env_buffer;
std::vector<wchar_t> 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<environment::key_value_pair> env_buffer;
std::vector<const char *> env;

View File

@@ -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<Executor>{exec};
}
#if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK)

View File

@@ -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<Executor>{exec};
}
#if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK)

View File

@@ -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<Executor>{exec};
}
#if !defined(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK)

View File

@@ -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<Executor>{exec};
}
ctx.notify_fork(net::execution_context::fork_parent);

View File

@@ -20,10 +20,12 @@
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/any_io_executor.hpp>
#include <boost/asio/dispatch.hpp>
#include <asio/post.hpp>
#include <utility>
#else
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/post.hpp>
#include <boost/core/exchange.hpp>
#endif
@@ -300,6 +302,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 +318,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)
@@ -355,25 +361,22 @@ private:
}
};
net::post(handle.get_executor(), completer{static_cast<int>(res), std::move(self)});
net::dispatch(
net::get_associated_immediate_executor(handle, handle.get_executor()),
completer{static_cast<int>(res), std::move(self)});
}
else
handle.async_wait(std::move(self));
handle.async_wait(res, std::move(self));
}
template<typename Self>
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;
else if (ec == boost::system::errc::no_child_process)
std::move(self).complete({}, evaluate_exit_code(res));
else
std::move(self).complete(ec, evaluate_exit_code(code));
std::move(self).complete(ec, evaluate_exit_code(res));
}
}
};

View File

@@ -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;
@@ -308,7 +308,7 @@ typedef process_io_binding<STDERR_FILENO> process_error_binding;
* * @code {.cpp}
* asio::io_context ctx;
* /// C++17
* v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr});
* v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.err=nullptr});
* /// C++11 & C++14
* v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});
* stdin ^ ^ stderr

View File

@@ -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<Executor, Inits...>
{
if (!allow_batch_files && ((executable.extension() == ".bat") || (executable.extension() == ".cmd")))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, ERROR_ACCESS_DENIED, system_category());
return basic_process<Executor>(exec);
}
auto command_line = this->build_command_line(executable, std::forward<Args>(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<wchar_t> ws);
@@ -395,6 +403,7 @@ struct default_launcher
{
return detail::conv_string<wchar_t>(arg.data(), arg.size());
});
return build_command_line_impl(pt, argw, L"");
}
@@ -403,8 +412,14 @@ 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()));
if (!buffer.empty())
escape_argv_string(&buffer.front(), buffer.size(), pt.native());
return buffer;
}
return build_command_line_impl(pt, args, *std::begin(args));
}
@@ -433,4 +448,4 @@ BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP

View File

@@ -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{};

View File

@@ -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<wchar_t>::npos;
const auto has_space = ws.find(space) != basic_string_view<wchar_t>::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<wchar_t> 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<wchar_t>::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<wchar_t>::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
#endif

View File

@@ -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 $<TARGET_FILE:boost_process_v2_${name}> )
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 $<TARGET_FILE:boost_process_v2_${name}>
-- $<TARGET_FILE:boost_process_v2_test_target>)
endfunction()
boost_process_v2_test_with_target(process)
boost_process_v2_test_with_target(ext)
boost_process_v2_test_with_target(ext)

View File

@@ -33,7 +33,6 @@ project : requirements
<os>NT,<toolset>cw:<library>ws2_32
<os>NT,<toolset>gcc:<library>ws2_32
<os>NT,<toolset>gcc:<library>Bcrypt
<define>BOOST_PROCESS_V2_SEPARATE_COMPILATION=1
<library>/boost/test//included
;

View File

@@ -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;
@@ -768,8 +845,93 @@ 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
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();