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

Compare commits

...

7 Commits

Author SHA1 Message Date
Klemens
7832cb6af3 Shell(posix) fixes. 2022-09-02 18:43:35 +08:00
Klemens Morgenstern
68f4c50be9 Exeuction support for shell. 2022-09-02 18:25:40 +08:00
Klemens Morgenstern
cd226a7616 Implemented shell on windows. 2022-09-02 17:05:49 +08:00
Klemens Morgenstern
4243ce72f8 Windows bugfixes. 2022-09-02 16:46:45 +08:00
Klemens
9065833e61 Added shell class. 2022-08-31 23:54:22 +08:00
Klemens
df33c1ad7b Fixed unsafe post-fork allocs for fd_whitelist. 2022-08-31 15:35:41 +08:00
Klemens
bbabea30dd Added reaping child for execve error, closes #265. 2022-08-31 15:35:41 +08:00
20 changed files with 447 additions and 96 deletions

View File

@@ -444,6 +444,8 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_)
}
if (_ec)
{
//if an error occured we need to reap the child process
::waitpid(this->pid, nullptr, WNOHANG);
boost::fusion::for_each(seq, call_on_error(*this, _ec));
return child();
}
@@ -537,6 +539,7 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::true_)
if (_ec)
{
::waitpid(this->pid, nullptr, WNOHANG);
boost::fusion::for_each(seq, call_on_error(*this, _ec));
return child();
}

View File

@@ -77,14 +77,14 @@ static BOOL CALLBACK enum_window(HWND hwnd, LPARAM param)
auto data = reinterpret_cast<enum_windows_data_t*>(param);
DWORD pid{0u};
GetWindowThreadProcessId(hwnd, &pid);
if (pid != data->pid)
return TRUE;
LRESULT res = ::SendMessageW(hwnd, WM_CLOSE, 0, 0);
if (!res)
if (res)
data->ec = detail::get_last_error();
return res != 0;
return res == 0;
}
void request_exit_(pid_type pid_, error_code & ec)

View File

@@ -87,17 +87,16 @@ struct basic_process_handle_win
{
}
basic_process_handle_win(basic_process_handle_win && handle)
basic_process_handle_win(basic_process_handle_win && handle)
: pid_(handle.id()), handle_(std::move(handle.handle_))
{
pid_ = handle.id();
handle_ = std::move(handle.handle_);
handle.pid_ = static_cast<DWORD>(-1);
}
basic_process_handle_win& operator=(basic_process_handle_win && handle)
{
pid_ = handle.pid_;
handle_ = std::mopve(handle_))
handle_ = std::move(handle.handle_);
handle.pid_ = static_cast<DWORD>(-1);
return *this;
}
@@ -166,7 +165,6 @@ struct basic_process_handle_win
{
if (!detail::check_pid_(pid_, ec))
return;
detail::request_exit_(pid_, ec);
}

View File

