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

Compare commits

..

6 Commits

Author SHA1 Message Date
Klemens Morgenstern
0ca663c826 Set ENOTSUP when PROC_PPID_ONLY is undefined
closes #452
2025-01-26 21:49:25 +08:00
Klemens Morgenstern
a2d2753aa8 zombie process fixes
closes #445, #447
2025-01-13 09:31:48 +08:00
Klemens Morgenstern
1b5272a9e1 changed cwd comparison to equivalent. 2025-01-13 08:36:36 +08:00
Jonas Greitemann
e842a060f1 implement move operations for process_io_binding and delete copy operations
This makes the test added in the previous commit pass.
2025-01-13 08:13:33 +08:00
Jonas Greitemann
4bb842564f add a (failing) test for process_stdio move semantics
This test currently fails on POSIX, as `process_io_binding` does not
explicitly implement move operations but holds on to a file descriptor
which exhibits reference semantics and has a non-trivial destructor.

The same test should pass on Windows as the Windows implementation makes
use of `unique_ptr` which gives it correct move semantics by virtue of
the rule of zero.
2025-01-13 08:13:33 +08:00
Jonas Greitemann
359820097b add test case for initializing process_stdio with complementary pipe ends
`process_stdio` holds on to the handles for `in`/`out`/`err`. In case of
pipes, `in` needs the handles of a `readable_pipe`, whereas `out` and
`err` need the handles of `writable_pipe`s. So far, the tests all create
a new pair using `connect_pipe`, pass the "correct" end into
`process_stdio`, and use the other end to interface with the process.

However, `process_io_binding` also supports construction from the
complementary pipe types, i.e., constructing `in`'s binding from a
`writable_pipe` and `out`/`err`'s bindings from `readable_pipe`s.
In this case, the constructor will create the corresponding pipe itself
and keep ownership of it. This mode was thus far not tested.
2025-01-13 08:13:33 +08:00
17 changed files with 204 additions and 343 deletions

View File

@@ -18,8 +18,6 @@
#else
#if defined(BOOST_PROCESS_V2_PDFORK)
#include <boost/process/v2/posix/pdfork_launcher.hpp>
#elif defined(BOOST_PROCESS_V2_PIPEFORK)
#include <boost/process/v2/posix/pipe_fork_launcher.hpp>
#else
#include <boost/process/v2/posix/default_launcher.hpp>
#endif
@@ -50,8 +48,6 @@ typedef windows::default_launcher default_process_launcher;
#else
#if defined(BOOST_PROCESS_V2_PDFORK)
typedef posix::pdfork_launcher default_process_launcher;
#elif defined(BOOST_PROCESS_V2_PIPEFORK)
typedef posix::pipe_fork_launcher default_process_launcher;
#else
typedef posix::default_launcher default_process_launcher;
#endif

View File

@@ -165,7 +165,7 @@ BOOST_PROCESS_V2_END_NAMESPACE
#include <sys/syscall.h>
#if defined(SYS_pidfd_open) && !defined(BOOST_PROCESS_V2_DISABLE_PIDFD_OPEN)
#if defined(SYS_pidfd_open)
#define BOOST_PROCESS_V2_PIDFD_OPEN 1
#define BOOST_PROCESS_V2_HAS_PROCESS_HANDLE 1
#endif

View File

@@ -255,7 +255,7 @@ struct basic_process_handle_fd
ec.clear();
exit_code = code;
}
return false;
return false;
}
bool running(native_exit_code_type &exit_code)

View File

