Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7212471
Update Link to regression test matrix in README
Flamefire Aug 25, 2025
1765cd5
Launchers use _exit instead of exit on error.
klemens-morgenstern Sep 7, 2025
1bfe21b
Failed pidfd_open causes an exception
klemens-morgenstern Sep 7, 2025
878a9e6
Fix required CMake version
Flamefire Oct 1, 2025
484d6e7
added test for special args to tests
klemens-morgenstern Jul 21, 2025
02e14e8
Fixed cmake for tests
klemens-morgenstern Oct 6, 2025
7fb5049
fix typo in stdio move constructor
CHN-beta Jul 29, 2025
ed70996
Removed filesystem::path from ABI
klemens-morgenstern Oct 6, 2025
01c9a5b
removed v2/test_impl target
klemens-morgenstern Oct 9, 2025
9df0ee0
target_link_Libraries signature fix
klemens-morgenstern Oct 9, 2025
322f581
Increased version range to 3.31
klemens-morgenstern Oct 9, 2025
773ac74
Fix 'unused variable' warning
EelisVP Oct 6, 2025
635c226
Fix: corrected empty double quotes being added to cmd.exe /c on Windo…
Aug 22, 2025
1e572e1
Added more docs about pipes & process_stdio.
klemens-morgenstern Oct 21, 2025
c5986d7
Added test for combined stdout/stderr
klemens-morgenstern Oct 21, 2025
aa40c13
Windows arg escape is handling internal quotes.
klemens-morgenstern Oct 21, 2025
60affa3
Reworked arg handling on windows (v2)
klemens-morgenstern Oct 21, 2025
47b5c3c
[win] Added escaping for \ followed by space
klemens-morgenstern Oct 21, 2025
dc00bf8
Fixes args & inherited handles.
klemens-morgenstern Oct 21, 2025
2a41d0a
batch file execution is forbidden by default
klemens-morgenstern Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |



Expand Down
53 changes: 49 additions & 4 deletions doc/reference/stdio.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -52,4 +97,4 @@ struct process_stdio
__implementation_defined__ out;
__implementation_defined__ err;
};
----
----
4 changes: 3 additions & 1 deletion include/boost/process/v1/detail/windows/basic_cmd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
2 changes: 2 additions & 0 deletions include/boost/process/v2/detail/process_handle_fd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions include/boost/process/v2/detail/utf8.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down
15 changes: 12 additions & 3 deletions include/boost/process/v2/environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions include/boost/process/v2/posix/default_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion include/boost/process/v2/posix/pdfork_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion include/boost/process/v2/posix/pipe_fork_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion include/boost/process/v2/stdio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 14 additions & 4 deletions include/boost/process/v2/windows/default_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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...);
Expand Down Expand Up @@ -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);




Expand Down Expand Up @@ -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"");
}

Expand All @@ -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));
}

Expand Down Expand Up @@ -438,4 +448,4 @@ BOOST_PROCESS_V2_END_NAMESPACE



#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
4 changes: 2 additions & 2 deletions src/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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{};
Expand Down
Loading
Loading