@@ -979,7 +979,8 @@ struct key_value_pair
const std::pair<Key, Value> & kv/*,
typename std::enable_if<std::is_constructible<struct key, Key >::value &&
std::is_constructible<struct value, Value>::value
>::type = 0*/) : value_(((struct key)(kv.first)).string() + equality_sign + ((struct value)(kv.second)).string())
>::type = 0*/) : value_(((struct key)(kv.first)).basic_string<char_type, traits_type>() + equality_sign
+ ((struct value)(kv.second)).basic_string<char_type, traits_type>())
{}
key_value_pair(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type * raw)
@@ -1045,6 +1046,7 @@ struct key_value_pair
operator string_type() const {return native();}
operator string_view_type() const {return native_view();}
operator typename string_view_type::string_view_type() const {return native_view();}
operator key_value_pair_view() const {return native_view();}
int compare( const key_value_pair& p ) const noexcept
@@ -1432,8 +1434,9 @@ auto find_key(Environment & env, key_view ky)
template<typename Environment = current_view>
inline filesystem::path home(Environment && env = current())
{
#if defined(ASIO_WINDOWS)
return detail::find_key(env, L"HOMEDRIVE") + detail::find_key(env, L"HOMEPATH").native_string();
#if defined(BOOST_PROCESS_V2_WINDOWS)
return detail::find_key(env, L"HOMEDRIVE").native_string()
+ detail::find_key(env, L"HOMEPATH").native_string();
#else
return detail::find_key(env, "HOME").native_string();
#endif
@@ -1468,7 +1471,7 @@ inline BOOST_PROCESS_V2_NAMESPACE::filesystem::path find_executable(
// first check if it has the extension already
BOOST_PROCESS_V2_NAMESPACE::filesystem::path full_nm(name);
BOOST_PROCESS_V2_NAMESPACE::filesystem::path pp(pp_view.begin(), pp_view.end());
auto p = pp / nm;
auto p = pp / full_nm;
error_code ec;
if (detail::is_executable(p, ec) && !ec)
@@ -1695,67 +1698,52 @@ struct process_environment
template<typename Args>
void build_env(Args && args, string_view rs)
static
std::vector<wchar_t> build_env(Args && args,
typename std::enable_if<
std::is_convertible<
decltype(*std::begin(std::declval<Args>())),
wcstring_ref>::value>::type * = nullptr)
{
std::size_t length = 0u;
for (string_view v : args)
length += detail::size_as_wide(v.data(), v.size(), ec) + 1u;
std::vector<wchar_t> res;
std::size_t sz = 1;
for (wcstring_ref cs : std::forward<Args>(args))
sz =+ cs.size() + 1;
res.reserve(sz);
for (wcstring_ref cs : std::forward<Args>(args))
res.insert(res.end(), cs.begin(), std::next(cs.end()));
if (ec)
return;
length ++ ;
unicode_env.resize(length);
auto itr = &unicode_env.front();
for (string_view v : args)
{
itr += detail::convert_to_wide(
v.data(), v.size(),
itr, &unicode_env.back() - itr,
ec);
if (ec)
break;
*(itr++) = '\0';
}
unicode_env.back() = '\0';
}
template<typename Args>
void build_env(Args && args, wstring_view rs)
{
std::size_t length = 0u;
for (const auto & v : std::forward<Args>(args))
length += v.size() + 1u;
length ++ ;
unicode_env.resize(length);
auto itr = unicode_env.begin();
for (wstring_view v : args )
{
itr = std::copy(v.begin(), v.end(), itr);
*(itr++) = L'\0';
}
unicode_env.back() = L'\0';
res.push_back(L'\0');
return res;
}
template<typename Args>
std::vector<wchar_t> build_env(Args && args,
typename std::enable_if<
!std::is_convertible<
decltype(*std::begin(std::declval<Args>())),
wcstring_ref>::value>::type * = nullptr)
{
for (auto && arg: std::forward<Args>(args))
env_buffer.emplace_back(arg);
return build_env(env_buffer);
}
process_environment(std::initializer_list<string_view> sv) { build_env(sv, ""); }
process_environment(std::initializer_list<wstring_view> sv) { build_env(sv, L""); }
process_environment(std::initializer_list<string_view> sv) : unicode_env{build_env(sv, "")} {}
process_environment(std::initializer_list<wstring_view> sv) : unicode_env{build_env(sv, L"")} {}
template<typename Args>
process_environment(Args && args)
process_environment(Args && args) : unicode_env{build_env(std::forward<Args>(args))}
{
if (std::begin(args) != std::end(args))
build_env(std::forward<Args>(args), *std::begin(args));
}
error_code error() {return ec;}
error_code ec;
std::vector<environment::key_value_pair> env_buffer;
std::vector<wchar_t> unicode_env;
error_code on_setup(windows::default_launcher & launcher,
const filesystem::path &, const std::wstring &);

View File

@@ -0,0 +1,131 @@
// 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_IMPL_SHELL_IPP
#define BOOST_PROCESS_V2_IMPL_SHELL_IPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/last_error.hpp>
#include <boost/process/v2/detail/throw_error.hpp>
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/error.hpp>
#include <boost/process/v2/shell.hpp>
#if defined(BOOST_PROCESS_V2_WINDOWS)
#include <shellapi.h>
#else
#include <wordexp.h>
#endif
BOOST_PROCESS_V2_BEGIN_NAMESPACE
#if defined(BOOST_PROCESS_V2_WINDOWS)
BOOST_PROCESS_V2_DECL const error_category& get_shell_category()
{
return system_category();
}
#else
struct shell_category_t final : public error_category
{
shell_category_t() : error_category(0xDAF1u) {}
const char* name() const noexcept
{
return "process.v2.utf8";
}
std::string message(int value) const
{
switch (value)
{
case WRDE_BADCHAR:
return "Illegal occurrence of newline or one of |, &, ;, <, >, (, ), {, }.";
case WRDE_BADVAL:
return "An undefined shell variable was referenced, and the WRDE_UNDEF flag told us to consider this an error.";
case WRDE_CMDSUB:
return "Command substitution occurred, and the WRDE_NOCMD flag told us to consider this an error.";
case WRDE_NOSPACE:
return "Out of memory.";
case WRDE_SYNTAX:
return "Shell syntax error, such as unbalanced parentheses or unmatched quotes.";
default:
return "process.v2.wordexp error";
}
}
};
BOOST_PROCESS_V2_DECL const error_category& get_shell_category()
{
static shell_category_t instance;
return instance;
}
#endif
#if defined (BOOST_PROCESS_V2_WINDOWS)
void shell::parse_()
{
argv_ = ::CommandLineToArgvW(input_.c_str(), &argc_);
if (argv_ == nullptr)
detail::throw_last_error();
}
shell::~shell()
{
if (argv_ != nullptr)
LocalFree(argv_);
}
auto shell::args() const-> args_type
{
return input_.c_str();
}
#else
void shell::parse_()
{
wordexp_t we{};
auto cd = wordexp(input_.c_str(), &we, WRDE_NOCMD);
if (cd != 0)
detail::throw_error(error_code(cd, get_shell_category()), "shell::parse");
else
{
argc_ = static_cast<int>(we.we_wordc);
argv_ = we.we_wordv;
reserved_ = static_cast<int>(we.we_offs);
}
}
shell::~shell()
{
if (argv_ != nullptr)
{
wordexp_t we{
.we_wordc = static_cast<std::size_t>(argc_),
.we_wordv = argv_,
.we_offs = static_cast<std::size_t>(reserved_)
};
wordfree(&we);
}
}
auto shell::args() const -> args_type
{
if (argc() == 0)
{
static const char * helper = nullptr;
return &helper;
}
else
return const_cast<const char**>(argv());
}
#endif
BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_IMPL_SHELL_IPP

View File

@@ -91,13 +91,16 @@ struct bind_fd
{
}
error_code on_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *)
{
launcher.fd_whitelist.push_back(target);
}
/// Implementation of the initialization function.
error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *)
{
if (::dup2(fd, target) == -1)
return error_code(errno, system_category());
launcher.fd_whitelist.push_back(target);
return error_code ();
}
};

