- What is reproc?
- Features
- Questions
- Installation
- Dependencies
- CMake options
- Documentation
- Error handling
- Multithreading
- Gotchas
reproc (Redirected Process) is a cross-platform C/C++ library that simplifies starting, stopping and communicating with external programs. The main use case is executing command line applications directly from C or C++ code and retrieving their output.
reproc consists out of two libraries: reproc and reproc++. reproc is a C99 library that contains the actual code for working with external programs. reproc++ depends on reproc and adapts its API to an idiomatic C++11 API. It also adds a few extras that simplify working with external programs from C++.
- Start any program directly from C or C++ code.
- Communicate with a program via its standard streams.
- Wait for a program to exit or forcefully stop it yourself. When forcefully stopping a process you can either allow the process to clean up its resources or stop it immediately.
- The core library (reproc) is written in C99. An optional C++11 wrapper library (reproc++) with extra features is available for use in C++ applications.
- Multiple installation methods. Either build reproc as part of your project or use a system installed version of reproc.
If you have any questions after reading the readme and documentation you can either make an issue or ask questions directly in the reproc gitter channel.
There are multiple ways to get reproc into your project. One way is to build reproc as part of your project using CMake. To do this, we first have to get the reproc source code into the project. This can be done using any of the following options:
- If you're using CMake 3.11 or later you can use the CMake
FetchContent
API to download reproc when running CMake. See https://cliutils.gitlab.io/modern-cmake/chapters/projects/fetch.html for an example. - Another option is to include reproc's repository as a git submodule. https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html provides more information.
- A very simple solution is to just include reproc's source code in your
repository. You can download a zip of the source code without the git history
and add it to your repository in a separate directory (reproc itself uses the
external
directory).
After including reproc's source code in your project, it can be built from the root CMakeLists.txt file as follows:
add_subdirectory(<path-to-reproc>) # For example: add_subdirectory(external/reproc)
Options can be specified before calling add_subdirectory
(omit FORCE
if you
don't want to override an existing value in the cache):
set(REPROC++ ON CACHE BOOL "" FORCE)
add_subdirectory(<path-to-reproc>)
You can also depend on a system installed version of reproc. You can either build and install reproc to your system yourself or install reproc via a package manager. reproc is available in the following package repositories:
- Arch User Repository (https://aur.archlinux.org/packages/reproc)
- vcpkg (https://github.com/microsoft/vcpkg/tree/master/ports/reproc)
After installing reproc to the system your build system will have to find it. reproc provides both CMake config files and pkg-config files to simplify finding a system installed version of reproc using CMake and pkg-config respectively. Note that reproc and reproc++ are separate libraries and as a result have separate config files as well. Make sure to search for the one you need.
To find a system installation of reproc using CMake:
find_package(reproc) # Find reproc.
find_package(reproc++) # Find reproc++.
After building reproc as part of your project or finding a system installed reproc, you can link against it from within your CMakeLists.txt file as follows:
target_link_libraries(myapp reproc) # Link against reproc.
target_link_libraries(myapp reproc++) # Link against reproc++.
Building reproc requires CMake 3.13 or higher.
By default, reproc has a single dependency on pthreads on POSIX systems. However, the dependency is included in both reproc's CMake config and pkg-config files so it should be picked up by your build system automatically.
reproc's build can be configured using the following CMake options:
-
REPROC++
: Build reproc++ (default:OFF
). -
REPROC_TEST
: Build tests (default:OFF
).Run the tests by building the
test
target or running thetests
executable from within the build directory. -
REPROC_EXAMPLES
: Build examples (default:OFF
).The built executables will be located in the examples folder of each project subdirectory in the build directory.
REPROC_INSTALL
: Generate installation rules (default:ON
unlessBUILD_SHARED_LIBS
is false and reproc is built viaadd_subdirectory
).REPROC_INSTALL_CMAKECONFIGDIR
: CMake config files installation directory (default:${CMAKE_INSTALL_LIBDIR}/cmake
).REPROC_INSTALL_PKGCONFIG
: Install pkg-config files (default:ON
)REPROC_INSTALL_PKGCONFIGDIR
: pkg-config files installation directory (default:${CMAKE_INSTALL_LIBDIR}/pkgconfig
).
POSIX only:
-
REPROC_MULTITHREADED
: Usepthread_sigmask
instead ofsigprocmask
(default:ON
).sigprocmask
's behaviour is only defined for single-threaded programs. When using multiple threads,pthread_sigmask
should be used instead. Because we cannot determine whether reproc will be used in a multi-threaded program when building reproc,REPROC_MULTITHREADED
is enabled by default to guarantee defined behaviour. Users that know for certain their program will only use a single thread can opt to disableREPROC_MULTITHREADED
.When using reproc via CMake
add_subdirectory
andREPROC_MULTITHREADED
is enabled, reproc will only callfind_package(Threads)
if the user has not calledfind_package(Threads)
himself. TheTHREADS_PREFER_PTHREAD_FLAG
variable influences the behaviour offind_package(Threads)
. if it is not defined, reproc's build enables it before callingfind_package(Threads)
.
REPROC_SANITIZERS
: Build with sanitizers (default:OFF
).REPROC_TIDY
: Run clang-tidy when building (default:OFF
).REPROC_WARNINGS_AS_ERRORS
: Add -Werror or equivalent to the compile flags and clang-tidy (default:OFF
).
Each function and class is documented extensively in its header file. Examples can be found in the examples subdirectory of reproc and reproc++.
Most functions in reproc's API return REPROC_ERROR
. The REPROC_ERROR
enum
represents all possible errors that can occur when calling reproc API functions.
Not all errors apply to each function so the documentation of each function
includes a section detailing which errors can occur. System errors are
represented by REPROC_ERROR_SYSTEM
. The reproc_system_error
error can be
used to retrieve the actual system error. To get a string representation of the
error, pass the error code to reproc_strerror
. If the error code passed to
reproc_strerror
is REPROC_ERROR_SYSTEM
, reproc_strerror
returns a string
representation of the error returned by reproc_system_error
.
reproc++'s API integrates with the C++ standard library error codes mechanism
(std::error_code
and std::error_condition
). All functions in reproc++'s API
return std::error_code
values that contain the actual system error that
occurred. This means reproc_system_error
is not necessary in reproc++ since
the returned error codes store the actual system error instead of the value of
REPROC_ERROR_SYSTEM
. You can test against these error codes using the
std::errc
error condition enum:
reproc::process;
std::error_code ec = process.start(...);
if (ec == std::errc::no_such_file_or_directory) {
std::cerr << "Executable not found. Make sure it is available from the PATH.";
return 1;
}
if (ec) {
// Will print the actual system error value from errno or GetLastError() if a
// system error occurred.
std::cerr << ec.value() << std::endl;
// You can also print a string representation of the error.
std::cerr << ec.message();
return 1;
}
If needed, you can also convert std::error_code
's to exceptions using
std::system_error
:
reproc::process;
std::error_code ec = process.start(...);
if (ec) {
throw std::system_error(ec, "Unable to start process");
}
Guidelines for using a reproc child process from multiple threads:
- Don't wait for or stop the same child process from multiple threads at the same time.
- Don't read from or write to the same stream of the same child process from multiple threads at the same time.
Different threads can read from or write to different streams at the same time. This is a valid approach when you want to write to stdin and read from stdout in parallel.
Look at the forward and background examples to see examples of how to work with reproc from multiple threads.
-
(POSIX) On POSIX a parent process is required to wait on a child process that has exited (using
reproc_wait
) before all resources related to that process can be released by the kernel. If the parent doesn't wait on a child process after it exits, the child process becomes a zombie process. -
While
reproc_terminate
allows the child process to perform cleanup it is up to the child process to correctly clean up after itself. reproc only sends a termination signal to the child process. The child process itself is responsible for cleaning up its own child processes and other resources. -
When using
reproc_kill
the child process does not receive a chance to perform cleanup which could result in resources being leaked. Chief among these leaks is that the child process will not be able to stop its own child processes. Always try to let a child process exit normally by callingreproc_terminate
before callingreproc_kill
. -
(Windows)
reproc_kill
is not guaranteed to kill a child process immediately on Windows. For more information, read the Remarks section in the documentation of the WindowsTerminateProcess
function that reproc uses to kill child processes on Windows. -
While reproc tries its very best to avoid leaking file descriptors into child processes, there are scenarios where it cannot guarantee that no file descriptors will be leaked to child processes.
-
(POSIX) Writing to a closed stdin pipe of a child process will crash the parent process with the
SIGPIPE
signal. To avoid this theSIGPIPE
signal has to be ignored in the parent process. If theSIGPIPE
signal is ignoredreproc_write
will returnREPROC_ERROR_STREAM_CLOSED
as expected when writing to a closed stdin pipe. -
(POSIX) Ignoring the
SIGCHLD
signal by setting its disposition toSIG_IGN
changes the behavior of thewaitpid
system call which will causereproc_wait
to stop working as expected. Read the Notes section of thewaitpid
man page for more information.