@@ -18,27 +18,19 @@
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/any_io_executor.hpp>
#include <asio/append.hpp>
#include <asio/associated_immediate_executor.hpp>
#include <asio/compose.hpp>
#include <asio/dispatch.hpp>
#include <asio/posix/basic_stream_descriptor.hpp>
#include <asio/post.hpp>
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
#include <asio/signal_set.hpp>
#endif
#include <asio/windows/signal_set.hpp>
#else
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_immediate_executor.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/posix/basic_stream_descriptor.hpp>
#include <boost/asio/post.hpp>
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
#include <boost/asio/signal_set.hpp>
#endif
#endif
BOOST_PROCESS_V2_BEGIN_NAMESPACE
@@ -53,7 +45,7 @@ struct basic_process_handle_fd_or_signal
typedef Executor executor_type;
executor_type get_executor()
{ return descriptor_.get_executor(); }
{ return signal_set_.get_executor(); }
/// Rebinds the process_handle to another executor.
template<typename Executor1>
@@ -285,13 +277,14 @@ struct basic_process_handle_fd_or_signal
int res = ::waitpid(pid_, &code, WNOHANG);
if (res == -1)
ec = get_last_error();
else if (res == 0)
else
ec.clear();
if (process_is_running(res))
return true;
else
{
ec.clear();
exit_code = code;
}
return false;
}
@@ -318,19 +311,12 @@ struct basic_process_handle_fd_or_signal
struct basic_process_handle_fd_or_signal;
pid_type pid_ = -1;
net::posix::basic_stream_descriptor<Executor> descriptor_;
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
net::basic_signal_set<Executor> signal_set_{descriptor_.get_executor(), SIGCHLD};
#else
int signal_set_;
#endif
struct async_wait_op_
{
net::posix::basic_descriptor<Executor> &descriptor;
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
net::basic_signal_set<Executor> &handle;
#else
int dummy;
#endif
pid_type pid_;
bool needs_post = true;
@@ -357,41 +343,35 @@ struct basic_process_handle_fd_or_signal
if (!ec && (wait_res == 0))
{
needs_post = false;
if (descriptor.is_open())
{
needs_post = false;
descriptor.async_wait(
net::posix::descriptor_base::wait_read,
std::move(self));
return;
}
descriptor.async_wait(
net::posix::descriptor_base::wait_read,
std::move(self));
else
{
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
needs_post = false;
handle.async_wait(std::move(self));
return;
#else
BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::operation_not_supported);
#endif
}
handle.async_wait(std::move(self));
return;
}
struct completer
{
error_code ec;
native_exit_code_type code;
typename std::decay<Self>::type self;
void operator()()
{
self.complete(ec, code);
}
};
const auto exec = self.get_executor();
completer cpl{ec, exit_code, std::move(self)};
if (needs_post)
{
auto exec = net::get_associated_immediate_executor(self, descriptor.get_executor());
net::dispatch(exec, net::append(std::move(self), exit_code, ec));
}
net::post(exec, std::move(cpl));
else
{
auto exec = net::get_associated_executor(self);
net::dispatch(exec, net::append(std::move(self), exit_code, ec));
}
}
template<typename Self>
void operator()(Self &&self, native_exit_code_type code, error_code ec)
{
self.complete(ec, code);
net::dispatch(exec, std::move(cpl));
}
};
public:

View File