View File

@@ -378,6 +378,7 @@ struct default_launcher
detail::on_error(*this, executable, argv, ec, inits...);
return basic_process<Executor>(exec);
}
fd_whitelist.push_back(pg.p[1]);
auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query(
exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context);
@@ -399,7 +400,6 @@ struct default_launcher
ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
{
fd_whitelist.push_back(pg.p[1]);
close_all_fds(ec);
}
if (!ec)
@@ -485,6 +485,11 @@ struct default_launcher
return argv_.data();
}
const char * const * build_argv_(const filesystem::path &, const char ** argv)
{
return argv;
}
template<typename Args>
const char * const * build_argv_(const filesystem::path & pt, const Args & args,
typename std::enable_if<

View File

@@ -99,6 +99,7 @@ struct pdfork_launcher : default_launcher
detail::on_error(*this, executable, argv, ec, inits...);
return basic_process<Executor>(exec);
}
fd_whitelist.push_back(pg.p[1]);
auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query(
exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context);
@@ -121,7 +122,6 @@ struct pdfork_launcher : default_launcher
ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
{
fd_whitelist.push_back(pg.p[1]);
close_all_fds(ec);
}
if (!ec)

View File

@@ -19,9 +19,11 @@
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/any_io_executor.hpp>
#include <asio/post.hpp>
#include <utility>
#else
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/post.hpp>
#include <boost/core/exchange.hpp>
#endif
@@ -164,7 +166,7 @@ struct basic_process
typename std::enable_if<
std::is_convertible<ExecutionContext&,
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
: process_handle_(context, pid, native_handle) {}
: process_handle_(context.get_executor(), pid, native_handle) {}
/// Create an invalid handle
template <typename ExecutionContext>
@@ -172,7 +174,7 @@ struct basic_process
typename std::enable_if<
is_convertible<ExecutionContext&,
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
: process_handle_(context) {}
: process_handle_(context.get_executor()) {}
@@ -339,7 +341,7 @@ private:
};
BOOST_PROCESS_V2_ASIO_NAMESPACE::post(handle.get_executor(),
completer{res, std::move(self)});
completer{static_cast<int>(res), std::move(self)});
}
else
handle.async_wait(std::move(self));

