2
0
mirror of https://github.com/boostorg/process.git synced 2026-01-19 04:22:15 +00:00

xproc fixes

This commit is contained in:
Klemens Morgenstern
2023-02-06 20:50:00 +08:00
committed by Klemens Morgenstern
parent f1302430cb
commit 9d51e1cd32
19 changed files with 266 additions and 81 deletions

View File

@@ -8,4 +8,6 @@ A special thank you goes to [@http://www.intra2net.com/ Intra2net AG] (especiall
Great thanks also goes to Boris Schaeling, who despite having boost.process rejected, went on to work on it and maintained it up until this day and participated in the development of the current version.
Many Thanks, to [@https://github.com/time-killer-games Samuel Venable] for contributing the v2::ext functionality and all the research that went into it.
[endsect]

View File

@@ -263,7 +263,7 @@ public:
auto st1 = key + ::boost::process::detail::equal_sign<Char>();
while (*p != nullptr)
{
const auto len = std::char_traits<Char>::length(*p);
const int len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) < len)
&& std::equal(st1.begin(), st1.end(), *p))
break;

View File

@@ -101,9 +101,8 @@ struct basic_process_handle_signal
handle.pid_ = -1;
}
pid_type id() const
{ return pid_; }
native_handle_type native_handle() {return pid_;}
pid_type id() const { return pid_; }
native_handle_type native_handle() {return {};}
void terminate_if_running(error_code &)
{

View File

@@ -0,0 +1,16 @@
//
// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net)
//
// 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_EXT_HPP
#define BOOST_PROCESS_V2_EXT_HPP
#include <boost/process/v2/ext/cmd.hpp>
#include <boost/process/v2/ext/cwd.hpp>
#include <boost/process/v2/ext/env.hpp>
#include <boost/process/v2/ext/exe.hpp>
#endif //BOOST_PROCESS_V2_EXT_HPP

View File

@@ -20,7 +20,8 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
/// Get the argument vector from a given pid
/// @{
/// Get the argument vector of another process
BOOST_PROCESS_V2_DECL shell cmd(pid_type pid, error_code & ec);
BOOST_PROCESS_V2_DECL shell cmd(pid_type pid);
@@ -32,15 +33,25 @@ BOOST_PROCESS_V2_DECL shell cmd(HANDLE handle);
template<typename Executor>
BOOST_PROCESS_V2_DECL shell cmd(basic_process_handle<Executor> & handle, error_code & ec)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return cmd(handle.native_handle(), ec);
#else
return cmd(handle.id(), ec);
#endif
}
template<typename Executor>
BOOST_PROCESS_V2_DECL shell cmd(basic_process_handle<Executor> & handle)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return cmd(handle.native_handle());
#else
return cmd(handle.id());
#endif
}
/// @}
} // namespace ext
BOOST_PROCESS_V2_END_NAMESPACE

View File

@@ -15,26 +15,38 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
/// Obtain the current path of a process
/// @{
/// Obtain the current path of another process
BOOST_PROCESS_V2_DECL filesystem::path cwd(pid_type pid, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path cwd(pid_type pid);
template<typename Executor>
BOOST_PROCESS_V2_DECL filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return cwd(handle.native_handle(), ec);
#else
return cwd(handle.id(), ec);
#endif
}
template<typename Executor>
BOOST_PROCESS_V2_DECL filesystem::path cwd(basic_process_handle<Executor> & handle)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return cwd(handle.native_handle());
#else
return cwd(handle.id());
#endif
}
/// @}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path cwd(HANDLE handle);
#endif
template<typename Executor>
BOOST_PROCESS_V2_DECL filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec)
{
return cwd(handle.native_handle(), ec);
}
template<typename Executor>
BOOST_PROCESS_V2_DECL filesystem::path cwd(basic_process_handle<Executor> & handle)
{
return cwd(handle.native_handle());
}
} // namespace ext

View File