@@ -17,25 +17,17 @@
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/any_io_executor.hpp>
#include <asio/append.hpp>
#include <asio/associated_immediate_executor.hpp>
#include <asio/compose.hpp>
#include <asio/dispatch.hpp>
#include <asio/post.hpp>
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
#include <asio/signal_set.hpp>
#endif
#else
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_immediate_executor.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/post.hpp>
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
#include <boost/asio/signal_set.hpp>
#endif
#endif
BOOST_PROCESS_V2_BEGIN_NAMESPACE
@@ -94,14 +86,9 @@ struct basic_process_handle_signal
basic_process_handle_signal& operator=(basic_process_handle_signal && handle)
{
pid_ = handle.id();
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
signal_set_.~basic_signal_set();
using ss = net::basic_signal_set<Executor>;
new (&signal_set_) ss(handle.get_executor(), SIGCHLD);
#else
signal_set_.executor = handle.signal_set_.executor;
#endif
handle.pid_ = -1;
return *this;
}
@@ -257,13 +244,11 @@ struct basic_process_handle_signal
int res = ::waitpid(pid_, &code, WNOHANG);
if (res == -1)
ec = get_last_error();
else if (res == 0)
if (res == 0)
return true;
else
{
ec.clear();
exit_code = code;
}
return false;
}
@@ -288,24 +273,10 @@ struct basic_process_handle_signal
template<typename>
friend struct basic_process_handle_signal;
pid_type pid_ = -1;
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
net::basic_signal_set<Executor> signal_set_;
#else
struct signal_set_dummy_
{
signal_set_dummy_(signal_set_dummy_ &&) = default;
signal_set_dummy_(const signal_set_dummy_ &) = default;
Executor executor;
using executor_type = Executor;
executor_type get_executor() {return executor;}
signal_set_dummy_(Executor executor, int) : executor(std::move(executor)) {}
};
signal_set_dummy_ signal_set_;
#endif
struct async_wait_op_
{
#if !defined(BOOST_PROCESS_V2_DISABLE_SIGNALSET)
net::basic_signal_set<Executor> &handle;
pid_type pid_;
@@ -344,25 +315,20 @@ struct basic_process_handle_signal
return;
}
struct completer
{
error_code ec;
native_exit_code_type code;
typename std::decay<Self>::type self;
void operator()()
{
self.complete(ec, code);
}
};
const auto exec = self.get_executor();
net::dispatch(exec, net::append(std::move(self), exit_code, ec));
}
#else
signal_set_dummy_ dummy_;
pid_t pid;
template<typename Self>
void operator()(Self &&self)
{
auto exec = net::get_associated_immediate_executor(self, dummy_.get_executor());
error_code ec;
BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::operation_not_supported);
net::dispatch(exec, net::append(std::move(self), native_exit_code_type(), ec));
}
#endif
template<typename Self>
void operator()(Self &&self, native_exit_code_type code, error_code ec)
{
self.complete(ec, code);
net::dispatch(exec, completer{ec, exit_code, std::move(self)});
}
};
public:

View File

@@ -26,6 +26,8 @@
#endif
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -96,7 +98,7 @@ template<typename Launcher, typename Init>
inline auto invoke_on_error(Launcher & launcher, const filesystem::path &executable,
const char * const * (&cmd_line),
const error_code & ec, Init && init, derived && )
-> decltype(init.on_error(launcher, ec, executable, cmd_line, ec))
-> decltype(init.on_error(launcher, executable, cmd_line, ec))
{
init.on_error(launcher, executable, cmd_line, ec);
}
@@ -160,7 +162,7 @@ template<typename Launcher, typename Init>
inline auto invoke_on_fork_error(Launcher & launcher, const filesystem::path &executable,
const char * const * (&cmd_line),
const error_code & ec, Init && init, derived && )
-> decltype(init.on_fork_error(launcher, ec, executable, cmd_line, ec))
-> decltype(init.on_fork_error(launcher, executable, cmd_line, ec))
{
init.on_fork_error(launcher, executable, cmd_line, ec);
}
@@ -182,41 +184,6 @@ inline void on_fork_error(Launcher & launcher, const filesystem::path &executabl
on_fork_error(launcher, executable, cmd_line, ec, inits...);
}
template<typename Launcher, typename Init>
inline void invoke_on_fork_success(Launcher & /*launcher*/, const filesystem::path &/*executable*/,
const char * const * (&/*cmd_line*/),
Init && /*init*/, base && )
{
}
template<typename Launcher, typename Init>
inline auto invoke_on_fork_success(Launcher & launcher, const filesystem::path &executable,
const char * const * (&cmd_line),
Init && init, derived && )
-> decltype(init.on_fork_success(launcher, executable, cmd_line))
{
init.on_fork_success(launcher, executable, cmd_line);
}
template<typename Launcher>
inline void on_fork_success(Launcher & /*launcher*/, const filesystem::path &/*executable*/,
const char * const * (&/*cmd_line*/))
{
}
template<typename Launcher, typename Init1, typename ... Inits>
inline void on_fork_success(Launcher & launcher, const filesystem::path &executable,
const char * const * (&cmd_line),
Init1 && init1, Inits && ... inits)
{
invoke_on_fork_success(launcher, executable, cmd_line, init1, derived{});
on_fork_success(launcher, executable, cmd_line, inits...);
}
template<typename Launcher, typename Init>
inline error_code invoke_on_exec_setup(Launcher & /*launcher*/, const filesystem::path &/*executable*/,
const char * const * (&/*cmd_line*/),
@@ -266,7 +233,7 @@ template<typename Launcher, typename Init>
inline auto invoke_on_exec_error(Launcher & launcher, const filesystem::path &executable,
const char * const * (&cmd_line),
const error_code & ec, Init && init, derived && )
-> decltype(init.on_exec_error(launcher, ec, executable, cmd_line, ec))
-> decltype(init.on_exec_error(launcher, executable, cmd_line, ec))
{
init.on_exec_error(launcher, executable, cmd_line, ec);
}
@@ -440,6 +407,7 @@ struct default_launcher
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
do { ::waitpid(pid, nullptr, 0); } while (errno == EINTR);
return basic_process<Executor>{exec};
}
}