View File

@@ -0,0 +1,139 @@
// 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_SHELL_HPP
#define BOOST_PROCESS_V2_SHELL_HPP
#include <boost/core/exchange.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#include <boost/process/v2/detail/throw_error.hpp>
#include <boost/process/v2/environment.hpp>
#include <memory>
#include <string>
BOOST_PROCESS_V2_BEGIN_NAMESPACE
/// Error category used by the shell parser.
extern BOOST_PROCESS_V2_DECL const error_category& get_shell_category();
static const error_category& shell_category = get_shell_category();
/// Utility to parse commands
/** This utility class parses command lines into tokens
* and allows users to executed based on textual inputs.
*
* In v1, this was possible directly when starting a process,
* but has been removed based on the security risks associated with this.
*
* By making the shell parsing explicity, it is encouraged
* that a user runs a sanity check on the executable before launching it.
*
* @par Example
* @code {.cpp}
* asio::io_context ctx;
*
* auto cmd = shell("my-app --help");
* auto exe = cmd.exe();
* check_if_malicious(exe);
*
* process proc{ctx, exe, cmd.args()};
*
* @endcode
*
*
*/
struct shell
{
#if defined(BOOST_PROCESS_V2_WINDOWS)
using char_type = wchar_t;
using args_type = const wchar_t *;
#else
using char_type = char;
using args_type = const char **;
#endif
shell() = default;
template<typename Char, typename Traits>
shell(basic_string_view<Char, Traits> input)
: buffer_(detail::conv_string<char_type>(input.data(), input.size()))
{
parse_();
}
shell(basic_cstring_ref<char_type> input) : input_(input) {parse_();}
shell(basic_string_view<
typename std::conditional<
std::is_same<char_type, char>::value,
wchar_t, char>::type> input) : buffer_(detail::conv_string<char_type>(input.data(), input.size()))
{
parse_();
}
shell(const shell &) = delete;
shell& operator=(const shell &) = delete;
shell(shell && lhs) noexcept
: buffer_(std::move(lhs.buffer_)),
input_(std::move(lhs.input_)),
argc_(boost::exchange(lhs.argc_, 0)),
argv_(boost::exchange(lhs.argv_, nullptr)),
reserved_(boost::exchange(lhs.reserved_, 0))
{
}
shell& operator=(shell && lhs) noexcept
{
shell tmp(std::move(*this));
buffer_ = std::move(lhs.buffer_);
input_ = std::move(lhs.input_);
argc_ = boost::exchange(lhs.argc_, 0);
argv_ = boost::exchange(lhs.argv_, nullptr);
reserved_ = boost::exchange(lhs.reserved_, 0);
return *this;
}
// the length of the parsed shell, including the executable
int argc() const { return argc_; }
char_type** argv() const { return argv_; }
char_type** begin() const {return argv();}
char_type** end() const {return argv() + argc();}
bool empty() const {return argc() == 0;}
std::size_t size() const {return static_cast<std::size_t>(argc()); }
/// Native representation of the arguments to be used - excluding the executable
BOOST_PROCESS_V2_DECL args_type args() const;
template<typename Environment = environment::current_view>
filesystem::path exe(Environment && env = environment::current()) const
{
if (argc() == 0)
return "";
else
return environment::find_executable(0[argv()], std::forward<Environment>(env));
}
BOOST_PROCESS_V2_DECL ~shell();
private:
BOOST_PROCESS_V2_DECL void parse_();
// storage in case we need a conversion
std::basic_string<char_type> buffer_;
basic_cstring_ref<char_type> input_{buffer_};
// impl details
int argc_ = 0;
char_type ** argv_ = nullptr;
int reserved_ = 0;
};
BOOST_PROCESS_V2_END_NAMESPACE
#if defined(BOOST_PROCESS_V2_HEADER_ONLY)
#include <boost/process/v2/impl/shell.ipp>
#endif
#endif //BOOST_PROCESS_V2_ERROR_HPP

