Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

boost.process

Quickstart

A process needs four things to be launched:

  • an asio execution_context / executor

  • a path to an executable

  • a list of arguments

  • a variadic set of initializers

example/quickstart.cpp:13-17
// process(asio::any_io_executor, filesystem::path, range<string> args, AdditionalInitializers...)
process proc(ctx.get_executor(), (1)
             "/usr/bin/cp", (2)
             {"source.txt", "target.txt"} (3)
); (4)
1 The executor for the process handle
2 The Path to the executable
3 The argument list in the form of an std::initializer_list.
4 Not additional initializers

The started process can then be awaited or terminated.

Lifetime

If the process handle goes out of scope, it will terminate the subprocess. You can prevent this, by calling proc.detach(); do however note that this can lead to zombie processes.

A process that completed will deliver an exit-code, which can be obtained by calling .exit_code on the exited process and which is also returned from .wait().

example/quickstart.cpp:22-23
process proc(ctx, "/bin/ls", {});
assert(proc.wait() == 0);

The normal exit-code is what the subprocess returned from main; posix will however add additional information about the process. This is called the native_exit_code.

The .running() function can be used to detect if the process is still active.

Signalling the subprocess

The parent process can signal the subprocess demanding certain actions.

.terminate will cause the subprocess to exit immediately (SIGKILL on posix). This is the only reliable & portable way to end a subprocess.

example/quickstart.cpp:28-29
process proc(ctx, "/bin/totally-not-a-virus", {});
proc.terminate();

.request_exit will ask the subprocess to shutdown (SIGTERM on posix), which the subprocess might ignore.

example/quickstart.cpp:34-36
    process proc(ctx, "/bin/bash", {});
    proc.request_exit();
    proc.wait();

.interrupt will send an SIGINT to the subprocess, which a subprocess might interpret as a signal for shutdown.

interrupt requires the initializer windows::create_new_process_group to be set on windows
example/quickstart.cpp:41-43
    process proc(ctx, "/usr/bin/addr2line", {});
    proc.interrupt();
    proc.wait();

Process v2 provides execute and async_execute functions that can be used for managed executions.

example/quickstart.cpp:48
    assert(execute(process(ctx, "/bin/ls", {})) == 0);

The async version supports cancellation and will forward cancellation types as follows:

  • asio::cancellation_type::total → interrupt

  • asio::cancellation_type::partial → request_exit

  • asio::cancellation_type::terminal → terminate

example/quickstart.cpp:53-56
    async_execute(process(ctx, "/usr/bin/g++", {"hello_world.cpp"}))
        (asio::cancel_after(std::chrono::seconds(10), asio::cancellation_type::partial)) (1)
        (asio::cancel_after(std::chrono::seconds(10), asio::cancellation_type::terminal)) (2)
        (asio::detached);
1 After 10 seconds send a request_exit.
2 After 20 seconds terminate

Launcher

The process creation is done by a process_launcher. The constructor of process will use the default_launcher, which varies by system. There are additional launcher available on most systems.

Name Summary Default on Available on

windows::default_launcher

CreateProcessW

windows

windows

windows::as_user_launcher

CreateProcessAsUserW

windows

windows::with_logon_launcher

CreateProcessWithLogonW

windows

windows::with_token_launcher

CreateProcessWithTokenW

windows

posix::default_launcher

fork & an error pipe

most of posix

posix

posix::fork_and_forget

fork without error pipe

posix

posix::vfork_launcher

vfork

posix

A launcher is invoked through the call operator.

auto l = windows::as_user_launcher((HANDLE)0xDEADBEEF);
asio::io_context ctx;
boost::system::error_code ec;
auto proc = l(ctx, ec, "C:\\User\\boost\\Downloads\\totally_not_a_virus.exe", {});

The launcher will call certain functions on the initializer if they’re present, as documented below. The initializer are used to modify the process behaviour.

Linux Launchers

The default launchers on linux open an internal pipe to communicate errors that occur after forking back to the parent process.

A pipe can be used if one end is open on the parent, the other on the child. This allows the parents to select on his pipe-end to know if the child exited.

This can be prevented by using the fork_and_forget_launcher. Alternatively, the vfork_launcher can report errors directly back to the parent process.

Thus some calls to the initializers occur after forking from the child process.

struct custom_initializer
{
    // called before a call to fork. A returned error will cancel the launch.
    template<typename Launcher>
    error_code on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));

    // called for every initializer if an error occurred during setup or process creation
    template<typename Launcher>
    void on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line),
                  const error_code & ec);

    // called after successful process creation
    template<typename Launcher>
    void on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));

    // called for every initializer if an error occurred when forking, in addition to on_error.
    template<typename Launcher>
    void on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line),
                       const error_code & ec);


    // called before a call to execve. A returned error will cancel the launch. Called from the child process.
    template<typename Launcher>
    error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));


    // called after a failed call to execve from the child process.
    template<typename Launcher>
    void on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));
};

The call sequence on success:

posix success

The call sequence when fork fails:

posix fork err

The call sequence when exec fails:

posix exec err

The launcher will close all non-whitelisted file descriptors after on_exec_setup.

Windows Launchers

Windows launchers are pretty straight forward, they will call the following functions on the initializer if present.

struct custom_initializer
{
    // called before a call to CreateProcess. A returned error will cancel the launch.
    template<typename Launcher>
    error_code on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line);

    // called for every initializer if an error occurred during setup or process creation
    template<typename Launcher>
    void on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line,
                  const error_code & ec);

    // called after successful process creation
    template<typename Launcher>
    void on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line);

};

NTOE: All the additional launchers for windows inherit default_launcher.

The call sequence is as follows:

image::windows_exec.svg '''

Initializers

process_start_dir

The easier initializer to use is process_start_dir:

example/start_dir.cpp:17-20
asio::io_context ctx;
process ls(ctx.get_executor(), "/ls", {}, process_start_dir("/home"));
ls.wait();

This will run ls in the folder /home instead of the current folder.

If your path is relative, it may fail on posix, because the directory is changed before a call to execve. = stdio

When using io with a subprocess, all three standard streams (stdin, stdout, stderr) get set for the child-process. The default setting is to inherit the parent process.

This feature meant to be flexible, which is why there is little checking on the arguments assigned to one of those streams.

Pipes

asio pipes can be used for io. When using in process_stdio they will get automatically connected and the other side will get assigned to the child process:

example/stdio.cpp:20-29
asio::io_context ctx;
asio::readable_pipe rp{ctx};

process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, rp, { /* err to default */ }});
std::string output;

boost::system::error_code ec;
asio::read(rp, asio::dynamic_buffer(output), ec);
assert(!ec || (ec == asio::error::eof));
proc.wait();

readable pipes can be assigned to out and err, while writable_pipes can be assigned to in.

FILE*

FILE* can also be used for either side; this allows the stdin, stderr, stdout macros to be used:

example/stdio.cpp:35-38
    asio::io_context ctx;
    // forward both stderr & stdout to stdout of the parent process
    process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, stdout, stdout});
    proc.wait();

nullptr

nullptr may be used to set a given stream to be opened on the null-device (/dev/null on posix, NUL on windows). This is used to ignore output or give only EOF as input.

example/stdio.cpp:43-46
    asio::io_context ctx;
    // forward stderr to /dev/null or NUL
    process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, {}, nullptr});
    proc.wait();

native_handle

A native handle can be used as well, which means an int on posix or a HANDLE on windows. Furthermore, any object that has a native_handle function which returns a valid type for a stdio stream.

E.g. a domain socket on linux.

example/stdio.cpp:52-57
    asio::io_context ctx;
    // ignore stderr
    asio::local::stream_protocol::socket sock{ctx}, other{ctx};
    asio::local::connect_pair(sock, other);
    process proc(ctx, "~/not-a-virus", {}, process_stdio{sock, sock, nullptr});
    proc.wait();

popen

Additionally, process v2 provides a popen class. It starts a process and connects pipes for stdin and stdout, so that the popen object can be used as a stream.

example/stdio.cpp:63-66
    asio::io_context ctx;
    boost::process::popen proc(ctx, "/usr/bin/addr2line", {argv[0]});
    asio::write(proc, asio::buffer("main\n"));
    std::string line;
    asio::read_until(proc, asio::dynamic_buffer(line), '\n');

Environment

The environment namespace provides all sorts of facilities to query and manipulate the environment of the current process.

The api should be straight forward, but one oddity that needs to be pointed out is, that environment names are not case sensitive on windows. The key_traits class implements the proper traits depending on the current system.

Additionally, environment can be lists separated by : or ;; environment::value and environment::value_view can be used to iterate those.

Beyond that, the requirements on an environment are a low as possible; an environment is either a list of strings or a list of string-pairs. It is however recommended to use the environment types, as to have the right value comparisons.

To note is the find_executable functions, which searches in an environment for an executable.

example/env.cpp:19-28
    // search in the current environment
    auto exe = environment::find_executable("g++");

    std::unordered_map <environment::key, environment::value> my_env =
        {
            {"SECRET", "THIS_IS_A_TEST"},
            {"PATH",   {"/bin", "/usr/bin"}}
        };

    auto other_exe = environment::find_executable("g++", my_env);

Subprocess environment

The subprocess environment assignment follows the same constraints:

example/env.cpp:34-42
    asio::io_context ctx;
    std::unordered_map<environment::key, environment::value> my_env =
        {
            {"SECRET", "THIS_IS_A_TEST"},
            {"PATH", {"/bin", "/usr/bin"}}
        };
    auto exe = environment::find_executable("g++", my_env);
    process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));
    process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env));

Reference

bind_launcher.hpp

The bind_launcher utlitities allow on the fly construction of a launcher with bound initializers.

// Creates a new launcher with the bound initializer.
template<typename Launcher, typename ... Init>
auto bind_launcher(Launcher && launcher, Init && ... init);

// Calls bind_launcher with the default_launcher as the first parameter.
// The new launcher with bound parameters
template<typename ... Init>
auto bind_default_launcher(Init && ... init);

cstring_ref.hpp

The cstring_ref is a string-view like type that holds a null-terminated string. == default_launcher.hpp

The default_launcher is the standard way of creating a process.

asio::io_context ctx;
process proc(ctx.get_executor(), "test", {});
// equivalent to
process prod = default_launcher()(ctx.get_executor(), "test", {});

It has four overloads:

(ExecutionContext &,              filesystem::path, Args && args, Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>
(Executor &,                      filesystem::path, Args && args, Inits && ... inits) -> basic_process<Executor>;
(ExecutionContext &, error_code&, filesystem::path, Args && args, Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>;`
(Executor &,         error_code&, filesystem::path, Args && args, Inits && ... inits) -> basic_process<Executor>

environment.hpp

environment

The environment header provides facilities to manipulate the current environment and set it for new processes.

An environment is a a range of T fulfilling these requirements:

For T value * - std::get<0>(value) must return a type comparable to key_view. * - std::get<1>(value) must return a type convertible to value_view.

// Namespace for information and functions regarding the calling process.
namespace environment
{

// A char traits type that reflects the OS rules for string representing environment keys.
/* Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`.
 *
 * Windows treats keys as case-insensitive yet preserving. The char traits are made to reflect
 * that behaviour.
*/
template<typename Char>
using key_char_traits = implementation_defined ;

// A char traits type that reflects the OS rules for string representing environment values.
/* Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`.
*/
template<typename Char>
using value_char_traits = implementation_defined ;

// The character type used by the environment. Either `char` or `wchar_t`.
using char_type = implementation_defined ;

// The equal character in an environment string used to separate key and value.
constexpr char_type equality_sign = implementation_defined;

// The delimiter in environemtn lists. Commonly used by the `PATH` variable.
constexpr char_type delimiter = implementation_defined;

// The native handle of an environment. Note that this can be an owning pointer and is generally not thread safe.
using native_handle = implementation_defined;


// A forward iterator over string_view used by a value or value_view to iterator through environments that are lists.
struct value_iterator;

// A view type for a key of an environment
struct key_view
{
    using value_type       = char_type;
    using traits_type      = key_char_traits<char_type>;
    using string_view_type = basic_string_view<char_type, traits_type>;
    using string_type      = std::basic_string<char_type, key_char_traits<char_type>>;

    key_view() noexcept = default;
    key_view( const key_view& p ) = default;
    key_view( key_view&& p ) noexcept = default;
    template<typename Source>
    key_view( const Source& source );
    key_view( const char_type * p);
    key_view(       char_type * p);

    ~key_view() = default;

    key_view& operator=( const key_view& p ) = default;
    key_view& operator=( key_view&& p ) noexcept = default;
    key_view& operator=( string_view_type source );

    void swap( key_view& other ) noexcept;

    string_view_type native() const noexcept;

    operator string_view_type() const;

    int compare( const key_view& p ) const noexcept;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;

    std::string       string() const;
    std::wstring     wstring() const;

    string_type native_string() const;

    friend bool operator==(key_view l, key_view r);
    friend bool operator!=(key_view l, key_view r);
    friend bool operator<=(key_view l, key_view r);
    friend bool operator>=(key_view l, key_view r);
    friend bool operator< (key_view l, key_view r);
    friend bool operator> (key_view l, key_view r);

    bool empty() const;

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key_view& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key_view& p );

    const value_type * data() const;
    std::size_t size() const;
};

// A view for a value in an environment
struct value_view
{
    using value_type       = char_type;
    using string_view_type = basic_cstring_ref<char_type, value_char_traits<char_type>>;
    using string_type      = std::basic_string<char_type, value_char_traits<char_type>>;
    using traits_type      = value_char_traits<char_type>;

    value_view() noexcept = default;
    value_view( const value_view& p ) = default;
    value_view( value_view&& p ) noexcept = default;
    template<typename Source>
    value_view( const Source& source );
    value_view( const char_type * p);
    value_view(       char_type * p);

    ~value_view() = default;

    value_view& operator=( const value_view& p ) = default;
    value_view& operator=( value_view&& p ) noexcept = default;
    value_view& operator=( string_view_type source );

    void swap( value_view& other ) noexcept;

    string_view_type native() const noexcept ;

    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;

    int compare( const value_view& p ) const noexcept;
    int compare( string_view_type str ) const;
    int compare( const value_type* s )  const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc() ) const;
    std::string string() const;
    std::wstring wstring() const;

    string_type native_string() const;
    bool empty() const;

    friend bool operator==(value_view l, value_view r);
    friend bool operator!=(value_view l, value_view r);
    friend bool operator<=(value_view l, value_view r);
    friend bool operator>=(value_view l, value_view r);
    friend bool operator< (value_view l, value_view r);
    friend bool operator> (value_view l, value_view r);


    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const value_view& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, value_view& p );
    value_iterator begin() const;
    value_iterator   end() const;

    const char_type * c_str();
    const value_type * data() const;
    std::size_t size() const;
};

// A view for a key value pair in an environment
struct key_value_pair_view
{
  using value_type       = char_type;
  using string_type      = std::basic_string<char_type>;
  using string_view_type = basic_cstring_ref<char_type>;
  using traits_type      = std::char_traits<char_type>;

  key_value_pair_view() noexcept = default;
  key_value_pair_view( const key_value_pair_view& p ) = default;
  key_value_pair_view( key_value_pair_view&& p ) noexcept = default;
  template<typename Source,
           typename = typename std::enable_if<is_constructible<string_view_type, Source>::value>::type>
  key_value_pair_view( const Source& source );

  key_value_pair_view( const char_type * p);
  key_value_pair_view(       char_type * p);


  ~key_value_pair_view() = default;

  key_value_pair_view& operator=( const key_value_pair_view& p ) = default;
  key_value_pair_view& operator=( key_value_pair_view&& p ) noexcept = default;

  void swap( key_value_pair_view& other ) noexcept;

  string_view_type native() const noexcept;

  operator string_view_type() const;
  operator typename string_view_type::string_view_type() const;

  int compare( key_value_pair_view p ) const noexcept;
  int compare( const string_type& str ) const;
  int compare( string_view_type str ) const;
  int compare( const value_type* s ) const;

  template< class CharT, class Traits = std::char_traits<CharT>, class Alloc = std::allocator<CharT> >
  std::basic_string<CharT,Traits,Alloc>
  basic_string( const Alloc& alloc = Alloc()) const;
  std::string   string() const;
  std::wstring wstring() const;

  string_type native_string() const;

  bool empty() const;

  key_view key() const;
  value_view value() const;

  friend bool operator==(key_value_pair_view l, key_value_pair_view r);
  friend bool operator!=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator<=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator>=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator< (key_value_pair_view l, key_value_pair_view r);
  friend bool operator> (key_value_pair_view l, key_value_pair_view r);


  template< class CharT, class Traits >
  friend std::basic_ostream<CharT,Traits>&
  operator<<( std::basic_ostream<CharT,Traits>& os, const key_value_pair_view& p );

  template< class CharT, class Traits >
  friend std::basic_istream<CharT,Traits>&
  operator>>( std::basic_istream<CharT,Traits>& is, key_value_pair_view& p );

  template<std::size_t Idx>
  inline auto get() const -> typename conditional<Idx == 0u, key_view,
                                                             value_view>::type;
  const value_type * c_str() const noexcept;
  const value_type * data() const;
  std::size_t size() const;

};

// Allow tuple-likg getters & structured bindings.
template<>   key_view key_value_pair_view::get<0u>() const;
template<> value_view key_value_pair_view::get<1u>() const;

// A class representing a key within an environment.
struct key
{
    using value_type       = char_type;
    using traits_type      = key_char_traits<char_type>;
    using string_type      = std::basic_string<char_type, traits_type>;
    using string_view_type = basic_string_view<char_type, traits_type>;

    key();
    key( const key& p ) = default;
    key( key&& p ) noexcept = default;
    key( const string_type& source );
    key( string_type&& source );
    key( const value_type * raw );
    key(       value_type * raw );

    explicit key(key_view kv);


    template< class Source >
    key( const Source& source);

    key(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt >
    key( InputIt first, InputIt last);

    ~key() = default;

    key& operator=( const key& p ) = default;
    key& operator=( key&& p );
    key& operator=( string_type&& source );

    template< class Source >
    key& operator=( const Source& source );

    key& assign( string_type&& source );

    template< class Source >
    key& assign( const Source& source );
    template< class InputIt >
    key& assign( InputIt first, InputIt last );

    void clear();

    void swap( key& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;

    int compare( const key& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
        class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;


    std::string string() const;
    std::wstring wstring() const;

    const string_type & native_string() const;
    bool empty() const;

    friend bool operator==(const key & l, const key & r);
    friend bool operator!=(const key & l, const key & r);
    friend bool operator<=(const key & l, const key & r);
    friend bool operator>=(const key & l, const key & r);
    friend bool operator< (const key & l, const key & r);
    friend bool operator> (const key & l, const key & r);

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key& p );
    const value_type * data() const;
    std::size_t size() const;
};

bool operator==(const value_view &, const value_view);
bool operator!=(const value_view &, const value_view);
bool operator<=(const value_view &, const value_view);
bool operator< (const value_view &, const value_view);
bool operator> (const value_view &, const value_view);
bool operator>=(const value_view &, const value_view);


struct value
{
    using value_type       = char_type;
    using traits_type      = value_char_traits<char_type>;
    using string_type      = std::basic_string<char_type, traits_type>;
    using string_view_type = basic_cstring_ref<char_type, traits_type>;

    value();
    value( const value& p ) = default;

    value( const string_type& source );
    value( string_type&& source );
    value( const value_type * raw );
    value(       value_type * raw );

    explicit value(value_view kv);

    template< class Source >
    value( const Source& source );
    value(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt >
    value( InputIt first, InputIt last);

    ~value() = default;

    value& operator=( const value& p ) = default;
    value& operator=( value&& p );
    value& operator=( string_type&& source );
    template< class Source >
    value& operator=( const Source& source );

    value& assign( string_type&& source );
    template< class Source >
    value& assign( const Source& source );

    template< class InputIt >
    value& assign( InputIt first, InputIt last );

    void push_back(const value & sv);
    void clear() {value_.clear();}

    void swap( value& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;

    int compare( const value& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;

    std::string string() const;
    std::wstring wstring() const;


    const string_type & native_string() const;

    bool empty() const;

    friend bool operator==(const value & l, const value & r);
    friend bool operator!=(const value & l, const value & r);
    friend bool operator<=(const value & l, const value & r);
    friend bool operator>=(const value & l, const value & r);
    friend bool operator< (const value & l, const value & r);
    friend bool operator> (const value & l, const value & r);

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const value& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, value& p );

    value_iterator begin() const;
    value_iterator   end() const;
    const value_type * data() const;
    std::size_t size() const;
};


bool operator==(const value_view &, const value_view);
bool operator!=(const value_view &, const value_view);
bool operator<=(const value_view &, const value_view);
bool operator< (const value_view &, const value_view);
bool operator> (const value_view &, const value_view);
bool operator>=(const value_view &, const value_view);

struct key_value_pair
{
    using value_type       = char_type;
    using traits_type      = std::char_traits<char_type>;
    using string_type      = std::basic_string<char_type>;
    using string_view_type = basic_cstring_ref<char_type>;

    key_value_pair()l
    key_value_pair( const key_value_pair& p ) = default;
    key_value_pair( key_value_pair&& p ) noexcept = default;
    key_value_pair(key_view key, value_view value);

    key_value_pair(key_view key, std::initializer_list<basic_string_view<char_type>> values);
    key_value_pair( const string_type& source );
    key_value_pair( string_type&& source );
    key_value_pair( const value_type * raw );
    key_value_pair(       value_type * raw );

    explicit key_value_pair(key_value_pair_view kv) : value_(kv.c_str()) {}

    template< class Source >
    key_value_pair( const Source& source);

    template< typename Key,
              typename Value >
    key_value_pair(
         const std::pair<Key, Value> & kv);

    key_value_pair(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt , typename std::iterator_traits<InputIt>::iterator_category>
    key_value_pair( InputIt first, InputIt last );

    ~key_value_pair() = default;

    key_value_pair& operator=( const key_value_pair& p ) = default;
    key_value_pair& operator=( key_value_pair&& p );
    key_value_pair& operator=( string_type&& source );
    template< class Source >
    key_value_pair& operator=( const Source& source );

    key_value_pair& assign( string_type&& source );

    template< class Source >
    key_value_pair& assign( const Source& source );


    template< class InputIt >
    key_value_pair& assign( InputIt first, InputIt last );

    void clear();

    void swap( key_value_pair& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;
    operator key_value_pair_view() const;

    int compare( const key_value_pair& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>, class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc() ) const;
    std::string string() const       {return basic_string<char>();}
    std::wstring wstring() const     {return basic_string<wchar_t>();}

    const string_type & native_string() const;
    friend bool operator==(const key_value_pair & l, const key_value_pair & r);
    friend bool operator!=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator<=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator>=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator< (const key_value_pair & l, const key_value_pair & r);
    friend bool operator> (const key_value_pair & l, const key_value_pair & r);

    bool empty() const;

    struct key_view key() const;
    struct value_view value() const;

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key_value_pair& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key_value_pair& p );

    const value_type * data() const;
    std::size_t size() const;

    template<std::size_t Idx>
    inline auto get() const -> typename conditional<Idx == 0u,environment::key_view, environment::value_view>::type;
};

bool operator==(const key_value_pair_view &, const key_value_pair_view);
bool operator!=(const key_value_pair_view &, const key_value_pair_view);
bool operator<=(const key_value_pair_view &, const key_value_pair_view);
bool operator< (const key_value_pair_view &, const key_value_pair_view);
bool operator> (const key_value_pair_view &, const key_value_pair_view);
bool operator>=(const key_value_pair_view &, const key_value_pair_view);


// Allow tuple-likg getters & structured bindings.
template<>
key_view key_value_pair::get<0u>() const;

template<>
value_view key_value_pair::get<1u>() const;

// A view object for the current environment of this process.
/*
 * The view might (windows) or might not (posix) be owning;
 * if it owns it will deallocate the on destruction, like a unique_ptr.
 *
 * Note that accessing the environment in this way is not thread-safe.
 *
 * @code
 *
 * void dump_my_env(current_view env = current())
 * {
 *    for (auto  & [k, v] : env)
 *        std::cout << k.string() << " = "  << v.string() << std::endl;
 * }
 *
 * @endcode
 *
 *
 */
struct current_view
{
    using native_handle_type = environment::native_handle_type;
    using value_type = key_value_pair_view;

    current_view() = default;
    current_view(current_view && nt) = default;

    native_handle_type  native_handle() { return handle_.get(); }

    struct iterator
    {
        using value_type        = key_value_pair_view;
        using difference_type   = int;
        using reference         = key_value_pair_view;
        using pointer           = key_value_pair_view;
        using iterator_category = std::forward_iterator_tag;

        iterator() = default;
        iterator(const iterator & ) = default;
        iterator(const native_iterator &native_handle) : iterator_(native_handle) {}

        iterator & operator++()
        {
            iterator_ = detail::next(iterator_);
            return *this;
        }

        iterator operator++(int)
        {
            auto last = *this;
            iterator_ = detail::next(iterator_);
            return last;
        }
        key_value_pair_view operator*() const
        {
            return detail::dereference(iterator_);
        }

        friend bool operator==(const iterator & l, const iterator & r) {return l.iterator_ == r.iterator_;}
        friend bool operator!=(const iterator & l, const iterator & r) {return l.iterator_ != r.iterator_;}

      private:
        environment::native_iterator iterator_;
    };

    iterator begin() const {return iterator(handle_.get());}
    iterator   end() const {return iterator(detail::find_end(handle_.get()));}

 private:

  std::unique_ptr<typename remove_pointer<native_handle_type>::type,
                    detail::native_handle_deleter> handle_{environment::detail::load_native_handle()};
};

// Obtain a handle to the current environment
inline current_view current() {return current_view();}

// Find the home folder in an environment-like type.
/*
 * @param env The environment to search. Defaults to the current environment of this process
 *
 * The environment type passed in must be a range with value T that fulfills the following requirements:
 *
 *  For `T value`
 *
 *  - std::get<0>(value) must return a type comparable to `key_view`.
 *  - std::get<1>(value) must return a type convertible to filesystem::path.
 *
 * @return A filesystem::path to the home directory or an empty path if it cannot be found.
 *
 */
template<typename Environment = current_view>
inline filesystem::path home(Environment && env = current());

// Find the executable `name` in an environment-like type.
template<typename Environment = current_view>
filesystem::path find_executable(BOOST_PROCESS_V2_NAMESPACE::filesystem::path name,
                                 Environment && env = current());

// Get an environment variable from the current process.
value get(const key & k, error_code & ec);
value get(const key & k);
value get(basic_cstring_ref<char_type, key_char_traits<char_type>> k, error_code & ec);
value get(basic_cstring_ref<char_type, key_char_traits<char_type>> k);
value get(const char_type * c, error_code & ec);
value get(const char_type * c);

// Set an environment variable for the current process.
void set(const key & k, value_view vw, error_code & ec);
void set(const key & k, value_view vw);
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, value_view vw, error_code & ec);
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, value_view vw);
void set(const char_type * k, value_view vw, error_code & ec);
void set(const char_type * k, value_view vw);
template<typename Char>
void set(const key & k, const Char * vw, error_code & ec);
template<typename Char>
void set(const key & k, const Char * vw);
template<typename Char>
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, const Char * vw, error_code & ec);
template<typename Char>
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, const Char * vw);
template<typename Char>
void set(const char_type * k, const Char * vw, error_code & ec);
template<typename Char>
void set(const char_type * k, const Char * vw);

//  Remove an environment variable from the current process.
void unset(const key & k, error_code & ec);
void unset(const key & k);
void unset(basic_cstring_ref<char_type, key_char_traits<char_type>> k, error_code & ec);
void unset(basic_cstring_ref<char_type, key_char_traits<char_type>> k);
void unset(const char_type * c, error_code & ec);
void unset(const char_type * c);

}

process_environment

In order to set the environment of a child process, process_environment can be used.

This will set the environment in a subprocess:
process proc{executor, find_executable("printenv"), {"foo"}, process_environment{"foo=bar"}};

The environment initializer will persist it’s state, so that it can be used multiple times. Do however note the the Operating System is allowed to modify the internal state.

auto exe = find_executable("printenv");
process_environment env = {"FOO=BAR", "BAR=FOO"};

process proc1(executor, exe, {"FOO"}, env);
process proc2(executor, exe, {"BAR"}, env);

error.hpp

The error header provides two error categories:

// Errors used for utf8 <-> UCS-2 conversions.
enum utf8_conv_error
{
    insufficient_buffer = 1,
    invalid_character,
};

extern const error_category& get_utf8_category();
static const error_category& utf8_category = get_utf8_category();

extern const error_category& get_exit_code_category();

/// An error category that can be used to interpret exit codes of subprocesses.
static const error_category& exit_code_category = get_exit_code_category();

}

The get_exit_code_category can be used as follows:

void run_my_process(filesystem::path pt, error_code & ec)
{
  process proc(pt, {});
  proc.wait();
  ec.assign(proc.native_exit_code(), error::get_exit_code_category());
}

execute.hpp

The execute header provides two error categories:

// Run a process and wait for it to complete.
template<typename Executor> int execute(basic_process<Executor> proc);
template<typename Executor> int execute(basic_process<Executor> proc, error_code & ec)

// Execute a process asynchronously
template<typename Executor = net::any_io_executor,
        BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
        WaitHandler = net::default_completion_token_t<Executor>>
auto async_execute(basic_process<Executor> proc,
                         WaitHandler && handler = net::default_completion_token_t<Executor>());

The async_execute function asynchronously for a process to complete.

Cancelling the execution will signal the child process to exit
with the following interpretations:
  • cancellation_type::total → interrupt

  • cancellation_type::partial → request_exit

  • cancellation_type::terminal → terminate

It is to note that async_execute will use the lowest selected cancellation type. A subprocess might ignore anything not terminal.

exit_code.hpp

The exit code header provides portable handles for exit codes.

// The native exit-code type, usually an integral value
/* The OS may have a value different from `int` to represent
 * the exit codes of subprocesses. It might also
 * contain additional information.
 */
typedef implementation_defined native_exit_code_type;


// Check if the native exit code indicates the process is still running
bool process_is_running(native_exit_code_type code);

// Obtain the portable part of the exit code, i.e. what the subprocess has returned from main.
int evaluate_exit_code(native_exit_code_type code);

// Helper to subsume an exit-code into an error_code if there's no actual error isn't set.
error_code check_exit_code(
    error_code &ec, native_exit_code_type native_code,
    const error_category & category = error::get_exit_code_category());

The check_exit_code can be used like this:

process proc{co_await this_coro::executor, "exit", {"1"}};

co_await proc.async_wait(
    asio::deferred(
     [&proc](error_code ec, int)
     {
       return asio::deferred.values(
                 check_exit_code(ec, proc.native_exit_code())
             );

ext

The headers in process/ext provides features to obtain information about third part processes.

// Get the cmd line used to launche the process
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle);
shell cmd(pid_type pid, error_code & ec);
shell cmd(pid_type pid);


// Get the current working directory of the process.
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle)
filesystem::path cwd(pid_type pid, error_code & ec);
filesystem::path cwd(pid_type pid);


// Get the current environment of the process.
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle)
env_view env(pid_type pid, error_code & ec);
env_view env(pid_type pid);


// Get the executable of the process.
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle)
filesystem::path exe(pid_type pid, error_code & ec);
filesystem::path exe(pid_type pid);
The function may fail with "operation_not_supported" on some niche platforms.
On windows overloads taking a HANDLE are also available.

pid.hpp

//An integral type representing a process id.
typedef implementation_defined pid_type;

// Get the process id of the current process.
pid_type current_pid();

// List all available pids.
std::vector<pid_type> all_pids(boost::system::error_code & ec);
std::vector<pid_type> all_pids();

// return parent pid of pid.
pid_type parent_pid(pid_type pid, boost::system::error_code & ec);
pid_type parent_pid(pid_type pid);

// return child pids of pid.
std::vector<pid_type> child_pids(pid_type pid, boost::system::error_code & ec);
std::vector<pid_type> child_pids(pid_type pid);

popen.hpp

popen is a class that launches a process and connect stdin & stderr to pipes.

popen proc(executor, find_executable("addr2line"), {argv[0]});
asio::write(proc, asio::buffer("main\n"));
std::string line;
asio::read_until(proc, asio::dynamic_buffer(line), '\n');
// A subprocess with automatically assigned pipes.
template<typename Executor = net::any_io_executor>
struct basic_popen : basic_process<Executor>
{
    // The executor of the process
    using executor_type = Executor;

    // Rebinds the popen type to another executor.
    template <typename Executor1>
    struct rebind_executor
    {
        // The pipe type when rebound to the specified executor.
        typedef basic_popen<Executor1> other;
    };

    // Move construct a popen
    basic_popen(basic_popen &&) = default;
    // Move assign a popen
    basic_popen& operator=(basic_popen &&) = default;

    // Move construct a popen and change the executor type.
    template<typename Executor1>
    basic_popen(basic_popen<Executor1>&& lhs)
        : basic_process<Executor>(std::move(lhs)),
                stdin_(std::move(lhs.stdin_)), stdout_(std::move(lhs.stdout_))
    {
    }

    // Create a closed process handle
    explicit basic_popen(executor_type exec);

    // Create a closed process handle
    template <typename ExecutionContext>
    explicit basic_popen(ExecutionContext & context);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ... Inits>
    explicit basic_popen(
            executor_type executor,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            executor_type executor,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Args, typename ... Inits>
    explicit basic_popen(
            executor_type executor,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename Args, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            executor_type executor,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ExecutionContext, typename ... Inits>
    explicit basic_popen(
            ExecutionContext & context,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

        // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ExecutionContext, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            ExecutionContext & context,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ExecutionContext, typename Args, typename ... Inits>
    explicit basic_popen(
            ExecutionContext & context,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ExecutionContext, typename Args, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            ExecutionContext & context,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // The type used for stdin on the parent process side.
    using stdin_type = net::basic_writable_pipe<Executor>;
    // The type used for stdout on the parent process side.
    using stdout_type = net::basic_readable_pipe<Executor>;

    // Get the stdin pipe.

    // Get the stdout pipe.

    // Get the stdin pipe.
          stdin_type & get_stdin();
    const stdin_type & get_stdin() const;
    // Get the stdout pipe.
          stdout_type & get_stdout();
    const stdout_type & get_stdout() const;

    // Write some data to the stdin pipe.
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers);
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers,
                           boost::system::error_code& ec);

    // Start an asynchronous write.
    template <typename ConstBufferSequence,
            BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code, std::size_t))
            WriteToken = net::default_completion_token_t<executor_type>>
    auto async_write_some(const ConstBufferSequence& buffers,
                     WriteToken && token = net::default_completion_token_t<executor_type>());

    // Read some data from the stdout pipe.
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers);
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers,
                          boost::system::error_code& ec)

    // Start an asynchronous read.    template <typename MutableBufferSequence,
            BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (boost::system::error_code, std::size_t))
            ReadToken = net::default_completion_token_t<executor_type>>
    auto async_read_some(const MutableBufferSequence& buffers,
                    BOOST_ASIO_MOVE_ARG(ReadToken) token
                    = net::default_completion_token_t<executor_type>());
};

// A popen object with the default  executor.
using popen = basic_popen<>;

process.hpp

// A class managing a subprocess
/* A `basic_process` object manages a subprocess; it tracks the status and exit-code,
 * and will terminate the process on destruction if `detach` was not called.
*/
template<typename Executor = net::any_io_executor>
struct basic_process
{
  // The executor of the process
  using executor_type = Executor;
  // Get the executor of the process
  executor_type get_executor() {return process_handle_.get_executor();}

  // The non-closing handle type
  using handle_type = basic_process_handle<executor_type>;

  // Get the underlying non-closing handle
  handle_type & handle() { return process_handle_; }

  // Get the underlying non-closing handle
  const handle_type & handle() const { return process_handle_; }

  // Provides access to underlying operating system facilities
  using native_handle_type = typename handle_type::native_handle_type;

  // Rebinds the process_handle to another executor.
  template <typename Executor1>
  struct rebind_executor
  {
    // The socket type when rebound to the specified executor.
    typedef basic_process<Executor1> other;
  };

  /** An empty process is similar to a default constructed thread. It holds an empty
  handle and is a place holder for a process that is to be launched later. */
  basic_process() = default;

  basic_process(const basic_process&) = delete;
  basic_process& operator=(const basic_process&) = delete;

  // Move construct the process. It will be detached from `lhs`.
  basic_process(basic_process&& lhs) = default;

  // Move assign a process. It will be detached from `lhs`.
  basic_process& operator=(basic_process&& lhs) = default;

  // Move construct and rebind the executor.
  template<typename Executor1>
  basic_process(basic_process<Executor1>&& lhs);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename ... Inits>
  explicit basic_process(
      executor_type executor,
      const filesystem::path& exe,
      std::initializer_list<string_view> args,
      Inits&&... inits);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename Args, typename ... Inits>
  explicit basic_process(
      executor_type executor,
      const filesystem::path& exe,
      Args&& args, Inits&&... inits);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename ExecutionContext, typename ... Inits>
  explicit basic_process(
          ExecutionContext & context,
          const filesystem::path& exe,
          std::initializer_list<string_view> args,
          Inits&&... inits);
  // Construct a child from a property list and launch it using the default launcher.
  template<typename ExecutionContext, typename Args, typename ... Inits>
  explicit basic_process(
          ExecutionContext & context,
          const filesystem::path&>::type exe,
          Args&& args, Inits&&... inits);

  // Attach to an existing process
  explicit basic_process(executor_type exec, pid_type pid);

  // Attach to an existing process and the internal handle
  explicit basic_process(executor_type exec, pid_type pid, native_handle_type native_handle);

  // Create an invalid handle
  explicit basic_process(executor_type exec);

  // Attach to an existing process
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context, pid_type pid);

  // Attach to an existing process and the internal handle
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context, pid_type pid, native_handle_type native_handle);

  // Create an invalid handle
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context);



  // Destruct the handle and terminate the process if it wasn't detached.
  ~basic_process();

  // Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
  /** Maybe be ignored by the subprocess. */
  void interrupt(error_code & ec);
  void interrupt();

  // Throwing @overload void interrupt()


  // Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
  void request_exit(error_code & ec);
  void request_exit();

  // Send the process a signal requesting it to stop. This may rely on undocumented functions.
  void suspend(error_code &ec);
  void suspend();


  // Send the process a signal requesting it to resume. This may rely on undocumented functions.
  void resume(error_code &ec);
  void resume();

  // Unconditionally terminates the process and stores the exit code in exit_status.
  void terminate(error_code & ec);
  void terminate();

  // Waits for the process to exit, store the exit code internally and return it.
  int wait(error_code & ec);
  int wait();

  // Detach the process.
  handle_type detach();
  // Get the native
  native_handle_type native_handle() {return process_handle_.native_handle(); }

  // Return the evaluated exit_code.
  int exit_code() cons;

  // Get the id of the process;
  pid_type id() const;

  // The native handle of the process.
  /** This might be undefined on posix systems that only support signals */
  native_exit_code_type native_exit_code() const;

  // Checks if the current process is running.
  /* If it has already completed the exit code will be stored internally
   * and can be obtained by calling `exit_code.
   */
  bool running();
  bool running(error_code & ec) noexcept;

  // Check if the process is referring to an existing process.
  /** Note that this might be a process that already exited.*/
  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, int))
  WaitHandler = net::default_completion_token_t<executor_type>>
  auto async_wait(WaitHandler && handler = net::default_completion_token_t<executor_type>());
};

// Process with the default executor.
typedef basic_process<> process;

process_handle.hpp

A process handle is an unmanaged version of a process. This means it does not terminate the process on destruction and will not keep track of the exit-code.

that the exit code might be discovered early, during a call to running. Thus it can only be discovered that process has exited already.
template<typename Executor = net::any_io_executor>
struct basic_process_handle
{
    // The native handle of the process.
    /* This might be undefined on posix systems that only support signals */
    using native_handle_type = implementation_defined;

    // The executor_type of the process_handle
    using executor_type =  Executor;

    // Getter for the executor
    executor_type get_executor();

    // Rebinds the process_handle to another executor.
    template<typename Executor1>
    struct rebind_executor
    {
        // The socket type when rebound to the specified executor.
        typedef basic_process_handle<Executor1> other;
    };


    // Construct a basic_process_handle from an execution_context.
    /*
    * @tparam ExecutionContext The context must fulfill the asio::execution_context requirements
    */
    template<typename ExecutionContext>
    basic_process_handle(ExecutionContext &context);

    // Construct an empty process_handle from an executor.
    basic_process_handle(executor_type executor);

    // Construct an empty process_handle from an executor and bind it to a pid.
    /* On NON-linux posix systems this call is not able to obtain a file-descriptor and will thus
     * rely on signals.
     */
    basic_process_handle(executor_type executor, pid_type pid);

    // Construct an empty process_handle from an executor and bind it to a pid and the native-handle
    /* On some non-linux posix systems this overload is not present.
     */
    basic_process_handle(executor_type executor, pid_type pid, native_handle_type process_handle);

    // Move construct and rebind the executor.
    template<typename Executor1>
    basic_process_handle(basic_process_handle<Executor1> &&handle);

    // Get the id of the process
    pid_type id() const
    { return pid_; }

    // Terminate the process if it's still running and ignore the result
    void terminate_if_running(error_code &);

    // Throwing @overload void terminate_if_running(error_code & ec;
    void terminate_if_running();
    // wait for the process to exit and store the exit code in exit_status.
    void wait(native_exit_code_type &exit_status, error_code &ec);
    // Throwing @overload wait(native_exit_code_type &exit_code, error_code & ec)
    void wait(native_exit_code_type &exit_status);

    // Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
    /* Maybe be ignored by the subprocess. */
    void interrupt(error_code &ec);

    // Throwing @overload void interrupt()
    void interrupt();

    // Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
    void request_exit(error_code &ec);

    // Throwing @overload void request_exit(error_code & ec)
    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);\
    // Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
    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`.
     */
    bool running(native_exit_code_type &exit_code, error_code &ec);
    // Throwing @overload bool running(native_exit_code_type &exit_code, error_code & ec)
    bool running(native_exit_code_type &exit_code);

    // 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))
             WaitHandler = net::default_completion_token_t<executor_type>>
    auto async_wait(WaitHandler &&handler = net::default_completion_token_t<executor_type>());
};

shell.hpp

This utility class parses command lines into tokens and allows users to execute processes 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 explicitly, it encourages a user to run a sanity check on the executable before launching it.

Example
asio::io_context ctx;

auto cmd = shell("my-app --help");
auto exe = cmd.exe();
check_if_malicious(exe);

process proc{ctx, exe, cmd.args()};
/// Utility to parse commands
struct shell
{
  shell() = default;
  template<typename Char, typename Traits>
  shell(basic_string_view<Char, Traits> input);

  shell(basic_cstring_ref<char_type> input);
  shell(const shell &) = delete;
  shell(shell && lhs) noexcept;
  shell& operator=(const shell &) = delete;
  shell& operator=(shell && lhs) noexcept;


  // the length of the parsed shell, including the executable
  int argc() const ;
  char_type** argv() const;

  char_type** begin() const;
  char_type** end()   const;

  bool empty() const;
  std::size_t size() const;

  // Native representation of the arguments to be used - excluding the executable
  args_type args() const;
  template<typename Environment = environment::current_view>
  filesystem::path exe(Environment && env = environment::current()) const;
};

start_dir.hpp

/// Initializer for the starting directory of a subprocess to be launched.
struct process_start_dir
{
  filesystem::path start_dir;

  process_start_dir(filesystem::path start_dir);
  {
  }
};

stdio.hpp

 The initializer for the stdio of a subprocess
The subprocess stdio initializer has three members:
  • in for stdin

  • out for stdout

  • err for stderr

If the initializer is present all three will be set for the subprocess. By default they will inherit the stdio handles from the parent process. This means that this will forward stdio to the subprocess:

asio::io_context ctx;
v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{});

No constructors are provided in order to support designated initializers in later version of C++.

asio::io_context ctx;

/// C++17
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr});
/// C++11 & C++14
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});

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.

/// The initializer for the stdio of a subprocess
struct process_stdio
{
  __implementation_defined__ in;
  __implementation_defined__ out;
  __implementation_defined__ err;
};

ext

The headers in process/ext provides features to obtain information about third part processes.

// Get the cmd line used to launche the process
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle);
shell cmd(pid_type pid, error_code & ec);
shell cmd(pid_type pid);


// Get the current working directory of the process.
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle)
filesystem::path cwd(pid_type pid, error_code & ec);
filesystem::path cwd(pid_type pid);


// Get the current environment of the process.
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle)
env_view env(pid_type pid, error_code & ec);
env_view env(pid_type pid);


// Get the executable of the process.
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle)
filesystem::path exe(pid_type pid, error_code & ec);
filesystem::path exe(pid_type pid);
The function may fail with "operation_not_supported" on some niche platforms.
On windows overloads taking a HANDLE are also available.

posix/bind_fd.hpp

bind_fd is a utility class to bind a file descriptor to an explicit file descriptor for the child process.

struct bind_fd
{
  // Inherit file descriptor with the same value.
  /*
  * This will pass descriptor 42 as 42 to the child process:
  * @code
  * process p{"test", {},  posix::bind_fd(42)};
  * @endcode
  */
  bind_fd(int target);

  // Inherit an asio io-object as a given file descriptor to the child process.
  /*
  * This will pass the tcp::socket, as 42 to the child process:
  * @code
  * extern tcp::socket sock;
  * process p{"test", {},  posix::bind_fd(42, sock)};
  * @endcode
  */

  template<typename Stream>
  bind_fd(int target, Stream && str);

  // Inherit a `FILE` as a given file descriptor to the child process.
  /* This will pass the given `FILE*`, as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, stderr)};

   */
  bind_fd(int target, FILE * f);

  // Inherit a file descriptor with as a different value.
  /* This will pass 24 as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, 24)};

   */
  bind_fd(int target, int fd):

  // Inherit a null device as a set descriptor.
  /* This will a null device as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, nullptr)};

   */
  bind_fd(int target, std::nullptr_t);

  // Inherit a newly opened-file as a set descriptor.
  /* This will pass a descriptor to "extra-output.txt" as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, "extra-output.txt")};

   */
  bind_fd(int target, const filesystem::path & pth, int flags = O_RDWR | O_CREAT);

};

Using bind_fd can be used to inherit file descriptors explicitly, because no unused one will be.

windows/creation_flags.hpp

Creation flags allows explicitly setting dwFlags

// An initializer to add to the dwFlags in the startup-info
template<DWORD Flags>
struct process_creation_flags;

// A flag to create a new process group. Necessary to allow interrupts for the subprocess.
constexpr static process_creation_flags<CREATE_NEW_PROCESS_GROUP> create_new_process_group;

// Breakaway from the current job object.
constexpr static process_creation_flags<CREATE_BREAKAWAY_FROM_JOB> create_breakaway_from_job;
// Allocate a new console.
constexpr static process_creation_flags<CREATE_NEW_CONSOLE>        create_new_console;

The flags can be used like this:

process p{"C:\\not-a-virus.exe", {}, process::windows::create_new_console};

windows/show_window.hpp

Creation flags allows explicitly setting wShowWindow options

/// A templated initializer to set wShowWindow flags.
template<DWORD Flags>
struct process_show_window;

//Hides the window and activates another window.
constexpr static process_show_window<SW_HIDE           > show_window_hide;
//Activates the window and displays it as a maximized window.
constexpr static process_show_window<SW_SHOWMAXIMIZED  > show_window_maximized;
//Activates the window and displays it as a minimized window.
constexpr static process_show_window<SW_SHOWMINIMIZED  > show_window_minimized;
//Displays the window as a minimized window. This value is similar to `minimized`, except the window is not activated.
constexpr static process_show_window<SW_SHOWMINNOACTIVE> show_window_minimized_not_active;
//Displays a window in its most recent size and position. This value is similar to show_normal`, except that the window is not activated.
constexpr static process_show_window<SW_SHOWNOACTIVATE > show_window_not_active;
//Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
constexpr static process_show_window<SW_SHOWNORMAL     > show_window_normal;

The flags can be used like this:

process p{"C:\\not-a-virus.exe", {}, process::windows::show_window_minimized};

Version 2

Boost.process V2 is an redesign of boost.process, based on previous design mistakes & improved system APIs.

The major changes are

  • Simplified interface

  • Reliance on pidfd_open on linux

  • Full asio integration

  • Removed unreliable functionality

  • UTF8 Support

  • separate compilation

  • fd safe by default

Version 2 is now the default. In order to discourage usage of the deprecated v1, it’s documentation has been removed.

Simplified Interface

In process v1 one can define partial settings in the constructor of the process, which has lead to a small DSL.

child c{exe="test", args+="--help", std_in < null(), env["FOO"] += "BAR"};

While this looks fancy at first, it really does not scale well with more parameters. For process v2, the interfaces is simple:

extern std::unordered_map<std::string, std::string> my_env;
extern asio::io_context ctx;
process proc(ctx, "./test", {"--help"}, process_stdio{nullptr, {}, {}}, process_environment(my_env));

Every initializer addresses one logical component (e.g. stdio) instead of multiple ones accumulating. Furthermore, every process has a path and arguments, instead of a confusing mixture of cmd-style and exe-args that can be randomly spread out.

pidfd_open

Since process v1 came out, linux has moved along and added [pidfd_open](https://man7.org/linux/man-pages/man2/pidfd_open.2.html) which allows users to obtain a descriptor for a process. This is much more reliable since it is not as easy to miss as a SIGCHLD. Windows has provided `HANDLE`s for processes all along. Unless the OS doesn’t support it, process v2 will use file descriptors and handles to implement waiting for processes.

Full asio integration

Process v1 aimed to make asio optional, but synchronous IO with subprocesses usually means one is begging for deadlocks. Since asio added pipes in boost 1.78, boost process V2 is fully asio based and uses it’s pipes and file-handles for the subprocess.

Unreliable functionality

Certain parts of boost.process were not as reliable as they should’ve been.

This concerns especially the wait_for and wait_until functions on the process. The latter are easy to do on windows, but posix does not provide an API for this. Thus the wait_for used signals or fork, which was all but safe. Since process v2 is based on asio and thus supports cancellation, a wait_for can now safely be implemented with an async_wait + timeout.

UTF-8

Instead of using ascii-APIs on windows, process V2 just assumes UTF-8 everywhere and uses the UTF-16 APIs.

Fd safe by default

While not a problem on windows (since HANDLEs get manually enabled for inheritance), posix systems create a problem with inheriting file handles by default.

Process V2 will automatically close all non-whitelisted descriptors, without needing any option to enable it.

Acknowledgements

The first Boost.Process draft was created in 2006. A lof of people have worked on various drafts since then. Especially Merino Vidal, Ilya Sokolov and Felipe Tanus spent a lot of time working on early drafts. They influenced Boost.Process over the years and wrote code which, to various extents, is still around in the library today.

The design of earlier versions of Boost.Process was not always satisfying. In 2011 Jeff Flinn proposed the executor and initializer concepts Boost.Process is based on today. Without Jeff’s idea the overall design of Boost.Process would not look like it does today.

A special thank you goes to [http://www.intra2net.com/(Intra2net AG) (especially Thomas Jarosch) who sponsored a project to support the development of Boost.Process. It was this sponsorship which made it possible to create a new Boost.Process version in 2012.

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.