View File

@@ -124,6 +124,7 @@ struct fork_and_forget_launcher : default_launcher
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
do { ::waitpid(pid, nullptr, 0); } while (errno == EINTR);
return basic_process<Executor>{exec};
}
}

View File

@@ -161,6 +161,7 @@ struct pdfork_launcher : default_launcher
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
do { ::waitpid(pid, nullptr, 0); } while (errno == EINTR);
return basic_process<Executor>{exec};
}
}

View File

@@ -1,185 +0,0 @@
// Copyright (c) 2022 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_V2_POSIX_PIPE_FORK_LAUNCHER_HPP
#define BOOST_PROCESS_V2_POSIX_PIPE_FORK_LAUNCHER_HPP
#include <boost/process/v2/posix/default_launcher.hpp>
#include <unistd.h>
BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace posix
{
/// A launcher using `pipe_fork`. Default on FreeBSD
struct pipe_fork_launcher : default_launcher
{
/// The file descriptor of the subprocess. Set after fork.
pipe_fork_launcher() = default;
template<typename ExecutionContext, typename Args, typename ... Inits>
auto operator()(ExecutionContext & context,
const typename std::enable_if<is_convertible<
ExecutionContext&, net::execution_context&>::value,
filesystem::path >::type & executable,
Args && args,
Inits && ... inits ) -> basic_process<typename ExecutionContext::executor_type>
{
error_code ec;
auto proc = (*this)(context, ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
v2::detail::throw_error(ec, "pipe_fork_launcher");
return proc;
}
template<typename ExecutionContext, typename Args, typename ... Inits>
auto operator()(ExecutionContext & context,
error_code & ec,
const typename std::enable_if<is_convertible<
ExecutionContext&, net::execution_context&>::value,
filesystem::path >::type & executable,
Args && args,
Inits && ... inits ) -> basic_process<typename ExecutionContext::executor_type>
{
return (*this)(context.get_executor(), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
}
template<typename Executor, typename Args, typename ... Inits>
auto operator()(Executor exec,
const typename std::enable_if<
net::execution::is_executor<Executor>::value ||
net::is_executor<Executor>::value,
filesystem::path >::type & executable,
Args && args,
Inits && ... inits ) -> basic_process<Executor>
{
error_code ec;
auto proc = (*this)(std::move(exec), ec, executable, std::forward<Args>(args), std::forward<Inits>(inits)...);
if (ec)
v2::detail::throw_error(ec, "pipe_fork_launcher");
return proc;
}
template<typename Executor, typename Args, typename ... Inits>
auto operator()(Executor exec,
error_code & ec,
const typename std::enable_if<
net::execution::is_executor<Executor>::value ||
net::is_executor<Executor>::value,
filesystem::path >::type & executable,
Args && args,
Inits && ... inits ) -> basic_process<Executor>
{
auto argv = this->build_argv_(executable, std::forward<Args>(args));
int fd = -1;
{
pipe_guard pg, pg_wait;
if (::pipe(pg.p))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category());
return basic_process<Executor>{exec};
}
if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category());
return basic_process<Executor>{exec};
}
if (::pipe(pg_wait.p))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category());
return basic_process<Executor>{exec};
}
if (::fcntl(pg_wait.p[1], F_SETFD, FD_CLOEXEC))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category());
return basic_process<Executor>{exec};
}
ec = detail::on_setup(*this, executable, argv, inits ...);
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
return basic_process<Executor>(exec);
}
fd_whitelist.push_back(pg.p[1]);
fd_whitelist.push_back(pg_wait.p[1]);
auto & ctx = net::query(
exec, net::execution::context);
ctx.notify_fork(net::execution_context::fork_prepare);
pid = ::fork();
if (pid == -1)
{
ctx.notify_fork(net::execution_context::fork_parent);
detail::on_fork_error(*this, executable, argv, ec, inits...);
detail::on_error(*this, executable, argv, ec, inits...);
BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category());
return basic_process<Executor>{exec};
}
else if (pid == 0)
{
ctx.notify_fork(net::execution_context::fork_child);
::close(pg.p[0]);
ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
{
close_all_fds(ec);
}
if (!ec)
::execve(executable.c_str(), const_cast<char * const *>(argv), const_cast<char * const *>(env));
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);
return basic_process<Executor>{exec};
}
ctx.notify_fork(net::execution_context::fork_parent);
::close(pg.p[1]);
pg.p[1] = -1;
::close(pg_wait.p[1]);
pg_wait.p[1] = -1;
int child_error{0};
int count = -1;
while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == -1)
{
int err = errno;
if ((err != EAGAIN) && (err != EINTR))
{
BOOST_PROCESS_V2_ASSIGN_EC(ec, err, system_category());
break;
}
}
if (count != 0)
BOOST_PROCESS_V2_ASSIGN_EC(ec, child_error, system_category());
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
return basic_process<Executor>{exec};
}
std::swap(fd, pg_wait.p[0]);
}
basic_process<Executor> proc(exec, pid, fd);
detail::on_success(*this, executable, argv, ec, inits...);
return proc;
}
};
}
BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_POSIX_PIPE_FORK_LAUNCHER_HPP