View File

@@ -22,5 +22,6 @@
#include <boost/process/v2/impl/default_launcher.ipp>
#include <boost/process/v2/impl/environment.ipp>
#include <boost/process/v2/impl/process_handle.ipp>
#include <boost/process/v2/impl/shell.ipp>
#endif //BOOST_PROCESS_V2_SRC_HPP

View File

@@ -12,7 +12,7 @@
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/default_launcher.hpp>
#include <cstddef>
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/connect_pipe.hpp>
#else
@@ -52,7 +52,7 @@ struct handle_closer
DWORD flags{0xFFFFFFFFu};
};
template<DWORD Io>
template<DWORD Target>
struct process_io_binding
{
HANDLE prepare()
@@ -62,7 +62,7 @@ struct process_io_binding
return hh;
}
std::unique_ptr<void, handle_closer> h{::GetStdHandle(Io), false};
std::unique_ptr<void, handle_closer> h{::GetStdHandle(Target), false};
static DWORD get_flags(HANDLE h)
{
@@ -82,10 +82,11 @@ struct process_io_binding
process_io_binding(FILE * f) : process_io_binding(_get_osfhandle(_fileno(f))) {}
process_io_binding(HANDLE h) : h{h, get_flags(h)} {}
process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("NUL")) {}
process_io_binding(const filesystem::path & pth)
template<typename T, typename = typename std::enable_if<std::is_same<T, filesystem::path>::value>::type>
process_io_binding(const T & pth)
: h(::CreateFileW(
pth.c_str(),
Io == STD_INPUT_HANDLE ? GENERIC_READ : GENERIC_WRITE,
Target == STD_INPUT_HANDLE ? GENERIC_READ : GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_ALWAYS,
@@ -101,11 +102,13 @@ struct process_io_binding
typename std::enable_if<Target != STD_INPUT_HANDLE, Executor*>::type = 0)
{
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
error_code ec;
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
if (ec)
return ;
detail::throw_error(ec, "create_pipe");
h = std::unique_ptr<void, handle_closer>{p[1], true};
readable_pipe.assign(p[0], ec);
readable_pipe.assign(p[0]);
}
@@ -114,11 +117,13 @@ struct process_io_binding
typename std::enable_if<Target == STD_INPUT_HANDLE, Executor*>::type = 0)
{
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
error_code ec;
BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
if (ec)
return ;
detail::throw_error(ec, "create_pipe");
h = std::unique_ptr<void, handle_closer>{p[0], true};
writable_pipe.assign(p[1], ec);
writable_pipe.assign(p[1]);
}
};
@@ -289,11 +294,6 @@ struct process_stdio
if (::dup2(err.fd, err.target) == -1)
return error_code(errno, system_category());
launcher.fd_whitelist.push_back(STDIN_FILENO);
launcher.fd_whitelist.push_back(STDOUT_FILENO);
launcher.fd_whitelist.push_back(STDERR_FILENO);
return error_code {};
};
#endif

View File

@@ -398,6 +398,11 @@ struct default_launcher
return build_command_line_impl(pt, args, *std::begin(args));
}
static std::wstring build_command_line(const filesystem::path & pt, const wchar_t * args)
{
return args;
}
};

View File

@@ -113,3 +113,17 @@ BOOST_AUTO_TEST_CASE(ignore_error)
BOOST_CHECK_NO_THROW(bp::child c("doesnt-exit", bp::ignore_error));
}
}
BOOST_AUTO_TEST_CASE(not_found)
{
try
{
bp::child c("doesnt-exit");
BOOST_CHECK_MESSAGE(false, "Should throw");
}
catch( bp::process_error & se)
{
BOOST_CHECK(se.code());
}
}

View File