@@ -26,6 +26,9 @@ namespace ext
#if defined(BOOST_PROCESS_V2_WINDOWS)
using native_env_handle_type = wchar_t *;
using native_env_iterator = wchar_t *;
#elif defined(__FreeBSD__)
using native_env_handle_type = char **;
using native_env_iterator = char **;
#else
using native_env_handle_type = char *;
using native_env_iterator = char *;
@@ -45,6 +48,7 @@ BOOST_PROCESS_V2_DECL const environment::char_type * dereference(native_env_iter
namespace ext {
/// The view of an environment
struct env_view
{
using native_handle_type = detail::ext::native_env_handle_type;
@@ -105,26 +109,38 @@ struct env_view
detail::ext::native_env_handle_deleter> handle_;
};
/// Get the argument vector from a given pid
/// @{
/// Get the environment of another process.
BOOST_PROCESS_V2_DECL env_view env(pid_type pid, error_code & ec);
BOOST_PROCESS_V2_DECL env_view env(pid_type pid);
template<typename Executor>
BOOST_PROCESS_V2_DECL env_view env(basic_process_handle<Executor> & handle, error_code & ec)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return env(handle.native_handle(), ec);
#else
return env(handle.id(), ec);
#endif
}
template<typename Executor>
BOOST_PROCESS_V2_DECL env_view env(basic_process_handle<Executor> & handle)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return env(handle.native_handle());
#else
return env(handle.id());
#endif
}
/// @}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL env_view env(HANDLE handle);
#endif
template<typename Executor>
BOOST_PROCESS_V2_DECL env_view env(basic_process_handle<Executor> & handle, error_code & ec)
{
return env(handle.native_handle(), ec);
}
template<typename Executor>
BOOST_PROCESS_V2_DECL env_view env(basic_process_handle<Executor> & handle)
{
return env(handle.native_handle());
}
} // namespace ext

View File

@@ -16,27 +16,40 @@ BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace ext {
/// Return the executable path from pid
/// @{
/// Return the executable of another process by pid or handle.
BOOST_PROCESS_V2_DECL filesystem::path exe(pid_type pid, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path exe(pid_type pid);
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle);
#endif
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle, error_code & ec)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return exe(handle.native_handle(), ec);
#else
return exe(handle.id(), ec);
#endif
}
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle)
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
return exe(handle.native_handle());
#else
return exe(handle.id());
#endif
}
///@}
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL filesystem::path exe(HANDLE handle);
#endif
} // namespace ext
BOOST_PROCESS_V2_END_NAMESPACE

View File

@@ -92,13 +92,12 @@ struct make_cmd_shell_
str_lengths += (std::strlen(*c) + 1);
}
// yes, not the greatest solution.
std::string buffer;
res.buffer_.resize(str_lengths);
res.argv_ = new char*[res.argc_ + 1];
res.free_argv_ = +[](int argc, char ** argv) {delete[] argv;};
res.argv_[res.argc_] = nullptr;
auto p = &buffer[sizeof(int) * (res.argc_) + 1];
auto p = &*res.buffer_.begin();
for (int i = 0; i < res.argc_; i++)
{
@@ -179,7 +178,7 @@ shell cmd(boost::process::v2::pid_type pid, boost::system::error_code & ec)
return {};
}
int argc = *reinterpret_cast<int*>(procargs.data());
int argc = *reinterpret_cast<const int*>(procargs.data());
auto itr = procargs.begin() + sizeof(argc);
std::unique_ptr<char*[]> argv{new char*[argc + 1]};
@@ -305,16 +304,9 @@ shell cmd(boost::process::v2::pid_type pid, boost::system::error_code & ec)
ec = detail::get_last_error();
return {};
}
struct free_argv
{
struct procstat * proc_stat;
~free_argv()
{
procstat_freeargv(proc_stat);
}
};
return make_cmd_shell_::clone(cmd);
auto res = make_cmd_shell_::clone(cmd);
procstat_freeargv(proc_stat.get());
return res;
}
#elif defined(__DragonFly__)

View File

@@ -101,10 +101,10 @@ filesystem::path cwd(HANDLE proc)
filesystem::path cwd(boost::process::v2::pid_type pid, boost::system::error_code & ec)
{
proc_vnodepathinfo vpi;
if (proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)) > 0) {
if (proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)) > 0)
return filesystem::canonical(vpi.pvi_cdir.vip_path, ec);
}
ec = detail::get_last_error();
else
ec = detail::get_last_error();
return "";
}

