mirror of
https://github.com/boostorg/process.git
synced 2026-01-19 04:22:15 +00:00
switched to asciidoc
This commit is contained in:
committed by
Klemens Morgenstern
parent
b5eacaca51
commit
7874a04958
121
doc/Jamfile.jam
121
doc/Jamfile.jam
@@ -1,106 +1,31 @@
|
||||
# Copyright (c) 2006, 2007 Julio M. Merino Vidal
|
||||
# Copyright (c) 2008 Ilya Sokolov, Boris Schaeling
|
||||
# Copyright (c) 2009 Boris Schaeling
|
||||
# Copyright (c) 2010 Felipe Tanus, Boris Schaeling
|
||||
# Copyright (c) 2011, 2012 Jeff Flinn, Boris Schaeling
|
||||
#
|
||||
# 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)
|
||||
# Copyright 2022 Klemens D. Morgenstern
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
using boostbook ;
|
||||
using quickbook ;
|
||||
using doxygen ;
|
||||
import asciidoctor ;
|
||||
|
||||
html index.html : index.adoc ;
|
||||
|
||||
local images = [ glob images/*.svg ] ;
|
||||
install images : $(images) : <location>html/boost_process ;
|
||||
install images_glob : $(images) : <location>$(BOOST_ROOT)/doc/html/boost_process ;
|
||||
install html_ : index.html : <location>html ;
|
||||
|
||||
import type ;
|
||||
type.register XMLPROCESSWORKAROUND : : XML ;
|
||||
import generators ;
|
||||
generators.register-standard common.copy : XML : XMLPROCESSWORKAROUND ;
|
||||
pdf process.pdf : index.adoc ;
|
||||
explicit process.pdf ;
|
||||
|
||||
xmlprocessworkaround posix_pseudocode : v1/posix_pseudocode.xml ;
|
||||
xmlprocessworkaround windows_pseudocode : v1/windows_pseudocode.xml ;
|
||||
install pdf_ : process.pdf : <location>pdf ;
|
||||
explicit pdf_ ;
|
||||
|
||||
path-constant INCLUDES : ../include ;
|
||||
|
||||
doxygen reference_v1
|
||||
:
|
||||
$(INCLUDES)/boost/process/v1.hpp
|
||||
[ glob $(INCLUDES)/boost/process/v1/*.hpp ]
|
||||
:
|
||||
<doxygen:param>EXCLUDE_SYMBOLS=BOOST_ASIO_INITFN_RESULT_TYPE
|
||||
<doxygen:param>"PREDEFINED=\\
|
||||
BOOST_PROCESS_DOXYGEN=1 \\
|
||||
BOOST_PROCESS_V1_INLINE=
|
||||
"
|
||||
<doxygen:param>HIDE_UNDOC_CLASSES=YES
|
||||
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
|
||||
<doxygen:param>EXAMPLE_PATH=.
|
||||
<dependency>posix_pseudocode
|
||||
<dependency>windows_pseudocode
|
||||
<xsl:path>.
|
||||
;
|
||||
|
||||
|
||||
|
||||
doxygen reference_v2
|
||||
:
|
||||
$(INCLUDES)/boost/process/v2.hpp
|
||||
[ glob $(INCLUDES)/boost/process/v2/*.hpp ]
|
||||
:
|
||||
<doxygen:param>EXCLUDE_SYMBOLS=BOOST_ASIO_INITFN_RESULT_TYPE
|
||||
<doxygen:param>PROJECT_NAME="Process V2"
|
||||
<doxygen:param>PROJECT_BRIEF="The process library"
|
||||
<doxygen:param>MACRO_EXPANSION=YES
|
||||
<doxygen:param>EXPAND_ONLY_PREDEF=YES
|
||||
<doxygen:param>"PREDEFINED=\\
|
||||
GENERATING_DOCUMENTATION=1 \\
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE=boost::asio \\
|
||||
\"BOOST_PROCESS_V2_BEGIN_NAMESPACE=namespace boost { namespace process { namespace v2 { \" \\
|
||||
\"BOOST_PROCESS_V2_END_NAMESPACE= } } }\" \\
|
||||
BOOST_PROCESS_V2_NAMESPACE=boost::process::v2 \\
|
||||
BOOST_PROCESS_V2_DECL \\
|
||||
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(x,y)=deduced \\
|
||||
BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(X)=Token \\
|
||||
BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(E)=DEFAULT_TYPE \\
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN=DEFAULT \\
|
||||
BOOST_CONSTEXPR=constexpr \\
|
||||
BOOST_CXX14_CONSTEXPR=constexpr \\
|
||||
BOOST_PROCESS_V2_INLINE= \\
|
||||
BOOST_ATTRIBUTE_NODISCARD=[[nodiscard]]
|
||||
"
|
||||
<doxygen.doxproc.id>reference_v2
|
||||
<doxygen:param>SHOW_USED_FILES=NO
|
||||
<doxygen:param>SHOW_FILES=NO
|
||||
<doxygen:param>SHOW_NAMESPACES=YES
|
||||
<doxygen:param>CLASS_DIAGRAMS=NO
|
||||
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
|
||||
<doxygen:param>HIDE_UNDOC_CLASSES=NO
|
||||
<xsl:path>.
|
||||
;
|
||||
|
||||
|
||||
boostbook standalone
|
||||
:
|
||||
process.qbk
|
||||
:
|
||||
<dependency>reference_v1
|
||||
<dependency>reference_v2
|
||||
<dependency>images
|
||||
<dependency>images_glob
|
||||
<xsl:param>boost.root=../../../..
|
||||
<xsl:param>html.stylesheet=../../../../doc/src/boostbook.css
|
||||
;
|
||||
|
||||
###############################################################################
|
||||
alias boostdoc
|
||||
: standalone/<format>docbook
|
||||
install images
|
||||
:
|
||||
: <dependency>images_glob
|
||||
: ;
|
||||
images/posix_exec_err.png
|
||||
images/posix_fork_err.png
|
||||
images/posix_success.png
|
||||
images/windows_exec.png
|
||||
:
|
||||
<location>html/images
|
||||
;
|
||||
|
||||
alias boostdoc ;
|
||||
explicit boostdoc ;
|
||||
alias boostrelease ;
|
||||
explicit boostrelease ;
|
||||
alias boostrelease : html_ ;
|
||||
explicit boostrelease ;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[section Acknowledgements]
|
||||
= 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.
|
||||
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.
|
||||
Many Thanks, to [https://github.com/time-killer-games](Samuel Venable) for contributing the <<v2::ext>> functionality and all the research that went into it.
|
||||
|
||||
|
||||
[endsect]
|
||||
@@ -1,4 +1,4 @@
|
||||
[section:env Environment]
|
||||
== Environment
|
||||
|
||||
The `environment` namespace provides all sorts of facilities to query and manipulate the environment of the current process.
|
||||
|
||||
@@ -14,35 +14,33 @@ as to have the right value comparisons.
|
||||
|
||||
To note is the `find_executable` functions, which searches in an environment for an executable.
|
||||
|
||||
```
|
||||
// search in the current environment
|
||||
auto exe = environment::find_executable("g++");
|
||||
[source,cpp]
|
||||
----
|
||||
// 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"}}
|
||||
};
|
||||
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);
|
||||
```
|
||||
auto other_exe = environment::find_executable("g++", my_env);
|
||||
----
|
||||
|
||||
[section:process_env Subprocess environment]
|
||||
== Subprocess environment
|
||||
|
||||
The subprocess environment assignment follows the same constraints:
|
||||
|
||||
```
|
||||
asio::io_context ctx;
|
||||
std::unordered_map<environment::key, environment::value> my_env =
|
||||
{
|
||||
{"SECRET", "THIS_IS_A_TEST"},
|
||||
{"PATH", {"/bin", "/usr/bin"}}
|
||||
};
|
||||
auto exe = find_executable("g++");
|
||||
process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));
|
||||
process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env));
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ctx;
|
||||
std::unordered_map<environment::key, environment::value> my_env =
|
||||
{
|
||||
{"SECRET", "THIS_IS_A_TEST"},
|
||||
{"PATH", {"/bin", "/usr/bin"}}
|
||||
};
|
||||
auto exe = find_executable("g++");
|
||||
process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));
|
||||
process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env));
|
||||
----
|
||||
28
doc/index.adoc
Normal file
28
doc/index.adoc
Normal file
@@ -0,0 +1,28 @@
|
||||
= boost.process
|
||||
Klemens Morgenstern <klemens.morgenstern@gmx.net>
|
||||
Version 2.0, 19.11.2024
|
||||
:source-highlighter: rouge
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:icons: font
|
||||
:idprefix:
|
||||
:docinfo: private-footer
|
||||
:source-highlighter: rouge
|
||||
:source-language: c++
|
||||
:example-caption: Example
|
||||
|
||||
:imagesdir: ./images
|
||||
|
||||
:leveloffset: +1
|
||||
|
||||
include::quickstart.adoc[]
|
||||
include::launcher.adoc[]
|
||||
|
||||
= Initializers
|
||||
|
||||
include::start_dir.adoc[]
|
||||
include::stdio.adoc[]
|
||||
include::env.adoc[]
|
||||
|
||||
include::version2.adoc[]
|
||||
include::acknowledgements.adoc[]
|
||||
126
doc/launcher.adoc
Normal file
126
doc/launcher.adoc
Normal file
@@ -0,0 +1,126 @@
|
||||
= 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.
|
||||
|
||||
[cols="1,1,1,1"]
|
||||
|===
|
||||
|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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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.
|
||||
|
||||
NOTE: 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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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:
|
||||
|
||||
image::posix_success.svg[]
|
||||
|
||||
The call sequence when fork fails:
|
||||
|
||||
image::posix_fork_err.svg[]
|
||||
|
||||
The call sequence when exec fails:
|
||||
|
||||
image::posix_exec_err.svg[]
|
||||
|
||||
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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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
|
||||
'''
|
||||
@@ -1,18 +0,0 @@
|
||||
[library Boost.Process
|
||||
[quickbook 1.5]
|
||||
[authors [Morgenstern, Klemens David]]
|
||||
[copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 Julio M. Merino Vidal, Ilya Sokolov, Felipe Tanus, Jeff Flinn, Boris Schaeling, 2016 Klemens D. Morgenstern]
|
||||
[id process]
|
||||
[dirname process]
|
||||
[license
|
||||
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)
|
||||
]
|
||||
]
|
||||
|
||||
[note Process v1 will be deprecated in the next release (1.88). Use v2 for new projects.]
|
||||
|
||||
[include v1.qbk]
|
||||
[include v2.qbk]
|
||||
[include acknowledgements.qbk]
|
||||
113
doc/quickstart.adoc
Normal file
113
doc/quickstart.adoc
Normal file
@@ -0,0 +1,113 @@
|
||||
= 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
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// process(asio::any_io_executor, filesystem::path, range<string> args, AdditionalInitializers...)
|
||||
asio::io_context ctx;
|
||||
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()`.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
process proc("/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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
process proc("/bin/totally-not-a-virus", {});
|
||||
proc.terminate();
|
||||
----
|
||||
|
||||
`.request_exit` will ask the subprocess to shutdown (`SIGTERM` on posix),
|
||||
which the subprocess might ignore.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
process proc("/bin/bash", {});
|
||||
proc.request_exit();
|
||||
proc.wait();
|
||||
----
|
||||
|
||||
`.interrupt` will send an SIGINT to the subprocess, which a subprocess might
|
||||
interpret as a signal to shutdown.
|
||||
|
||||
WARNING: interrupt requires the initializer `windows::create_new_process_group` to be set on windows
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
process proc("/usr/bin/addr2line", {});
|
||||
proc.request_exit();
|
||||
proc.wait();
|
||||
----
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:execute Execute functions]
|
||||
|
||||
Process v2 provides `execute` and `async_execute` functions that can be used for managed executions.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
assert(execute(process("/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
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::awaitable<void> compile()
|
||||
{
|
||||
co_await async_execute(process("/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>
|
||||
}
|
||||
----
|
||||
<1> After 10 seconds send a request_exit.
|
||||
<2> After 20 seconds terminate
|
||||
|
||||
14
doc/start_dir.adoc
Normal file
14
doc/start_dir.adoc
Normal file
@@ -0,0 +1,14 @@
|
||||
== `process_start_dir`
|
||||
|
||||
The easier initializer to use is `process_start_dir`:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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.
|
||||
|
||||
WARNING: If your path is relative, it may fail on posix, because the directory is changed before a call to execve.
|
||||
81
doc/stdio.adoc
Normal file
81
doc/stdio.adoc
Normal file
@@ -0,0 +1,81 @@
|
||||
= 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:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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;
|
||||
|
||||
system::error_code ec;
|
||||
rp.read(asio::dynamic_buffer(output), ec);
|
||||
assert(ec == asio::eof);
|
||||
proc.wait();
|
||||
----
|
||||
|
||||
readable pipes can be assigned to `out` an `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:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ctx;
|
||||
// ignore stderr
|
||||
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` returning that native handle type is valid, too.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ctx;
|
||||
// ignore stderr
|
||||
asio::ip::tcp::socket sock{ctx};
|
||||
connect_my_socket(sock);
|
||||
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.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
popen proc(executor, "/usr/bin/addr2line, {argv[0]});
|
||||
asio::write(proc, asio::buffer("main\n"));
|
||||
std::string line;
|
||||
asio::read_until(proc, asio::dynamic_buffer(line), '\n');
|
||||
----
|
||||
|
||||
11
doc/v1.qbk
11
doc/v1.qbk
@@ -1,11 +0,0 @@
|
||||
[section:v1 Process V1]
|
||||
|
||||
[include v1/introduction.qbk]
|
||||
[include v1/concepts.qbk]
|
||||
[include v1/tutorial.qbk]
|
||||
[include v1/design.qbk]
|
||||
[include v1/extend.qbk]
|
||||
[include v1/faq.qbk]
|
||||
[xinclude reference_v1.xml]
|
||||
|
||||
[endsect]
|
||||
@@ -1,82 +0,0 @@
|
||||
[section:concepts Concepts]
|
||||
In this section, some of the underlying concepts of the operating system used in this library, will be explained.
|
||||
In the following chapters we will presume knowledge of that. Though please note,
|
||||
that this is a short summary and not conclusive of everything that can be done.
|
||||
|
||||
The goal of this library is to implement a portable wrapper, so that we will explain mostly what
|
||||
windows and posix have in common.
|
||||
|
||||
[section:pipes Pipes]
|
||||
Pipes are a facility for communication between different threads, processes and in some cases machines, the operating system provides.
|
||||
|
||||
The typical feature of a pipe is, that it is one channel, to which two handles are given, one for reading (source), one for writing (sink).
|
||||
In that it is different than other facilities (like sockets) and provides another way to manage the connectivity: if one side of the pipe is closed
|
||||
(i.e. the pipe is broken), the other is notified.
|
||||
|
||||
Pipes are typically used for interprocess communication. The main reason is, that pipes can be directly assigned to the process stdio, i.e. stderr, stdin and stdout.
|
||||
Additionally, half of the pipe can be inherited to the child process and closed in the father process. This will cause the pipe to be broken when the child process exits.
|
||||
|
||||
Though please note, that if the same thread reads and writes to a pipe, it will only talk to itself.
|
||||
|
||||
[section:anonymous Anonymous Pipes]
|
||||
|
||||
The most common pipes are anonymous. Since they have no name,
|
||||
a handle to them can only be obtained from duplicating either handle.
|
||||
|
||||
In this library the following functions are used for the creation of unnamed pipes:
|
||||
|
||||
* [@http://pubs.opengroup.org/onlinepubs/7908799/xsh/pipe.html posix]
|
||||
* [@https://msdn.microsoft.com/de-de/library/windows/desktop/aa365152.aspx windows]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:named Named Pipes]
|
||||
|
||||
As the name suggests, named pipes have a string identifier. This means that a
|
||||
handle to them can be obtained with the identifier, too.
|
||||
|
||||
The implementation on posix uses [@http://pubs.opengroup.org/onlinepubs/009695399/functions/mkfifo.html fifos],
|
||||
which means, that the named pipe behaves like a file.
|
||||
|
||||
Windows does provide a facility called [@https://msdn.microsoft.com/en-us/library/windows/desktop/aa365150(v=vs.85).aspx named pipes],
|
||||
which also have file-like names, but are in a different scope than the actual file system.
|
||||
|
||||
[note The main reason named pipes are part of this library, is because they need to be internally used for asynchronous communication on windows.]
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[section:process Processes]
|
||||
|
||||
A process is an independently executable entity, which is different from a thread, in that it has its own resources.
|
||||
Those include memory and hardware resources.
|
||||
|
||||
Every process is identified by a unique number[footnote it is unique as long as the process is active], called the process identification digit, `pid`.
|
||||
|
||||
[section:exit_code Exit code]
|
||||
A process will return an integer value indicating whether it was successful. On posix
|
||||
there are more codes associated with that, but not so on windows. Therefore there is no such encoding currently in the library.
|
||||
However an exit code of zero means the process was successful, while one different than zero indicates an error.
|
||||
[endsect]
|
||||
|
||||
[section:termination Termination]
|
||||
Processes can also be forced to exit. There are two ways to do this, signal the process to do so and wait, and just terminate the process without conditions.
|
||||
|
||||
Usually the first approach is to signal an exit request, but windows - unlike posix - does not provide a consistent way to do this. Hence this is not part of the
|
||||
library and only the hard terminate is.
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:env Environment]
|
||||
|
||||
The environment is a map of variables local to every process. The most significant one for this library
|
||||
is the `PATH` variable, which contains a list of paths, that ought to be searched for executables. A shell will do this automatically,
|
||||
while this library provides a function for that.
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,94 +0,0 @@
|
||||
[section:design Design Rationale]
|
||||
[section Scope]
|
||||
This library is meant to give a wrapper around the different OS-specific methods
|
||||
to launch processes. Its aim is to provide all functionality that is available on
|
||||
those systems and allow the user to do all related things, which require using the OS APIs.
|
||||
|
||||
[*This library does not try to provide a full library for everything process related.]
|
||||
In many discussions the proposal was made to build boost.process into a DSEL [footnote Domain Specific Embedded Language] of some sort.
|
||||
This is not the goal, it rather provides the facilities to build such a DSEL-library on top of it.
|
||||
Therefore the library also does [*not] force any particular use (such as only asynchronous communication) on its user.
|
||||
It rather could be integrated with such a library.
|
||||
|
||||
[endsect]
|
||||
[section Interface Style]
|
||||
|
||||
Boost.Process does use a very particular style when constructing a process.
|
||||
This is because a process holds many properties, which are not members of the actual child class.
|
||||
Those properties are in many cases not accessible by the father process, for example when using environments.
|
||||
Here the child process can modify its own environment, but there is no way for the father process to know.
|
||||
That means, that a child process has properties that cannot be accessed in C++.
|
||||
|
||||
This now leads to the two styles supported and mixed by this library. Overloading and properties.
|
||||
Consider that you may want to launch a process passing a number of arguments. This is supported in both styles, and would look like this:
|
||||
|
||||
```
|
||||
system("gcc", "--version"); //overloading
|
||||
system("gcc", args={"--version"}); //property style.
|
||||
```
|
||||
|
||||
Both styles can also be mixed in some cases.
|
||||
|
||||
```
|
||||
system("gcc", "-c", args+={"main.cpp"});
|
||||
```
|
||||
|
||||
In the following section the available styles will be described. Note that the
|
||||
overload style is implemented via type traits, so the types will be listed.
|
||||
|
||||
[caution There is no guarantee in which order the arguments will be applied!
|
||||
There is however a guarantee for arguments belonging together, i.e. the string
|
||||
argument and the args property will be evaluated in the order given.]
|
||||
|
||||
[endsect]
|
||||
[section:arg_cmd_style Arguments/Command Style]
|
||||
|
||||
When passing arguments to the process, two styles are provided, the cmd-style and the exe-/args-style.
|
||||
|
||||
The cmd style will interpret the string as a sequence of the exe and arguments and parse them as such, while the exe-/args-style will
|
||||
interpret each string as an argument.
|
||||
|
||||
[table:id Cmd vs Exe/Args
|
||||
[[String] [Cmd] [Exe/Args]]
|
||||
[["gcc --version"] [{"gcc", "--version"}] [{"\\"gcc --version\\""}]]
|
||||
]
|
||||
|
||||
When using the overloading variant, a single string will result in a cmd interpretation,
|
||||
several strings will yield a exe-args interpretation. Both versions can be set explicitly:
|
||||
|
||||
```
|
||||
system("grep -c false /etc/passwd"); //cmd style
|
||||
system("grep", "-c", "false", "/etc/passwd"); //exe-/args-
|
||||
|
||||
system(cmd="grep -c false /etc/passwd"); //cmd style
|
||||
system(exe="grep", args={"-c", "false", "/etc/passwd"}); //exe-/args-
|
||||
```
|
||||
|
||||
[note If a '"' sign is used in the argument style, it will be passed as part of the argument.
|
||||
If the same effect is wanted with the cmd syntax, it ought to be escaped, i.e. '\\\"'. ]
|
||||
[note The `PATH` variable will automatically be searched in the command style,
|
||||
but the one of the launching process, not the one passed to the child process.]
|
||||
[endsect]
|
||||
|
||||
[section:plat_ext Extensions]
|
||||
|
||||
The simplest form to extend functionality is to provide another handler, which
|
||||
will be called on the respective events on process launching. The names are:
|
||||
|
||||
*`boost::process::v1::on_setup`
|
||||
*`boost::process::v1::on_error`
|
||||
*`boost::process::v1::on_success`
|
||||
|
||||
|
||||
As an example:
|
||||
|
||||
```
|
||||
child c("ls", on_setup([](){cout << "On Setup" << endl;}));
|
||||
```
|
||||
|
||||
|
||||
[note On posix all those callbacks will be handled by this process, not the created one.
|
||||
This is different for the posix extensions, which can be executed on the forked process.]
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,212 +0,0 @@
|
||||
[def __on_exit__ [globalref boost::process::v1::on_exit on_exit]]
|
||||
[def __on_success__ [globalref boost::process::v1::extend::on_success ex::on_success]]
|
||||
[def __child__ [classref boost::process::v1::child child]]
|
||||
[def __handler__ [classref boost::process::v1::extend::handler handler]]
|
||||
[def __on_success__ [memberref boost::process::v1::extend::handler::on_success on_success]]
|
||||
[def __posix_executor__ [classref boost::process::v1::extend::posix_executor ex::posix_executor]]
|
||||
[def __windows_executor__ [classref boost::process::v1::extend::windows_executor ex::windows_executor]]
|
||||
[def __io_context__ [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_context.html boost::asio::io_context]]
|
||||
[def __require_io_context__ [classref boost::process::v1::extend::require_io_context ex::require_io_context]]
|
||||
[def __async_handler__ [classref boost::process::v1::extend::async_handler ex::async_handler]]
|
||||
[def __get_io_context__ [funcref boost::process::v1::extend::get_io_context ex::get_io_context]]
|
||||
|
||||
[section:extend Extensions]
|
||||
To extend the library, the header [headerref boost/process/extend.hpp extend] is provided.
|
||||
|
||||
It only provides the explicit style for custom properties, but no implicit style.
|
||||
|
||||
What this means is, that a custom initializer can be implemented, a reference which can be passed to one of the launching functions.
|
||||
If a class inherits [classref boost::process::v1::extend::handler] it will be regarded as an initializer and thus directly put into the sequence
|
||||
the executor gets passed.
|
||||
|
||||
[section:structure Structure]
|
||||
|
||||
The executor calls different handlers of the initializers during the process launch.
|
||||
The basic structure consists of three functions, as given below:
|
||||
|
||||
* [globalref boost::process::v1::extend::on_setup on_setup]
|
||||
* [globalref boost::process::v1::extend::on_error on_error]
|
||||
* [globalref boost::process::v1::extend::on_success on_success]
|
||||
|
||||
'''
|
||||
<imagedata fileref="boost_process/windows_exec.svg"/>
|
||||
'''
|
||||
|
||||
Additionally posix provides three more handlers, listed below:
|
||||
|
||||
* [globalref boost::process::v1::extend::on_fork_error on_fork_error]
|
||||
* [globalref boost::process::v1::extend::on_exec_setup on_exec_setup]
|
||||
* [globalref boost::process::v1::extend::on_exec_error on_exec_error]
|
||||
|
||||
For more information see the reference of [classref boost::process::v1::extend::posix_executor posix_executor].
|
||||
|
||||
[endsect]
|
||||
[section:simple Simple extensions]
|
||||
|
||||
The simplest extension just takes a single handler, which can be done in a functional style.
|
||||
So let's start with a simple hello-world example, while we use a C++14 generic lambda.
|
||||
|
||||
```
|
||||
using namespace boost::process;
|
||||
namespace ex = bp::extend;
|
||||
|
||||
__child__ c("foo", __on_success__=[](auto & exec) {std::cout << "hello world" << std::endl;});
|
||||
```
|
||||
|
||||
Considering that lambdas can also capture values, data can easily be shared between handlers.
|
||||
|
||||
To see which members the executor has, refer to [classref boost::process::v1::extend::windows_executor windows_executor]
|
||||
and [classref boost::process::v1::extend::posix_executor posix_executor].
|
||||
|
||||
[note Combined with __on_exit__ this can also handle the process exit.]
|
||||
|
||||
[caution The posix handler symbols are not defined on windows.]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:handler Handler Types]
|
||||
|
||||
Since the previous example is in a functional style, it is not very reusable.
|
||||
To solve that problem, the [classref boost::process::v1::extend::handler handler] has an alias in the `boost::process::v1::extend` namespace, to be inherited.
|
||||
So let's implement the hello world example in a class.
|
||||
|
||||
```
|
||||
struct hello_world : __handler__
|
||||
{
|
||||
template<typename Executor>
|
||||
void __on_success__(Executor & exec) const
|
||||
{
|
||||
std::cout << "hello world" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
//in our function
|
||||
__child__ c("foo", hello_world());
|
||||
```
|
||||
|
||||
[note The implementation is done via overloading, not overriding.]
|
||||
|
||||
Every handler not implemented defaults to [classref boost::process::v1::extend::handler handler], where an empty handler is defined for each event.
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section:async Asynchronous Functionality]
|
||||
|
||||
Since `boost.process` provides an interface for [@http://www.boost.org/doc/libs/release/libs/asio/ boost.asio],
|
||||
this functionality is also available for extensions. If the class needs the __io_context__ for some reason, the following code will do that.
|
||||
|
||||
```
|
||||
struct async_foo : __handler__, __require_io_context__
|
||||
{
|
||||
template<typename Executor>
|
||||
void on_setup(Executor & exec)
|
||||
{
|
||||
__io_context__ & ios = __get_io_context__(exec.seq); //gives us a reference and a compiler error if not present.
|
||||
//do something with ios
|
||||
}
|
||||
};
|
||||
```
|
||||
[note Inheriting [globalref boost::process::v1::extend::require_io_context require_io_context] is necessary, so [funcref boost::process::v1::system system] provides one.]
|
||||
|
||||
Additionally the handler can provide a function that is invoked when the child process exits. This is done through __async_handler__.
|
||||
|
||||
[note [globalref boost::process::v1::extend::async_handler async_handler] implies [globalref boost::process::v1::extend::require_io_context require_io_context] .]
|
||||
|
||||
```
|
||||
struct async_bar : __handler, __async_handler__
|
||||
{
|
||||
template<typename Executor>
|
||||
std::function<void(int, const std::error_code&)> on_exit_handler(Executor & exec)
|
||||
{
|
||||
auto handler_ = this->handler;
|
||||
return [handler_](int exit_code, const std::error_code & ec)
|
||||
{
|
||||
std::cout << "hello world, I exited with " << exit_code << std::endl;
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
[caution `on_exit_handler` does not default and is always required when [classref boost::process::v1::extend::async_handler async_handler] is inherited. ]
|
||||
|
||||
[caution `on_exit_handler` uses `boost::asio::signal_set` to listen for SIGCHLD on posix. The application must not also register a signal handler for SIGCHLD using functions such as `signal()` or `sigaction()` (but using `boost::asio::signal_set` is fine). ]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:error Error handling]
|
||||
|
||||
If an error occurs in the initializers it shall be told to the executor and not handled directly. This is because
|
||||
the behaviour can be changed through arguments passed to the launching function. Hence the executor
|
||||
has the function `set_error`, which takes an [@http://en.cppreference.com/w/cpp/error/error_code std::error_code] and a string.
|
||||
Depending on the configuration of the executor, this may either throw, set an internal `error_code`, or do nothing.
|
||||
|
||||
So let's take a simple example, where we set a randomly chosen `error_code`.
|
||||
|
||||
```
|
||||
auto set_error = [](auto & exec)
|
||||
{
|
||||
std::error_code ec{42, std::system_category()};
|
||||
exec.set_error(ec, "a fake error");
|
||||
|
||||
};
|
||||
__child__ c("foo", on_setup=set_error);
|
||||
```
|
||||
|
||||
Since we do not specify the error-handling mode in this example, this will throw [classref boost::process::v1::process_error process_error].
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:exec_over Executor Overloading]
|
||||
|
||||
Now that we have a custom initializer, let's consider how we can handle differences between different executors.
|
||||
The distinction is between posix and windows and `char` and `wchar_t` on windows.
|
||||
One solution is to use the [@http://www.boost.org/doc/libs/master/boost/system/api_config.hpp BOOST_WINDOWS_API and BOOST_POSIX_API] macros,
|
||||
which are automatically available as soon as any process-header is included.
|
||||
|
||||
Another variant are the type aliases __posix_executor__ and __windows_executor__, where the executor, not on the current system is a forward-declaration.
|
||||
This works fine, because the function will never get invoked. So let's implement another example, which prints the executable name __on_success__.
|
||||
|
||||
```
|
||||
struct hello_exe : __handler__
|
||||
{
|
||||
template<typename Sequence>
|
||||
void __on_success__(__posix_executor__<Sequence> & exec)
|
||||
{
|
||||
std::cout << "posix-exe: " << exec.exe << std::endl;
|
||||
}
|
||||
|
||||
template<typename Sequence>
|
||||
void __on_success__(__windows_executor__<char, Sequence> & exec)
|
||||
{
|
||||
//note: exe might be a nullptr on windows.
|
||||
if (exec.exe != nullptr)
|
||||
std::cout << "windows-exe: " << exec.exe << std::endl;
|
||||
else
|
||||
std::cout << "windows didn't use exe" << std::endl;
|
||||
}
|
||||
|
||||
template<typename Sequence>
|
||||
void __on_success__(__windows_executor__<wchar_t, Sequence> & exec)
|
||||
{
|
||||
//note: exe might be a nullptr on windows.
|
||||
if (exec.exe != nullptr)
|
||||
std::wcout << L"windows-exe: " << exec.exe << std::endl;
|
||||
else
|
||||
std::cout << "windows didn't use exe" << std::endl;
|
||||
}
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
So given our example, the definitions with the non-native executor are still a template so that they will not be evaluated if not used. Hence this provides a
|
||||
way to implement system-specific code without using the preprocessor.
|
||||
|
||||
[note If you only write a partial implementation, e.g. only for __posix_executor__, the other variants will default to __handler__].
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,87 +0,0 @@
|
||||
[section:faq Frequently Asked Questions]
|
||||
|
||||
[section:dead_lock Why does this produce a deadlock?]
|
||||
|
||||
Now let's revisit our c++filt example and we will put in an obvious mistake.
|
||||
This might however be not as obvious for more complex applications.
|
||||
|
||||
```
|
||||
vector<string> demangle(vector<string> in)
|
||||
{
|
||||
|
||||
ipstream is;
|
||||
opstream os;
|
||||
child c("c++filt", std_out > is, std_in < os);
|
||||
|
||||
vector<string> data;
|
||||
for (auto & elem : data)
|
||||
{
|
||||
string line;
|
||||
getline(is, line);
|
||||
os << elem;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
We switched the read and write operation up, so that's causing a dead-lock.
|
||||
This locks immediately. This is because `c++filt` expects input, before
|
||||
outputting any data. The launching process on the other hand waits for its output.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:closep Why does the pipe not close?]
|
||||
|
||||
Now for another example, which might look correct, let's consider you want
|
||||
to use `ls` to read the current directory.
|
||||
|
||||
```
|
||||
ipstream is;
|
||||
child c("ls", std_out > is);
|
||||
|
||||
std::string file;
|
||||
while (is >> file)
|
||||
cout << "File: " << file << endl;
|
||||
|
||||
```
|
||||
|
||||
This will also deadlock, because the pipe does not close when the subprocess exits.
|
||||
So the `ipstream` will still look for data even though the process has ended.
|
||||
|
||||
[note It is not possible to use automatic pipe-closing in this library, because
|
||||
a pipe might be a file-handle (as for async pipes on windows).]
|
||||
|
||||
But, since pipes are buffered, you might get incomplete data if you do this:
|
||||
|
||||
```
|
||||
ipstream is;
|
||||
child c("ls", std_out > is);
|
||||
|
||||
std::string file;
|
||||
while (c.running())
|
||||
{
|
||||
is >> file;
|
||||
cout << "File: " << file << endl;
|
||||
}
|
||||
```
|
||||
|
||||
It is therefore highly recommended that you use the asynchronous API if you are
|
||||
not absolutely sure how the output will look.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:wchar_t When will the codecvt be used?]
|
||||
|
||||
Since windows does not use UTF-8 it is sometimes unavoidable to use the `wchar_t` version of the WinApi.
|
||||
To keep this library consistent it provides `wchar_t` support on posix also.
|
||||
|
||||
Since the posix api is purely `char` every `wchar_t` based type will be converted into `char`.
|
||||
|
||||
Windows on the other hand is more selective; the default is to use `char`,
|
||||
but if any parameter requires `wchar_t`, everything will be converted to `wchar_t`.
|
||||
This also includes `boost::filesystem::path`. Additionally, if the system does not provide
|
||||
the `char` api (as is the case with Windows CE) everything will also be converted.
|
||||
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,24 +0,0 @@
|
||||
[section:introduction Introduction]
|
||||
|
||||
Boost.Process is a library to manage system processes. It can be used to:
|
||||
|
||||
* create child processes
|
||||
* setup streams for child processes
|
||||
* communicate with child processes through streams (synchronously or asynchronously)
|
||||
* wait for processes to exit (synchronously or asynchronously)
|
||||
* terminate processes
|
||||
|
||||
Here's a simple example of how to start a program with Boost.Process:
|
||||
|
||||
[def ipstream [classref boost::process::v1::ipstream ipstream]]
|
||||
[def system [funcref boost::process::v1::system system]]
|
||||
[def std_out [globalref boost::process::v1::std_out std_out]]
|
||||
[def child [globalref boost::process::v1::child child]]
|
||||
[def boost/process.hpp [headerref boost/process.hpp boost/process.hpp]]
|
||||
[def std::string [@http://en.cppreference.com/w/cpp/string/basic_string std::string]]
|
||||
[def std::getline [@http://en.cppreference.com/w/cpp/string/basic_string/getline std::getline]]
|
||||
|
||||
[import ../../example/intro.cpp]
|
||||
[intro]
|
||||
|
||||
[endsect]
|
||||
@@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<programlisting>
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_setup">on_setup</methodname>(*this);
|
||||
|
||||
if (<methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>())
|
||||
{
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>());
|
||||
return <classname alt="boost::process::v1::child">child</classname>();
|
||||
}
|
||||
|
||||
pid = <ulink url="http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html">fork()</ulink>
|
||||
<methodname alt="boost::process::v1::extend::handler::on_setup">on_setup</methodname>(*this);
|
||||
|
||||
if (pid == -1) //fork error
|
||||
{
|
||||
<methodname alt="boost::process::v1::extend::posix_executor::set_error">set_error</methodname>(<functionname alt="boost::process::v1::extend::get_last_error">get_last_error</functionname>());
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_fork_error">on_fork_error</methodname>(*this, <methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>());
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>());
|
||||
return <classname alt="boost::process::v1::child">child</classname>()
|
||||
}
|
||||
else if (pid == 0) //child process
|
||||
{
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_exec_setup">on_exec_setup</methodname>(*this);
|
||||
<ulink url="http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html">execve</ulink>(exe, cmd_line, env);
|
||||
auto ec = <functionname alt="boost::process::v1::extend::get_last_error">get_last_error</functionname>();
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_exec_error">on_exec_error</methodname>(*this);
|
||||
|
||||
<emphasis>unspecified();</emphasis>//here the error is sent to the father process internally
|
||||
|
||||
<ulink url="http://en.cppreference.com/w/cpp/utility/program/exit">std::exit</ulink>(<ulink url="http://en.cppreference.com/w/c/program/EXIT_status">EXIT_FAILURE</ulink>);
|
||||
return <classname alt="boost::process::v1::child">child</classname>(); //for C++ compliance
|
||||
}
|
||||
|
||||
<classname alt="boost::process::v1::child">child</classname> c(pid, exit_code);
|
||||
|
||||
<emphasis>unspecified();</emphasis>//here, we read the error from the child process
|
||||
|
||||
if (<methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>())
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>());
|
||||
else
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_success</methodname>(*this);
|
||||
|
||||
//now we check again, because an on_success handler might've errored.
|
||||
if (<methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>())
|
||||
{
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::posix_executor::error">error</methodname>());
|
||||
return <classname alt="boost::process::v1::child">child</classname>();
|
||||
}
|
||||
else
|
||||
return c;
|
||||
</programlisting>
|
||||
@@ -1,425 +0,0 @@
|
||||
[def bp::system [funcref boost::process::v1::system bp::system]]
|
||||
[def bp::async_system [funcref boost::process::v1::async_system bp::async_system]]
|
||||
[def bp::spawn [funcref boost::process::v1::spawn bp::spawn]]
|
||||
[def bp::child [classref boost::process::v1::child bp::child]]
|
||||
[def bp::cmd [classref boost::process::v1::cmd bp::cmd]]
|
||||
[def bp::group [classref boost::process::v1::group bp::group]]
|
||||
[def bp::ipstream [classref boost::process::v1::ipstream bp::ipstream]]
|
||||
[def bp::opstream [classref boost::process::v1::opstream bp::opstream]]
|
||||
[def bp::pstream [classref boost::process::v1::pstream bp::pstream]]
|
||||
[def bp::pipe [classref boost::process::v1::pipe bp::pipe]]
|
||||
[def bp::async_pipe [classref boost::process::v1::async_pipe bp::async_pipe]]
|
||||
[def bp::search_path [funcref boost::process::v1::search_path bp::search_path]]
|
||||
[def boost_org [@www.boost.org "www.boost.org"]]
|
||||
[def std::system [@http://en.cppreference.com/w/cpp/utility/program/system std::system]]
|
||||
[def child_running [memberref boost::process::v1::child::running running]]
|
||||
[def child_wait [memberref boost::process::v1::child::wait wait]]
|
||||
[def child_wait_for [memberref boost::process::v1::child::wait_for wait_for]]
|
||||
[def child_exit_code [memberref boost::process::v1::child::exit_code exit_code]]
|
||||
[def group_wait_for [memberref boost::process::v1::group::wait_for wait_for]]
|
||||
[def bp::on_exit [globalref boost::process::v1::on_exit bp::on_exit]]
|
||||
[def bp::null [globalref boost::process::v1::null bp::null]]
|
||||
[def child_terminate [memberref boost::process::v1::child::terminate terminate]]
|
||||
[def group_terminate [memberref boost::process::v1::group::terminate terminate]]
|
||||
[def group_wait [memberref boost::process::v1::group::wait wait]]
|
||||
[def bp::std_in [globalref boost::process::v1::std_in bp::std_in]]
|
||||
[def bp::std_out [globalref boost::process::v1::std_out bp::std_out]]
|
||||
[def bp::std_err [globalref boost::process::v1::std_err bp::std_err]]
|
||||
[def io_service [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_service.html boost::asio::io_service]]
|
||||
[def asio_buffer [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/buffer.html boost::asio::buffer]]
|
||||
[def asio_async_read [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/async_read.html boost::asio::async_read]]
|
||||
[def bp::environment [classref boost::process::v1::basic_environment bp::environment]]
|
||||
[def bp::native_environment [classref boost::process::v1::basic_native_environment bp::native_environment]]
|
||||
[def boost::this_process::environment [funcref boost::this_process::environment boost::this_process::environment]]
|
||||
[def std::chrono::seconds [@http://en.cppreference.com/w/cpp/chrono/duration std::chrono::seconds]]
|
||||
[def std::vector [@http://en.cppreference.com/w/cpp/container/vector std::vector]]
|
||||
|
||||
[def __wait_for__ [memberref boost::process::v1::child::wait_for wait_for]]
|
||||
[def __wait_until__ [memberref boost::process::v1::child::wait_until wait_until]]
|
||||
[def __detach__ [memberref boost::process::v1::child::detach detach]]
|
||||
|
||||
[def __reference__ [link process.reference reference]]
|
||||
[def __concepts__ [link boost_process.concepts concepts]]
|
||||
|
||||
[def boost::asio::yield_context [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/yield_context.html boost::asio::yield_context]]
|
||||
[def boost::asio::coroutine [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/coroutine.html boost::asio::coroutine]]
|
||||
[def bp::env [globalref boost::process::v1::env bp::env]]
|
||||
|
||||
[section:tutorial Tutorial]
|
||||
|
||||
In this section we will go step by step through the different features of
|
||||
boost.process. For a full description see the __reference__ and the __concepts__ sections.
|
||||
|
||||
[section Starting a process]
|
||||
|
||||
We want to start a process, so let's start with a simple process. We will
|
||||
invoke the gcc compiler to compile a simple program.
|
||||
|
||||
With the standard library this looks like this.
|
||||
|
||||
```
|
||||
int result = std::system("g++ main.cpp");
|
||||
```
|
||||
|
||||
Which we can write exactly like this in boost.process.
|
||||
|
||||
```
|
||||
namespace bp = boost::process; //we will assume this for all further examples
|
||||
int result = bp::system("g++ main.cpp");
|
||||
```
|
||||
|
||||
If a single string is given (or the explicit form bp::cmd), it will be interpreted as a command line.
|
||||
That will cause the execution function to search the `PATH` variable to find the executable.
|
||||
The alternative is the `exe-args` style, where the first string will be interpreted as a filename (including the path),
|
||||
and the rest as arguments passed to said function.
|
||||
|
||||
[note For more details on the `cmd`/`exe-args` style look [link boost_process.design.arg_cmd_style here].]
|
||||
|
||||
So as a first step, we'll use the `exe-args` style.
|
||||
|
||||
```
|
||||
int result = bp::system("/usr/bin/g++", "main.cpp");
|
||||
```
|
||||
|
||||
With that syntax we still have "g++" hard-coded, so let's assume we get the string
|
||||
from an external source as `boost::process::v1::filesystem::path`, we can do this too.
|
||||
|
||||
```
|
||||
boost::process::v1::filesystem::path p = "/usr/bin/g++"; //or get it from somewhere else.
|
||||
int result = bp::system(p, "main.cpp");
|
||||
```
|
||||
|
||||
Now we might want to find the `g++` executable in the `PATH`-variable, as the `cmd` syntax would do.
|
||||
`Boost.process` provides a function to this end: bp::search_path.
|
||||
|
||||
```
|
||||
boost::process::v1::filesystem::path p = bp::search_path("g++"); //or get it from somewhere else.
|
||||
int result = bp::system(p, "main.cpp");
|
||||
```
|
||||
|
||||
[note [funcref boost::process::v1::search_path search_path] will search for any executable with that name.
|
||||
This also includes to add a file suffix on windows, such as `.exe` or `.bat`.]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:launch_mode Launch functions]
|
||||
|
||||
Given that our example used the [funcref boost::process::v1::system system] function,
|
||||
our program will wait until the child process is completed. This may be unwanted,
|
||||
especially since compiling can take a while.
|
||||
|
||||
In order to avoid that, boost.process provides several ways to launch a process.
|
||||
Besides the already mentioned [funcref boost::process::v1::system system] function and its
|
||||
asynchronous version [funcref boost::process::v1::async_system async_system],
|
||||
we can also use the [funcref boost::process::v1::spawn spawn] function or the
|
||||
[classref boost::process::v1::child child] class.
|
||||
|
||||
The [funcref boost::process::v1::spawn spawn] function launches a process and
|
||||
immediately detaches it, so no handle will be returned and the process will be ignored.
|
||||
This is not what we need for compiling, but maybe we want to entertain the user,
|
||||
while compiling:
|
||||
|
||||
```
|
||||
bp::spawn(bp::search_path("chrome"), boost_org);
|
||||
```
|
||||
|
||||
Now for the more sensible approach for compiling: a non-blocking execution.
|
||||
To implement that, we directly call the constructor of [classref boost::process::v1::child child].
|
||||
|
||||
```
|
||||
bp::child c(bp::search_path("g++"), "main.cpp");
|
||||
|
||||
while (c.child_running())
|
||||
do_some_stuff();
|
||||
|
||||
c.child_wait(); //wait for the process to exit
|
||||
int result = c.child_exit_code();
|
||||
```
|
||||
|
||||
So we launch the process, by calling the child constructor. Then we check and do other
|
||||
things while the process is running and afterwards get the exit code. The call
|
||||
to child_wait is necessary, to obtain it and tell the operating system, that no
|
||||
one is waiting for the process anymore.
|
||||
|
||||
[note You can also wait for a time span or until a time point with __wait_for__ and __wait_until__.]
|
||||
|
||||
[warning If you don't call wait on a child object, it will be terminated on destruction.
|
||||
This can be avoided by calling __detach__ beforehand]
|
||||
|
||||
[endsect]
|
||||
[section:error_handling Error]
|
||||
|
||||
Until now, we have assumed that everything works out, but it is not impossible,
|
||||
that "g++" is not present. That will cause the launch of the process to fail.
|
||||
The default behaviour of all functions is to throw a
|
||||
[@http://en.cppreference.com/w/cpp/error/system_error std::system_error] on failure.
|
||||
As with many other functions in this library, passing an [@http://en.cppreference.com/w/cpp/error/error_code std::error_code]
|
||||
will change the behaviour, so that instead of throwing an exception, the error will be assigned to the error code.
|
||||
|
||||
```
|
||||
std::error_code ec;
|
||||
bp::system("g++ main.cpp", ec);
|
||||
```
|
||||
[endsect]
|
||||
[section:io Synchronous I/O]
|
||||
|
||||
In the examples given above, we have only started a program, but did not consider the output.
|
||||
The default depends on the system, but usually this will just write it to the same output as the launching process.
|
||||
If this shall be guaranteed, the streams can be explicitly forwarded like this.
|
||||
|
||||
```
|
||||
bp::system("g++ main.cpp", bp::std_out > stdout, bp::std_err > stderr, bp::std_in < stdin);
|
||||
```
|
||||
|
||||
Now for the first example, we might want to just ignore the output, which can be done by redirecting it to the null-device.
|
||||
This can be achieved this way:
|
||||
|
||||
```
|
||||
bp::system("g++ main.cpp", bp::std_out > bp::null);
|
||||
```
|
||||
|
||||
Alternatively we can also easily redirect the output to a file:
|
||||
|
||||
```
|
||||
bp::system("g++ main.cpp", bp::std_out > "gcc_out.log");
|
||||
```
|
||||
|
||||
Now, let's take a more visual example for reading data.
|
||||
[@http://pubs.opengroup.org/onlinepubs/009696699/utilities/nm.html nm] is a tool on posix,
|
||||
which reads the outline, i.e. a list of all entry points, of a binary.
|
||||
Every entry point will be put into a single line, and we will use a pipe to read it.
|
||||
At the end an empty line is appended, which we use as the indication to stop reading.
|
||||
Boost.process provides the pipestream ([classref boost::process::v1::ipstream ipstream],
|
||||
[classref boost::process::v1::opstream opstream], [classref boost::process::v1::pstream pstream]) to
|
||||
wrap around the [classref boost::process::v1::pipe pipe] and provide an implementation of the
|
||||
[@http://en.cppreference.com/w/cpp/io/basic_istream std::istream],
|
||||
[@http://en.cppreference.com/w/cpp/io/basic_ostream std::ostream] and
|
||||
[@http://en.cppreference.com/w/cpp/io/basic_iostream std::iostream] interface.
|
||||
|
||||
```
|
||||
std::vector<std::string> read_outline(std::string & file)
|
||||
{
|
||||
bp::ipstream is; //reading pipe-stream
|
||||
bp::child c(bp::search_path("nm"), file, bp::std_out > is);
|
||||
|
||||
std::vector<std::string> data;
|
||||
std::string line;
|
||||
|
||||
while (c.child_running() && std::getline(is, line) && !line.empty())
|
||||
data.push_back(line);
|
||||
|
||||
c.child_wait();
|
||||
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
What this does is redirect the `stdout` of the process into a pipe and we read this
|
||||
synchronously.
|
||||
|
||||
[note You can do the same thing with [globalref boost::process::v1::std_err std_err].]
|
||||
|
||||
Now we get the name from `nm` and we might want to demangle it, so we use input and output.
|
||||
`nm` has a demangle option, but for the sake of the example, we'll use
|
||||
[@https://sourceware.org/binutils/docs/binutils/c_002b_002bfilt.html c++filt] for this.
|
||||
|
||||
```
|
||||
bp::opstream in;
|
||||
bp::ipstream out;
|
||||
|
||||
bp::child c("c++filt", std_out > out, std_in < in);
|
||||
|
||||
in << "_ZN5boost7process8tutorialE" << endl;
|
||||
std::string value;
|
||||
out >> value;
|
||||
|
||||
c.child_terminate();
|
||||
```
|
||||
|
||||
Now you might want to forward output from one process to another processes input.
|
||||
|
||||
```
|
||||
std::vector<std::string> read_demangled_outline(const std::string & file)
|
||||
{
|
||||
bp::pipe p;
|
||||
bp::ipstream is;
|
||||
|
||||
std::vector<std::string> outline;
|
||||
|
||||
//we just use the same pipe, so the output of nm is directly passed as input to c++filt
|
||||
bp::child nm(bp::search_path("nm"), file, bp::std_out > p);
|
||||
bp::child filt(bp::search_path("c++filt"), bp::std_in < p, bp::std_out > is);
|
||||
|
||||
std::string line;
|
||||
while (filt.running() && std::getline(is, line)) //when nm finished the pipe closes and c++filt exits
|
||||
outline.push_back(line);
|
||||
|
||||
nm.child_wait();
|
||||
filt.wait();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This forwards the data from `nm` to `c++filt` without your process needing to do anything.
|
||||
|
||||
[endsect]
|
||||
[section:async_io Asynchronous I/O]
|
||||
|
||||
Boost.process allows the usage of boost.asio to implement asynchronous I/O.
|
||||
If you are familiar with [@http://www.boost.org/doc/libs/release/libs/asio/ boost.asio] (which we highly recommend),
|
||||
you can use [classref boost::process::v1::async_pipe async_pipe] which is implemented
|
||||
as an I/O-Object and can be used like [classref boost::process::v1::pipe pipe] as shown above.
|
||||
|
||||
Now we get back to our compiling example. For `nm` we might analyze the output line by line,
|
||||
but the compiler output will just be put into one large buffer.
|
||||
|
||||
With [@http://www.boost.org/doc/libs/release/libs/asio/ boost.asio] this is what it looks like.
|
||||
|
||||
```
|
||||
io_service ios;
|
||||
std::vector<char> buf(4096);
|
||||
|
||||
bp::async_pipe ap(ios);
|
||||
|
||||
bp::child c(bp::search_path("g++"), "main.cpp", bp::std_out > ap);
|
||||
|
||||
asio_async_read(ap, asio_buffer(buf),
|
||||
[](const boost::system::error_code &ec, std::size_t size){});
|
||||
|
||||
ios.run();
|
||||
int result = c.exit_code();
|
||||
```
|
||||
|
||||
To make it easier, boost.process provides a simpler interface for that, so that the buffer can be passed directly,
|
||||
provided we also pass a reference to an io_service.
|
||||
|
||||
```
|
||||
io_service ios;
|
||||
std::vector<char> buf(4096);
|
||||
|
||||
bp::child c(bp::search_path("g++"), "main.cpp", bp::std_out > asio_buffer(buf), ios);
|
||||
|
||||
ios.run();
|
||||
int result = c.exit_code();
|
||||
```
|
||||
|
||||
[note Passing an instance of io_service to the launching function automatically cause it to wait asynchronously for the exit, so no call of
|
||||
[memberref boost::process::v1::child::wait wait] is needed.]
|
||||
|
||||
To make it even easier, you can use [@http://en.cppreference.com/w/cpp/thread/future std::future] for asynchronous operations
|
||||
(you will still need to pass a reference to a io_service) to the launching function, unless you use bp::system or bp::async_system.
|
||||
|
||||
Now we will revisit our first example and read the compiler output asynchronously:
|
||||
|
||||
```
|
||||
boost::asio::io_service ios;
|
||||
|
||||
std::future<std::string> data;
|
||||
|
||||
child c("g++", "main.cpp", //set the input
|
||||
bp::std_in.close(),
|
||||
bp::std_out > bp::null, //so it can be written without anything
|
||||
bp::std_err > data,
|
||||
ios);
|
||||
|
||||
|
||||
ios.run(); //this will actually block until the compiler is finished
|
||||
|
||||
auto err = data.get();
|
||||
```
|
||||
|
||||
[endsect]
|
||||
[section:group Groups]
|
||||
|
||||
When launching several processes, they can be grouped together.
|
||||
This will also apply for a child process, that launches other processes,
|
||||
if they do not modify the group membership. E.g. if you call `make` which
|
||||
launches other processes and call terminate on it,
|
||||
it will not terminate all the child processes of the child unless you use a group.
|
||||
|
||||
The two main reasons to use groups are:
|
||||
|
||||
# Being able to terminate child processes of the child process
|
||||
# Grouping several processes into one, just so they can be terminated at once
|
||||
|
||||
If we have a program like `make`, which does launch its own child processes,
|
||||
a call of child_terminate might not suffice. I.e. if we have a makefile launching `gcc`
|
||||
and use the following code, the `gcc` process will still run afterwards:
|
||||
|
||||
```
|
||||
bp::child c("make");
|
||||
if (!c.child_wait_for(std::chrono::seconds(10))) //give it 10 seconds
|
||||
c.child_terminate(); //then terminate
|
||||
```
|
||||
|
||||
So in order to also terminate `gcc` we can use a group.
|
||||
|
||||
```
|
||||
bp::group g;
|
||||
bp::child c("make", g);
|
||||
if (!g.group_wait_for(std::chrono::seconds(10)))
|
||||
g.group_terminate();
|
||||
|
||||
c.child_wait(); //to avoid a zombie process & get the exit code
|
||||
```
|
||||
|
||||
Now given the example, we still call child_wait to avoid a zombie process.
|
||||
An easier solution for that might be to use [funcref boost::process::v1::spawn spawn].
|
||||
|
||||
|
||||
To put two processes into one group, the following code suffices. Spawn already
|
||||
launches a detached process (i.e. without a child-handle), but they can be grouped,
|
||||
to that in the case of a problem, RAII is still a given.
|
||||
|
||||
```
|
||||
void f()
|
||||
{
|
||||
bp::group g;
|
||||
bp::spawn("foo", g);
|
||||
bp::spawn("bar", g);
|
||||
|
||||
do_something();
|
||||
|
||||
g.group_wait();
|
||||
};
|
||||
```
|
||||
|
||||
In the example, it will wait for both processes at the end of the function unless
|
||||
an exception occurs. I.e. if an exception is thrown, the group will be terminated.
|
||||
|
||||
|
||||
Please see the [headerref boost/process/group.hpp reference] for more information.
|
||||
|
||||
[endsect]
|
||||
[section:env Environment]
|
||||
|
||||
This library provides access to the environment of the current process and allows
|
||||
setting it for the child process.
|
||||
|
||||
```
|
||||
//get a handle to the current environment
|
||||
auto env = boost::this_process::environment();
|
||||
//add a variable to the current environment
|
||||
env["VALUE_1"] = "foo";
|
||||
|
||||
//copy it into an environment separate to the one of this process
|
||||
bp::environment env_ = env;
|
||||
//append two values to a variable in the new env
|
||||
env_["VALUE_2"] += {"bar1", "bar2"};
|
||||
|
||||
//launch a process with `env_`
|
||||
bp::system("stuff", env_);
|
||||
```
|
||||
|
||||
A more convenient way to modify the environment for the child is the
|
||||
[globalref boost::process::v1::env env] property, which can be used in the example as following:
|
||||
|
||||
```
|
||||
bp::system("stuff", bp::env["VALUE_1"]="foo", bp::env["VALUE_2"]+={"bar1", "bar2"});
|
||||
|
||||
```
|
||||
|
||||
Please see the [headerref boost/process/environment.hpp reference] for more information.
|
||||
|
||||
[endsect]
|
||||
[endsect]
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<programlisting>
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_setup">on_setup</methodname>(*this);
|
||||
|
||||
if (<methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>())
|
||||
{
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>());
|
||||
return <classname alt="boost::process::v1::child">child</classname>();
|
||||
}
|
||||
int err_code = <ulink url="https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx">CreateProcess</ulink>(
|
||||
exe,
|
||||
cmd_line,
|
||||
proc_attrs,
|
||||
thread_attrs,
|
||||
creation_flags,
|
||||
env,
|
||||
work_dir,
|
||||
startup_info,
|
||||
proc_info);
|
||||
|
||||
<classname alt="boost::process::v1::child">child</classname> c(proc_info, exit_code);
|
||||
|
||||
if (<methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>())
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>());
|
||||
else
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_success</methodname>(*this);
|
||||
|
||||
//now we check again, because an on_success handler might've errored.
|
||||
if (<methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>())
|
||||
{
|
||||
for (auto & s : seq)
|
||||
s.<methodname alt="boost::process::v1::extend::handler::on_error">on_error</methodname>(*this, <methodname alt="boost::process::v1::extend::windows_executor::error">error</methodname>());
|
||||
return <classname alt="boost::process::v1::child">child</classname>();
|
||||
}
|
||||
else
|
||||
return c;
|
||||
|
||||
</programlisting>
|
||||
11
doc/v2.qbk
11
doc/v2.qbk
@@ -1,11 +0,0 @@
|
||||
[section:v2 Process V2]
|
||||
|
||||
[include v2/introduction.qbk]
|
||||
[include v2/quickstart.qbk]
|
||||
[include v2/launcher.qbk]
|
||||
[include v2/start_dir.qbk]
|
||||
[include v2/stdio.qbk]
|
||||
[include v2/env.qbk]
|
||||
[xinclude reference_v2.xml]
|
||||
|
||||
[endsect]
|
||||
@@ -1,128 +0,0 @@
|
||||
[section:launchers 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.
|
||||
|
||||
[table:launchers Launcher overview
|
||||
[[Name] [Summary] [Default on] [Available on]]
|
||||
[[`windows::default_launcher`] [Launcher using `CreateProcessW`] [windows] [windows]]
|
||||
[[`windows::as_user_launcher`] [Launcher using `CreateProcessAsUserW`] [] [windows]]
|
||||
[[`windows::with_logon_launcher`] [Launcher using `CreateProcessWithLogonW`] [] [windows]]
|
||||
[[`windows::with_token_launcher`] [Launcher using `CreateProcessWithTokenW`] [] [windows]]
|
||||
[[`posix::default_launcher`] [Launcher using fork & an error pipe] [most of posix] [posix]]
|
||||
[[`posix::fork_and_forget`] [Launcher using fork without error pipe] [] [posix]]
|
||||
[[`posix::pdfork_launcher`] [Launcher using pdfork with an error pipe] [FreeBSD] [FreeBSD]]
|
||||
[[`posix::vfork_launcher`] [Launcher using 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.
|
||||
|
||||
[section:linux Linux Launchers]
|
||||
|
||||
The default and pdfork launchers on linux open an internal pipe to communicate errors that occur after forking back to the parent process.
|
||||
|
||||
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
|
||||
{
|
||||
// functions called from the parent process:
|
||||
|
||||
|
||||
// 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:
|
||||
'''
|
||||
<imagedata fileref="boost_process/posix_success.svg"/>
|
||||
'''
|
||||
|
||||
The call sequence when fork fails:
|
||||
'''
|
||||
<imagedata fileref="boost_process/posix_fork_err.svg"/>
|
||||
'''
|
||||
|
||||
The call sequence when exec fails:
|
||||
'''
|
||||
<imagedata fileref="boost_process/posix_exec_err.svg"/>
|
||||
'''
|
||||
|
||||
The launcher will close all non-whitelisted file descriptors after `on_exec_setup`.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:windows 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);
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
[note All the additional launchers for windows inherit `default_launcher`]
|
||||
|
||||
The call sequence is as follows:
|
||||
'''
|
||||
<imagedata fileref="boost_process/windows_exec.svg"/>
|
||||
'''
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[endsect]
|
||||
@@ -1,123 +0,0 @@
|
||||
[section:quickstart 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
|
||||
|
||||
```
|
||||
// process(asio::any_io_executor, filesystem::path, range<string> args, AdditionalInitializers...)
|
||||
asio::io_context ctx;
|
||||
process proc(ctx, "/usr/bin/cp", {"source.txt", "target.txt"});
|
||||
```
|
||||
|
||||
|
||||
The started process can then be awaited or terminated.
|
||||
|
||||
[section:lifetime 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()`.
|
||||
|
||||
```
|
||||
process proc("/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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:signal 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.
|
||||
|
||||
```
|
||||
process proc("/bin/totally-not-a-virus", {});
|
||||
proc.terminate();
|
||||
```
|
||||
|
||||
`.request_exit` will ask the subprocess to shutdown (`SIGTERM` on posix),
|
||||
which the subprocess might ignore.
|
||||
|
||||
```
|
||||
process proc("/bin/bash", {});
|
||||
proc.request_exit();
|
||||
proc.wait();
|
||||
```
|
||||
|
||||
`.interrupt` will send an SIGINT to the subprocess, which a subprocess might
|
||||
interpret as a signal to shutdown.
|
||||
|
||||
[warning interrupt requires the initializer `windows::create_new_process_group` to be set]
|
||||
|
||||
```
|
||||
process proc("/usr/bin/addr2line", {});
|
||||
proc.request_exit();
|
||||
proc.wait();
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:execute Execute functions]
|
||||
|
||||
Process v2 provides `execute` and `async_execute` functions that can be used for managed executions.
|
||||
|
||||
```
|
||||
assert(execute(process("/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
|
||||
|
||||
```
|
||||
asio::io_context ctx;
|
||||
asio::steady_timer timeout{ctx, std::chrono::seconds(10)};
|
||||
|
||||
asio::cancellation_signal sig;
|
||||
async_execute(process("/usr/bin/g++", {"hello_world.cpp"}),
|
||||
asio::bind_cancellation_slot(sig.slot(),
|
||||
[&](error_code ec, int exit_code)
|
||||
{
|
||||
timeout.cancel(); // we're done earlier
|
||||
}));
|
||||
|
||||
timeout.async_wait(
|
||||
[&](error_code ec)
|
||||
{
|
||||
if (ec) // we were cancelled, do nothing
|
||||
return ;
|
||||
sig.emit(asio::cancellation_type::partial);
|
||||
// request exit first, but terminate after another 10 sec
|
||||
timeout.expires_after(std::chrono::seconds(10));
|
||||
timeout.async_wait(
|
||||
[&](error_code ec)
|
||||
{
|
||||
if (!ec)
|
||||
sig.emit(asio::cancellation_type::terminal);
|
||||
});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,16 +0,0 @@
|
||||
[section:start_dir process_start_dir]
|
||||
|
||||
The easier initializer to use is `process_start_dir`:
|
||||
|
||||
```
|
||||
asio::io_context ctx;
|
||||
process ls(ctx, "/ls", {}, process_start_dir("/home"));
|
||||
ls.wait();
|
||||
```
|
||||
|
||||
This will run `ls` in the folder `/home` instead of the current folder.
|
||||
|
||||
[warning If your path is relative, it may fail on posix, because the directory is changed before a call to execve.]
|
||||
|
||||
|
||||
[endsect]
|
||||
@@ -1,89 +0,0 @@
|
||||
[section:stdio 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.
|
||||
|
||||
[section:pipe Pipe]
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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;
|
||||
|
||||
system::error_code ec;
|
||||
rp.read(asio::dynamic_buffer(output), ec);
|
||||
assert(ec == asio::eof);
|
||||
proc.wait();
|
||||
```
|
||||
|
||||
readable pipes can be assigned to `out` an `err`, while writable_pipes can be assigned to `in`.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:file `FILE*`]
|
||||
|
||||
`FILE*` can also be used for either side; this allows the `stdin`, `stderr`, `stdout` macros to be used:
|
||||
|
||||
```
|
||||
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();
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:null `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.
|
||||
|
||||
```
|
||||
asio::io_context ctx;
|
||||
// ignore stderr
|
||||
process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, {}, nullptr});
|
||||
proc.wait();
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[section:native_handle `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` returning that native handle type is valid, too.
|
||||
|
||||
|
||||
```
|
||||
asio::io_context ctx;
|
||||
// ignore stderr
|
||||
asio::ip::tcp::socket sock{ctx};
|
||||
connect_my_socket(sock);
|
||||
process proc(ctx, "~/not-a-virus", {}, process_stdio{sock, sock, nullptr});
|
||||
proc.wait();
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:popen 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.
|
||||
|
||||
```
|
||||
popen proc(executor, "/usr/bin/addr2line, {argv[0]});
|
||||
asio::write(proc, asio::buffer("main\n"));
|
||||
std::string line;
|
||||
asio::read_until(proc, asio::dynamic_buffer(line), '\n');
|
||||
```
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -1,4 +1,4 @@
|
||||
[section:introduction Introduction]
|
||||
= Version 2
|
||||
|
||||
Boost.process V2 is an redesign of boost.process, based on previous
|
||||
design mistakes & improved system APIs.
|
||||
@@ -13,47 +13,52 @@ The major changes are
|
||||
* separate compilation
|
||||
* fd safe by default
|
||||
|
||||
[section:simplified Simplified Interface]
|
||||
Version 2 is now the defauled. 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"};
|
||||
[source,cpp]
|
||||
----
|
||||
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:
|
||||
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_io{nullptr, {}, {}}, process_environment(my_env));
|
||||
[source,cpp]
|
||||
----
|
||||
extern std::unordered_map<std::string, std::string> my_env;
|
||||
extern asio::io_context ctx;
|
||||
process proc(ctx, "./test", {"--help"}, process_io{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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:pidfd_open pidfd_open]
|
||||
== `pidfd_open`
|
||||
|
||||
Since process v1 came out, linux has moved along and added pidfd_open which allows users to get a
|
||||
file descriptor for a process. This is much more reliable since it is not as easy to miss as a `SIGCHLD`.
|
||||
FreeBSD has a similar feature with `pdfork` which is also supported, while windows has provided `HANDLE`s
|
||||
for processes all along.
|
||||
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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:asio Full asio integration]
|
||||
|
||||
== 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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:unreliable Unreliable functionality]
|
||||
== Unreliable functionality
|
||||
|
||||
Certain parts of boost.process were not as reliable as they should've been.
|
||||
|
||||
@@ -63,17 +68,12 @@ 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 not safely be implemented with an async_wait + timeout.
|
||||
|
||||
[endsect]
|
||||
== UTF-8
|
||||
|
||||
[section:utf8 UTF-8]
|
||||
Instead of using ascii-APIs on windows, process V2 just assumes UTF-8 everywhere
|
||||
and uses the UTF-16 APIs.
|
||||
|
||||
["UTF-8 or GTFO]--Vinnie Falco
|
||||
|
||||
Instead of using ascii-APIs on windows, process V2 just assumes UTF-8 everywhere.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:limit_fd Fd safe by default]
|
||||
== 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.
|
||||
@@ -81,6 +81,3 @@ 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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <boost/system/api_config.hpp>
|
||||
|
||||
#if !defined(BOOST_PROCESS_VERSION)
|
||||
#define BOOST_PROCESS_VERSION 1
|
||||
#define BOOST_PROCESS_VERSION 2
|
||||
#endif
|
||||
|
||||
#if BOOST_PROCESS_VERSION == 1
|
||||
|
||||
@@ -68,7 +68,7 @@ BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
|
||||
#if !defined(BOOST_PROCESS_VERSION)
|
||||
#define BOOST_PROCESS_VERSION 1
|
||||
#define BOOST_PROCESS_VERSION 2
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user