@@ -15,6 +15,7 @@ boost_process_v2_standalone_test(utf8)
boost_process_v2_standalone_test(cstring_ref)
boost_process_v2_standalone_test(pid)
boost_process_v2_standalone_test(environment)
boost_process_v2_standalone_test(shell)
add_library(boost_process_v2_header_test header_1.cpp header_2.cpp)
target_link_libraries(boost_process_v2_header_test PUBLIC Boost::process)

View File

@@ -53,6 +53,7 @@ test-suite standalone :
[ run cstring_ref.cpp test_impl ]
[ run environment.cpp test_impl ]
[ run pid.cpp test_impl ]
[ run shell.cpp test_impl ]
;
test-suite with_target :

View File

@@ -95,9 +95,9 @@ BOOST_AUTO_TEST_CASE(environment)
#else
std::unordered_map<std::wstring, std::wstring> custom_env =
{
L"HOME", L"/home/byzantium",
L"HOMEDRIVE", L"X:",
L"HOMEPATH", L"\\users\\theodora"
{L"HOME", L"/home/byzantium"},
{L"HOMEDRIVE", L"X:"},
{L"HOMEPATH", L"\\users\\theodora"}
};
std::vector<std::wstring> custom_env2 =
@@ -106,8 +106,8 @@ BOOST_AUTO_TEST_CASE(environment)
{L"HOMEDRIVE=X:"},
{L"HOMEPATH=\\users\\theodora"}
};
BOOST_CHECK_EQUAL(bpe::home(custom_env), L"X:\\Users\\theodora");
BOOST_CHECK_EQUAL(bpe::home(custom_env2), L"X:\\Users\\theodora");
BOOST_CHECK_EQUAL(bpe::home(custom_env), "X:\\users\\theodora");
BOOST_CHECK_EQUAL(bpe::home(custom_env2), "X:\\users\\theodora");
#endif

View File

@@ -164,6 +164,7 @@ BOOST_AUTO_TEST_CASE(request_exit)
, asio::windows::show_window_minimized_not_active
#endif
);
BOOST_CHECK(proc.running());
std::this_thread::sleep_for(std::chrono::milliseconds(250));
proc.request_exit();
proc.wait();
@@ -188,6 +189,8 @@ void trim_end(std::string & str)
{
auto itr = std::find_if(str.rbegin(), str.rend(), &std::char_traits<char>::not_eof);
str.erase(itr.base(), str.end());
if (!str.empty() && str.back() == '\r')
str.pop_back();
}
BOOST_AUTO_TEST_CASE(print_args_out)
@@ -356,16 +359,16 @@ BOOST_AUTO_TEST_CASE(popen)
// default CWD
bpv::popen proc(ctx, pth, {"echo"});
asio::write(proc, asio::buffer("FOOBAR"));
auto written = asio::write(proc, asio::buffer("FOOBAR"));
proc.get_stdin().close();
std::string res;
boost::system::error_code ec;
std::size_t n = asio::read(proc, asio::dynamic_buffer(res), ec);
res.resize(n - 1);
BOOST_CHECK_EQUAL(ec, asio::error::eof);
BOOST_CHECK(ec == asio::error::eof || ec == asio::error::broken_pipe);
BOOST_REQUIRE_GE(n, 1);
// remove EOF
res.pop_back();
BOOST_CHECK_EQUAL(res, "FOOBAR");
proc.wait();
@@ -429,7 +432,7 @@ std::string read_env(const char * name, Inits && ... inits)
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
out.resize(sz);
trim_end(out);
printf("Read env (%ld) %s: '%s'\n", sz, name, out.c_str());
printf("Read env (%ld) %s: '%s'\n", static_cast<long>(sz), name, out.c_str());
proc.wait();
BOOST_CHECK_EQUAL(proc.exit_code(), 0);
@@ -449,12 +452,12 @@ BOOST_AUTO_TEST_CASE(environment)
BOOST_CHECK_EQUAL("FOO-BAR", read_env("FOOBAR", bpv::process_environment{sub_env}));
sub_env.push_back("XYZ=ZYX");
auto itr = std::find_if(sub_env.begin(), sub_env.end(), [](const bpv::environment::key_value_pair & kv) {return kv.key() == "PATH";});
auto itr = std::find_if(sub_env.begin(), sub_env.end(), [](const bpv::environment::key_value_pair & kv) {return kv.key() == bpv::environment::key("PATH");});
path += static_cast<char>(bpv::environment::delimiter);
path += "/bar/foo";
bpv::environment::value pval = itr->value();
pval.push_back("/bar/foo");
*itr = bpv::environment::key_value_pair("PATH", pval);
*itr = bpv::environment::key_value_pair(bpv::environment::key("PATH"), pval);
BOOST_CHECK_EQUAL(path, read_env("PATH", bpv::process_environment{sub_env}));
#if defined(BOOST_PROCESS_V2_WINDOWS)
@@ -462,12 +465,13 @@ BOOST_AUTO_TEST_CASE(environment)
BOOST_CHECK_EQUAL("FOO-BAR", read_env("FOOBAR", bpv::process_environment{L"FOOBAR=FOO-BAR", wpath.c_str()}));
wpath += bpv::environment::delimiter;
wpath += L"C:\\bar\\foo";
BOOST_CHECK_EQUAL(wpath.substr(5), read_env("pATH", bpv::process_environment{wpath.c_str(), std::wstring(L"XYZ=ZYX")}));
BOOST_CHECK_EQUAL(
bpv::detail::conv_string<char>(wpath.c_str() + 5, wpath.size() - 5)
, read_env("pATH", bpv::process_environment{wpath.c_str(), std::wstring(L"XYZ=ZYX")}));
#endif
BOOST_CHECK_EQUAL(read_env("PATH", bpv::process_environment(bpv::environment::current())), ::getenv("PATH"));
}
BOOST_AUTO_TEST_SUITE_END();