View File

@@ -77,7 +77,7 @@ const environment::char_type * dereference(native_env_iterator iterator)
return iterator;
}
#else
#elif (defined(__APPLE___) || defined(__MACH__))
void native_env_handle_deleter::operator()(native_env_handle_type h) const
{
@@ -97,6 +97,36 @@ native_env_iterator find_end(native_env_iterator nh)
return nh ;
}
const environment::char_type * dereference(native_env_iterator iterator)
{
return iterator;
}
#elif defined(__FreeBSD__)
void native_env_handle_deleter::operator()(native_env_handle_type h) const
{
delete [] h;
}
native_env_iterator next(native_env_iterator nh)
{
return ++nh ;
}
native_env_iterator find_end(native_env_iterator nh)
{
while (*nh != nullptr)
nh++;
return nh ;
}
const environment::char_type * dereference(native_env_iterator iterator)
{
return *iterator;
}
#endif
}
@@ -206,34 +236,35 @@ env_view env(boost::process::v2::pid_type pid, boost::system::error_code & ec)
ec = detail::get_last_error();
return {};
}
std::uint32_t nargs;
memcpy(&nargs, &*procargs.begin(), sizeof(nargs));
char *cp = &*procargs.begin() + sizeof(nargs);
for (; cp < &&*procargs.begin()[size]; cp++) {
if (*cp == '\0') break;
}
for (; cp < &*procargs.end(); cp++)
if (*cp == '\0')
break;
if (cp == &procargs[s]) {
if (cp == &procargs[size])
return {};
}
for (; cp < &&*procargs.begin()[size]; cp++) {
for (; cp < &*procargs.end(); cp++)
if (*cp != '\0') break;
}
if (cp == &&*procargs.begin()[size]) {
if (cp == &*procargs.end())
return {};
}
int i = 0;
char *sp = cp;
std::vector<char> vec;
while ((*sp != '\0' || i < nargs) && sp < &&*procargs.begin()[size]) {
if (i >= nargs) {
while ((*sp != '\0' || i < nargs) && sp < &*procargs.end()) {
if (i >= nargs)
vec.push_back(*sp);
}
sp += 1;
}
@@ -279,6 +310,69 @@ env_view env(boost::process::v2::pid_type pid, boost::system::error_code & ec)
return ev;
}
#elif defined(__FreeBSD__)
env_view env(boost::process::v2::pid_type pid, boost::system::error_code & ec)
{
env_view ev;
unsigned cntp = 0;
procstat *proc_stat = procstat_open_sysctl();
if (proc_stat != nullptr)
{
kinfo_proc *proc_info = procstat_getprocs(proc_stat, KERN_PROC_PID, pid, &cntp);
if (proc_info != nullptr)
{
char **env = procstat_getenvv(proc_stat, proc_info, 0);
if (env != nullptr)
{
auto e = env;
std::size_t n = 0u, len = 0u;
while (e && *e != nullptr)
{
n ++;
len += std::strlen(*e);
e++;
}
std::size_t mem_needed =
// environ - nullptr - strlen + null terminators
(n * sizeof(char*)) + sizeof(char*) + len + n;
char * out = new (std::nothrow) char[mem_needed];
if (out != nullptr)
{
auto eno = reinterpret_cast<char**>(out);
auto eeo = eno;
auto str = out + (n * sizeof(char*)) + sizeof(char*);
e = env;
while (*e != nullptr)
{
auto len = std::strlen(*e) + 1u;
std::memcpy(str, *e, len);
*eno = str;
str += len;
eno ++;
e++;
}
*eno = nullptr;
ev.handle_.reset(eeo);
}
else
ec = detail::get_last_error();
}
procstat_freeprocs(proc_stat, proc_info);
}
else
ec = detail::get_last_error();
procstat_close(proc_stat);
}
else
ec = detail::get_last_error();
return ev;
}
#endif
env_view env(boost::process::v2::pid_type pid)

View File