View File

@@ -121,6 +121,7 @@ struct vfork_launcher : default_launcher
if (ec)
{
detail::on_error(*this, executable, argv, ec, inits...);
do { ::waitpid(pid, nullptr, 0); } while (errno == EINTR);
return basic_process<Executor>{exec};
}

View File

@@ -13,7 +13,7 @@
#if defined(BOOST_PROCESS_V2_PIDFD_OPEN)
#include <boost/process/v2/detail/process_handle_fd.hpp>
#elif defined(BOOST_PROCESS_V2_PDFORK) || defined(BOOST_PROCESS_V2_PIPEFORK)
#elif defined(BOOST_PROCESS_V2_PDFORK)
#include <boost/process/v2/detail/process_handle_fd_or_signal.hpp>
#else
// with asio support we could use EVFILT_PROC:NOTE_EXIT as well.
@@ -107,9 +107,9 @@ struct basic_process_handle
void request_exit()
/// Unconditionally terminates the process and stores the exit code in exit_status.
void terminate(native_exit_code_type &exit_status, error_code &ec);
void terminate(native_exit_code_type &exit_status, error_code &ec);\
/// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
void terminate(native_exit_code_type &exit_status);
void terminate(native_exit_code_type &exit_status);/
/// Checks if the current process is running.
/**If it has already completed, it assigns the exit code to `exit_code`.
@@ -137,7 +137,7 @@ using basic_process_handle = detail::basic_process_handle_win<Executor>;
#if defined(BOOST_PROCESS_V2_PIDFD_OPEN)
template<typename Executor = net::any_io_executor>
using basic_process_handle = detail::basic_process_handle_fd<Executor>;
#elif defined(BOOST_PROCESS_V2_PDFORK) || defined(BOOST_PROCESS_V2_PIPEFORK)
#elif defined(BOOST_PROCESS_V2_PDFORK) || defined(BOOST_PROCESS_V2_PIPE_LAUNCHER)
template<typename Executor = net::any_io_executor>
using basic_process_handle = detail::basic_process_handle_fd_or_signal<Executor>;
#else

View File

@@ -166,6 +166,31 @@ struct process_io_binding
}
process_io_binding() = default;
process_io_binding(const process_io_binding &) = delete;
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)
{
other.fd = target;
other.fd_needs_closing = false;
other.ec = {};
}
process_io_binding & operator=(process_io_binding && other) noexcept
{
if (fd_needs_closing)
::close(fd);
fd = other.fd;
fd_needs_closing = other.fd_needs_closing;
ec = other.ec;
other.fd = target;
other.fd_needs_closing = false;
other.ec = {};
return *this;
}
template<typename Stream>
process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle()) * = nullptr)

View File

@@ -97,7 +97,7 @@ inline void invoke_on_error(Launcher & /*launcher*/, const filesystem::path &/*e
template<typename Launcher, typename Init>
inline auto invoke_on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line,
const error_code & ec, Init && init, derived && )
-> decltype(init.on_error(launcher, ec, executable, cmd_line, ec))
-> decltype(init.on_error(launcher, executable, cmd_line, ec))
{
init.on_error(launcher, executable, cmd_line, ec);
}

View File

@@ -178,6 +178,8 @@ pid_type parent_pid(pid_type pid, error_code & ec)
std::vector<pid_type> child_pids(pid_type pid, error_code & ec)
{
std::vector<pid_type> vec;
#if defined(PROC_PPID_ONLY)
vec.resize(proc_listpids(PROC_PPID_ONLY, (uint32_t)pid, nullptr, 0) / sizeof(pid_type));
const auto sz = proc_listpids(PROC_PPID_ONLY, (uint32_t)pid, &vec[0], sizeof(pid_type) * vec.size());
if (sz < 0)
@@ -186,6 +188,9 @@ std::vector<pid_type> child_pids(pid_type pid, error_code & ec)
return {};
}
vec.resize(sz);
#else
BOOST_PROCESS_V2_ASSIGN_EC(ec, ENOTSUP, system_category());
#endif
return vec;
}

View File

@@ -11,7 +11,6 @@
#include <boost/process/v2/pid.hpp>
#include <boost/process/v2/process.hpp>
#include <boost/process/v2/start_dir.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(ext)
@@ -111,10 +110,10 @@ BOOST_AUTO_TEST_CASE(test_cwd_exe)
boost::asio::io_context ctx;
bp2::process proc(ctx, pth, {"sleep", "10000"},
bp2::process_start_dir{tmp});
auto tt = bp2::ext::cwd(proc.handle()).string();
if (tt.back() == '\\')
tt.pop_back();
BOOST_CHECK_EQUAL(tt, tmp);
auto tt = bp2::ext::cwd(proc.handle());
BOOST_CHECK_MESSAGE(bp2::filesystem::equivalent(tmp, tt), tmp << " == " << tt);
bp2::error_code ec;
bp2::filesystem::remove(tmp, ec);
}

View File

@@ -7,8 +7,6 @@
#include <boost/process/v2/pid.hpp>
#include <boost/process/v2/process.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>

View File

@@ -30,9 +30,11 @@
#include <boost/test/unit_test.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/cancel_after.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/streambuf.hpp>
@@ -328,6 +330,74 @@ BOOST_AUTO_TEST_CASE(echo_file)
BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code());
}
BOOST_AUTO_TEST_CASE(stdio_creates_complementary_pipes)
{
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};
// Pipes intentionally not connected. `process_stdio` will create pipes
// complementing both of these and retains ownership of those pipes.
bpv::process proc(ctx, pth, {"echo"}, bpv::process_stdio{/*.in=*/wp, /*.out=*/rp, /*.err=*/nullptr});
asio::write(wp, asio::buffer("foo", 3));
asio::write(wp, asio::buffer("bar", 3));
wp.close();
bpv::error_code ec;
std::string out;
auto sz = asio::read(rp, asio::dynamic_buffer(out), ec);
while (ec == asio::error::interrupted)
sz += asio::read(rp, asio::dynamic_buffer(out), ec);
BOOST_CHECK_EQUAL(sz, 6u);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_EQUAL(out, "foobar");
proc.wait();
BOOST_CHECK(proc.exit_code() == 0);
}
BOOST_AUTO_TEST_CASE(stdio_move_semantics)
{
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};
auto make_stdio = [&]() -> bpv::process_stdio {
bpv::process_stdio stdio{};
stdio.in = wp;
stdio.out = rp;
stdio.err = nullptr;
// intentionally pessimizing move, preventing NRVO
return std::move(stdio);
};
bpv::process proc(ctx, pth, {"echo"}, make_stdio());
bpv::error_code ec;
asio::write(wp, asio::buffer("foobar", 6), ec);
BOOST_CHECK_MESSAGE(!ec, ec.message());
wp.close();
std::string out;
auto sz = asio::read(rp, asio::dynamic_buffer(out), ec);
while (ec == asio::error::interrupted)
sz += asio::read(rp, asio::dynamic_buffer(out), ec);
BOOST_CHECK_EQUAL(sz, 6u);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_EQUAL(out, "foobar");
proc.wait();
BOOST_CHECK(proc.exit_code() == 0);
}
BOOST_AUTO_TEST_CASE(print_same_cwd)
{
using boost::unit_test::framework::master_test_suite;
@@ -655,7 +725,7 @@ BOOST_AUTO_TEST_CASE(async_cancel_wait)
proc.async_wait(asio::cancel_after(std::chrono::milliseconds(100),
[&](boost::system::error_code ec, int)
{
BOOST_CHECK_EQUAL(ec, asio::error::operation_aborted);
BOOST_CHECK(ec == asio::error::operation_aborted);
BOOST_CHECK(proc.running());
if (proc.running())
proc.terminate();
@@ -664,6 +734,42 @@ BOOST_AUTO_TEST_CASE(async_cancel_wait)
ctx.run();
}
#if defined(BOOST_POSIX_API)
struct capture_pid
{
pid_t &pid;
template<typename Launcher>
void on_error(Launcher &launcher, const bpv::filesystem::path& executable,
const char * const * (&/*cmd_line*/), const bpv::error_code & ec)
{
BOOST_REQUIRE(!bpv::filesystem::exists(executable));
this->pid = launcher.pid;
}
};
BOOST_AUTO_TEST_CASE(no_zombie)
{
asio::io_context ctx;
using boost::unit_test::framework::master_test_suite;
const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]);
pid_t res{-1};
boost::system::error_code ec;
bpv::default_process_launcher()(ctx, ec, "/send/more/cops", std::vector<std::string>{}, capture_pid{res});
BOOST_CHECK(ec == boost::system::errc::no_such_file_or_directory);
BOOST_REQUIRE(res != -1);
BOOST_CHECK(res != 0);
auto r = waitpid(res, nullptr, 0);
BOOST_CHECK(r < 0);
BOOST_CHECK_EQUAL(errno, ECHILD);
}
#endif
BOOST_AUTO_TEST_SUITE_END();