56
test/v2/shell.cpp Executable file
View File

@@ -0,0 +1,56 @@
// 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)
// Disable autolinking for unit tests.
#if !defined(BOOST_ALL_NO_LIB)
#define BOOST_ALL_NO_LIB 1
#endif // !defined(BOOST_ALL_NO_LIB)
// Test that header file is self-contained.
#include <boost/process/v2/shell.hpp>
#include <boost/process/v2/process.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/test/unit_test.hpp>
#if defined(BOOST_PROCESS_V2_WINDOWS)
#define STR(Value) L##Value
#define STR_VIEW(Value) boost::process::v2::wcstring_ref(STR(Value))
#else
#define STR(Value) Value
#define STR_VIEW(Value) boost::process::v2::cstring_ref(STR(Value))
#endif
BOOST_AUTO_TEST_CASE(test_shell_parser)
{
using boost::process::v2::shell;
namespace bpv = boost::process::v2;
#if defined(BOOST_PROCESS_V2_POSIX)
BOOST_CHECK_THROW(shell("foo \""), bpv::system_error);
#endif
auto sh = shell(STR("foo bar \"foo bar\""));
BOOST_CHECK(sh.argc() == 3u);
BOOST_CHECK(sh.argv()[0] == STR_VIEW("foo"));
BOOST_CHECK(sh.argv()[1] == STR_VIEW("bar"));
BOOST_CHECK(sh.argv()[2] == STR_VIEW("foo bar"));
#if defined(BOOST_PROCESS_V2_POSIX)
auto raw_shell = "sh -c false";
#else
auto raw_shell = "cmd /c exit 1";
#endif
sh = shell(raw_shell);
auto exe = sh.exe();
BOOST_CHECK(bpv::filesystem::exists(exe));
boost::asio::io_context ctx;
bpv::process proc{ctx, exe, sh.args()};
proc.wait();
BOOST_CHECK_EQUAL(proc.exit_code(), 1);
}

View File

@@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE(creation_flags)
BOOST_CHECK_EQUAL(proc.wait() & ~EXTENDED_STARTUPINFO_PRESENT, 0);
proc = bpv::process{ctx, master_test_suite().argv[1], {"creation-flags"}, bpv::windows::process_creation_flags<STARTF_TITLEISAPPID>()};
BOOST_CHECK(proc);
BOOST_CHECK(proc.running());
BOOST_CHECK_EQUAL(proc.wait() & ~EXTENDED_STARTUPINFO_PRESENT, STARTF_TITLEISAPPID);
}