@@ -154,7 +154,8 @@ filesystem::path exe(boost::process::v2::pid_type pid, boost::system::error_code
strbuff.resize(len);
if (sysctl(mib, 4, &strbuff[0], &len, nullptr, 0) == 0)
{
return filesystem::canonical(strbuff, ec);
strbuff.resize(len - 1);
return filesystem::canonical(strbuff, ec);
}
}

View File

@@ -177,7 +177,7 @@ std::vector<pid_type> child_pids(pid_type pid, boost::system::error_code & ec)
{
std::vector<pid_type> vec;
vec.reserve(proc_listpids(PROC_PPID_ONLY, (uint32_t)pid, nullptr, 0));
if (proc_listpids(PROC_PPID_ONLY, (uint32_t)pid, &proc_info[0], sizeof(pid_type) * cntp))
if (proc_listpids(PROC_PPID_ONLY, (uint32_t)pid, &vec[0], sizeof(pid_type) * vec.size()))
{
ec = detail::get_last_error();
return {};

View File

@@ -111,7 +111,7 @@ void shell::parse_()
shell::~shell()
{
if (argv_ != nullptr && free_argv_)
if (argv_ != nullptr && free_argv_ != nullptr)
free_argv_(argc_, argv_);
}

View File

@@ -215,13 +215,13 @@ struct basic_process
process_handle_.request_exit(ec);
}
/// Send the process a signal requesting it to stop. This may rely on undocmented functions.
/// Send the process a signal requesting it to stop. This may rely on undocumented functions.
void suspend(error_code &ec)
{
process_handle_.suspend(ec);
}
/// Send the process a signal requesting it to stop. This may rely on undocmented functions.
/// Send the process a signal requesting it to stop. This may rely on undocumented functions.
void suspend()
{
error_code ec;
@@ -231,13 +231,13 @@ struct basic_process
}
/// Send the process a signal requesting it to resume. This may rely on undocmented functions.
/// Send the process a signal requesting it to resume. This may rely on undocumented functions.
void resume(error_code &ec)
{
process_handle_.resume(ec);
}
/// Send the process a signal requesting it to resume. This may rely on undocmented functions.
/// Send the process a signal requesting it to resume. This may rely on undocumented functions.
void resume()
{
error_code ec;

View File

@@ -59,13 +59,13 @@ test-suite standalone :
[ run utf8.cpp test_impl ]
[ run cstring_ref.cpp test_impl ]
[ run environment.cpp test_impl ]
[ run pid.cpp test_impl ]
[ run pid.cpp test_impl : : : <target-os>darwin:<build>no ]
[ run shell.cpp test_impl ]
;
test-suite with_target :
[ run process.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
[ run windows.cpp test_impl : --log_level=all --catch_system_errors=no -- : target : <build>no <target-os>windows:<build>yes <target-os>windows:<source>Advapi32 ]
[ run ext.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
[ run ext.cpp test_impl : --log_level=all --catch_system_errors=no -- : target : <target-os>darwin:<build>no ]
;

View File

@@ -22,7 +22,8 @@ BOOST_AUTO_TEST_CASE(test_exe)
namespace bp2 = boost::process::v2;
auto pth = bp2::ext::exe(bp2::current_pid());
BOOST_CHECK(!pth.empty());
BOOST_CHECK_EQUAL(master_test_suite().argv[0], pth.string());
BOOST_CHECK_EQUAL(bp2::filesystem::canonical(master_test_suite().argv[0]).string(),
bp2::filesystem::canonical(pth).string());
}
@@ -30,7 +31,7 @@ BOOST_AUTO_TEST_CASE(test_child_exe)
{
namespace bp2 = boost::process::v2;
using boost::unit_test::framework::master_test_suite;
const auto pth = bp2::filesystem::canonical(master_test_suite().argv[1]);
const auto pth = bp2::filesystem::canonical(master_test_suite().argv[0]);
boost::asio::io_context ctx;
bp2::process proc(ctx, pth, {"sleep", "10000"});
@@ -66,7 +67,7 @@ BOOST_AUTO_TEST_CASE(cmd)
BOOST_AUTO_TEST_CASE(cmd_exe)
{
using boost::unit_test::framework::master_test_suite;
const auto pth = master_test_suite().argv[1];
const auto pth = master_test_suite().argv[0];
namespace bp2 = boost::process::v2;
@@ -101,8 +102,8 @@ BOOST_AUTO_TEST_CASE(test_cwd)
BOOST_AUTO_TEST_CASE(test_cwd_exe)
{
using boost::unit_test::framework::master_test_suite;
const auto pth = master_test_suite().argv[1];
namespace bp2 = boost::process::v2;
const auto pth = bp2::filesystem::absolute(master_test_suite().argv[0]);
auto tmp = bp2::filesystem::temp_directory_path();
@@ -122,6 +123,7 @@ BOOST_AUTO_TEST_CASE(test_env)
namespace bp2 = boost::process::v2;
auto env = bp2::ext::env(bp2::current_pid());
std::size_t e = 0;
for (const auto & kp : bp2::environment::current())
{
@@ -130,15 +132,20 @@ BOOST_AUTO_TEST_CASE(test_env)
{
return kp.key() == kp_.key();
});
BOOST_REQUIRE(itr != env.end());
BOOST_CHECK_EQUAL(kp.value(), (*itr).value());
if (itr != env.end())
{
BOOST_CHECK_EQUAL(kp.value(), (*itr).value());
e++;
}
}
BOOST_CHECK_GT(e, 0u);
}
BOOST_AUTO_TEST_CASE(test_env_exe)
{
using boost::unit_test::framework::master_test_suite;
const auto pth = master_test_suite().argv[1];
const auto pth = master_test_suite().argv[0];
namespace bp2 = boost::process::v2;
auto tmp = bp2::filesystem::temp_directory_path();

View File

@@ -18,6 +18,9 @@ BOOST_AUTO_TEST_CASE(test_pid)
auto all = bp2::all_pids();
auto itr = std::find(all.begin(), all.end(), bp2::current_pid());
#if !defined(__APPLE___) && !defined(__MACH__)
BOOST_CHECK_GT(all.size(), 0u);
BOOST_CHECK(itr != all.end());
std::vector<bp2::pid_type> children, grand_children;
@@ -36,4 +39,6 @@ BOOST_AUTO_TEST_CASE(test_pid)
return (!children.empty() || !grand_children.empty());
};
BOOST_CHECK_NE(grand_child_pids(bp2::root_pid, children, grand_children), false);
#endif
}

View File

@@ -221,6 +221,8 @@ BOOST_AUTO_TEST_CASE(print_args_out)
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());
@@ -267,6 +269,8 @@ BOOST_AUTO_TEST_CASE(print_args_err)
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());
@@ -320,6 +324,9 @@ BOOST_AUTO_TEST_CASE(echo_file)
bpv::error_code ec;
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(sz != 0);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_MESSAGE(out == test_data, out);
@@ -344,6 +351,9 @@ BOOST_AUTO_TEST_CASE(print_same_cwd)
bpv::error_code ec;
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(sz != 0);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == bpv::filesystem::current_path(),
@@ -373,6 +383,9 @@ BOOST_AUTO_TEST_CASE(popen)
std::string res;
boost::system::error_code ec;
std::size_t n = asio::read(proc, asio::dynamic_buffer(res), ec);
while (ec == asio::error::interrupted)
n += asio::read(rp, asio::dynamic_buffer(res), ec);
BOOST_CHECK_MESSAGE(ec == asio::error::eof || ec == asio::error::broken_pipe, ec.message());
BOOST_REQUIRE_GE(n, 1u);
// remove EOF
@@ -406,6 +419,9 @@ BOOST_AUTO_TEST_CASE(print_other_cwd)
bpv::error_code ec;
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(sz != 0);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == target,
@@ -436,7 +452,7 @@ std::string read_env(const char * name, Inits && ... inits)
std::string out;
bpv::error_code ec;
auto sz = asio::read(rp, asio::dynamic_buffer(out), ec);
auto sz = asio::read(rp, asio::dynamic_buffer(out), ec);
while (ec == asio::error::interrupted)
sz += asio::read(rp, asio::dynamic_buffer(out), ec);
@@ -534,6 +550,7 @@ BOOST_AUTO_TEST_CASE(bind_launcher)
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(sz != 0);
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == target,