pax_global_header00006660000000000000000000000064146005555220014516gustar00rootroot0000000000000052 comment=3179928ae7b085e41dfb846d987519fa7c12ffb3 reproc-14.2.5/000077500000000000000000000000001460055552200131015ustar00rootroot00000000000000reproc-14.2.5/.clang-format000066400000000000000000000012071460055552200154540ustar00rootroot00000000000000--- BasedOnStyle: LLVM --- Language: Cpp AllowAllParametersOfDeclarationOnNextLine: false AllowShortFunctionsOnASingleLine: Empty AlwaysBreakTemplateDeclarations: Yes BinPackParameters: false BreakBeforeBraces: Custom BraceWrapping: AfterFunction: true SplitEmptyRecord: false ConstructorInitializerAllOnOneLineOrOnePerLine: true Cpp11BracedListStyle: false FixNamespaceComments: false ForEachMacros: ['STRV_FOREACH', 'NULSTR_FOREACH'] IndentCaseLabels: true IndentPPDirectives: BeforeHash PenaltyBreakAssignment: 100 PenaltyBreakBeforeFirstCallParameter: 100 SpaceAfterCStyleCast: true SpaceBeforeParens: ControlStatementsExceptForEachMacros reproc-14.2.5/.clang-tidy000066400000000000000000000021201460055552200151300ustar00rootroot00000000000000--- Checks: ' *, -altera-*, -android-cloexec-fopen, -android-cloexec-pipe, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -bugprone-reserved-identifier, -cert-*, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -clang-diagnostic-unused-command-line-argument, -concurrency-mt-unsafe, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-macro-usage, -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, -fuchsia-*, -llvmlibc-*, -google-*, -hicpp-*, -llvm-else-after-return, -llvm-header-guard, -llvm-namespace-comment, -misc-no-recursion, -modernize-avoid-c-arrays, -modernize-use-trailing-return-type, -performance-no-int-to-ptr, -readability-else-after-return, -readability-function-cognitive-complexity, -readability-identifier-length, -readability-magic-numbers, ' HeaderFilterRegex: '.*reproc\+\+.*$' ... reproc-14.2.5/.editorconfig000066400000000000000000000002231460055552200155530ustar00rootroot00000000000000root = true [*] charset = utf-8 trim_trailing_whitespace = true end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true reproc-14.2.5/.github/000077500000000000000000000000001460055552200144415ustar00rootroot00000000000000reproc-14.2.5/.github/workflows/000077500000000000000000000000001460055552200164765ustar00rootroot00000000000000reproc-14.2.5/.github/workflows/codeql-analysis.yml000066400000000000000000000014401460055552200223100ustar00rootroot00000000000000name: "CodeQL" on: push: branches: - main pull_request: branches: - main jobs: analyze: name: Analyze ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: language: [cpp] os: [ubuntu-20.04, macos-latest, windows-latest] steps: - name: Checkout repository uses: actions/checkout@v2 - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} - name: Configure run: > cmake -B build -DREPROC++=ON -DREPROC_TEST=ON -DREPROC_EXAMPLES=ON - name: Build run: cmake --build build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 reproc-14.2.5/.github/workflows/main.yml000066400000000000000000000072551460055552200201560ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: branches: - main jobs: ci: name: ${{ matrix.os }}-${{ matrix.compiler }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest compiler: gcc - os: ubuntu-latest compiler: clang - os: windows-latest compiler: cl - os: windows-latest compiler: clang-cl - os: windows-latest compiler: clang - os: windows-latest compiler: gcc - os: macos-latest compiler: gcc - os: macos-latest compiler: clang steps: - uses: actions/checkout@v1 - name: Install (Ubuntu) if: runner.os == 'Linux' run: | sudo apt-get install -y --no-install-recommends ninja-build clang-tidy if [ "${{ matrix.compiler }}" = "gcc" ]; then echo CC=gcc >> $GITHUB_ENV echo CXX=g++ >> $GITHUB_ENV else echo CC=clang >> $GITHUB_ENV echo CXX=clang++ >> $GITHUB_ENV fi - name: Install (macOS) if: runner.os == 'macOS' run: | brew install ninja sudo ln -s /usr/local/opt/llvm/bin/clang-tidy /usr/local/bin/clang-tidy if [ "${{ matrix.compiler }}" = "gcc" ]; then echo CC=gcc >> $GITHUB_ENV echo CXX=g++ >> $GITHUB_ENV else echo CC=clang >> $GITHUB_ENV echo CXX=clang++ >> $GITHUB_ENV fi - name: Install (Windows) if: runner.os == 'Windows' run: | iex "& {$(irm get.scoop.sh)} -RunAsAdmin" scoop install ninja llvm --global if ("${{ matrix.compiler }}" -eq "gcc") { echo CC=gcc | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 echo CXX=g++ | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 } elseif ("${{ matrix.compiler }}" -eq "clang") { echo CC=clang | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 echo CXX=clang++ | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 } else { echo CC=${{ matrix.compiler }} | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 echo CXX=${{ matrix.compiler }} | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 } # We add the output directories to the PATH to make sure the tests and # examples can find the reproc and reproc++ DLL's. $env:PATH += ";$pwd\build\reproc\lib" $env:PATH += ";$pwd\build\reproc++\lib" # Make all PATH additions made by scoop and ourselves global. echo "PATH=$env:PATH" | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 if ("${{ matrix.compiler }}".endswith("cl")) { & .github\workflows\vsenv.ps1 -arch x64 -hostArch x64 } # We build reproc as a shared library to verify all the necessary symbols # are exported. # YAML folded multiline strings ('>') require the same indentation for all # lines in order to turn newlines into spaces. - name: Configure run: > cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DREPROC++=ON -DREPROC_TEST=ON -DREPROC_EXAMPLES=ON -DREPROC_WARNINGS=ON -DREPROC_WARNINGS_AS_ERRORS=ON -DREPROC_TIDY=ON -DREPROC_SANITIZERS=ON - name: Build run: cmake --build build - name: Test run: cmake --build build --target test env: CTEST_OUTPUT_ON_FAILURE: ON reproc-14.2.5/.github/workflows/vsenv.ps1000066400000000000000000000011501460055552200202610ustar00rootroot00000000000000param ( [string]$arch = "x64", [string]$hostArch = "x64" ) $vswherePath = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" $vsInstallationPath = & "$vswherePath" -latest -products * -property installationPath $vsDevCmdPath = "`"$vsInstallationPath\Common7\Tools\vsdevcmd.bat`"" $command = "$vsDevCmdPath -no_logo -arch=$arch -host_arch=$hostArch" # https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt & "${env:COMSPEC}" /s /c "$command && set" | ForEach-Object { $name, $value = $_ -split '=', 2 Add-Content -Path $env:GITHUB_ENV -Encoding utf8 "$name=$value" } reproc-14.2.5/CHANGELOG.md000066400000000000000000001212751460055552200147220ustar00rootroot00000000000000# Changelog ## 14.2.4 - Bugfix: Fix a memory leak in `reproc_start()` on Windows (thanks @AokiYuune). - Bugfix: Fix a memory leak in reproc++ `array` class move constructor. - Allow passing zero-sized array's to reproc's `input` option (thanks @lightray22). ## 14.2.3 - Bugfix: Fix sign of EWOULDBLOCK error returned from `reproc_read`. ## 14.2.2 - Bugfix: Disallow using `fork` option when using `reproc_run`. ## 14.2.1 - Bugfix: `reproc_run` now handles forked child processes correctly. - Bugfix: Sinks of different types can now be passed to `reproc::drain`. - Bugfix: Processes on Windows returning negative exit codes don't cause asserts anymore. - Bugfix: Dependency on librt on POSIX (except osx) systems is now explicit in CMake. - Bugfix: Added missing stdout redirect option to reproc++. ## 14.2.0 - Added `reproc_pid`/`process::pid` to get the pid of the process - Fixed compilation error when including reproc/drain.h in C++ code - Added missing extern "C" block to reproc/run.h ## 14.1.0 - `reproc_run`/`reproc::run` now return the exit code of `reproc_stop`/`process::stop` even if the deadline is exceeded. - A bug where deadlines wouldn't work was fixed. ## 14.0.0 - Renamed `environment` to `env`. - Added configurable behavior to `env` option. Extra environment variables are now added via `env.extra`. Extra environment variables now extend the parent environment by default instead of replacing it. ## 13.0.1 - Bugfix: Reset the `events` parameter of every event source if a deadline expires when calling `reproc_poll`. - Bugfix: Return 1 from `reproc_poll` if a deadline expires. - Bugfix: Don't block in `reproc_read` on Windows if the `nonblocking` option was specified. ## 13.0.0 - Allow passing empty event sources to `reproc_poll`. If the `process` member of a `reproc_event_source` object is `NULL`, `reproc_poll` ignores the event source. - Return zero from `reproc_poll` if the given timeout expires instead of `REPROC_ETIMEDOUT`. In reproc, we follow the general pattern that we don't modify output arguments if an error occurs. However, when `reproc_poll` errors, we still want to set all events of the given event sources to zero. To signal that we're modifying the output arguments if the timeout expires, we return zero instead of `REPROC_ETIMEDOUT`. This is also more consistent with `poll` and `WSAPoll` which have the same behaviour. - If one or more events occur, return the number of processes with events from `reproc_poll`. ## 12.0.0 ### reproc - Put pipes in blocking mode by default. This allows using `reproc_read` and `reproc_write` directly without having to figure out `reproc_poll`. - Add `nonblocking` option. Allows putting pipes back in nonblocking mode if needed. - Child process stderr streams are now redirected to the parent stderr stream by default. Because pipes are blocking again by default, there's a (small) chance of deadlocks if we redirect both stdout and stderr to pipes. Redirecting stderr to the parent by default avoids that issue. The other (bigger) issue is that if we redirect stderr to a pipe, there's a good chance users might forget to read from it and discard valuable error output from the child process. By redirecting to the parent stderr by default, it's immediately noticeable when a child process is not behaving according to expectations as its error output will appear directly on the parent process stderr stream. Users can then still decide to explicitly discard the output from the stderr stream if needed. - Turn `timeout` option into an argument for `reproc_poll`. While deadlines can differ per process, the timeout is very likely to always be the same so we make it an argument to the only function that uses it, namely `reproc_poll`. - In `reproc_drain`, call `sink` once with an empty buffer when a stream is closed. This allows sinks to handle stream closure if needed. - Change sinks to return `int` instead of `bool`. If a `sink` returns a non-zero value, `reproc_drain` exits immediately with the same value. This change allows sinks to return their own errors without having to store extra state. - `reproc_sink_string` now returns `REPROC_ENOMEM` from `reproc_drain` if a memory allocation fails and no longer frees any output that was read previously. This allows the user to still do something with the remaining output even if a memory allocation failed. On the flipside, it is now required to always call `reproc_free` after calling `reproc_drain` with a sink string, even if it fails. - Renamed sink.h to drain.h. Reflect that sink.h contains `reproc_drain` by renaming it to drain.h. - Add `REPROC_REDIRECT_PATH` and shorthand `path` options. ### reproc++ - Equivalent changes as those done for reproc. - Remove use of reprocxx. Meson gained support for CMake subprojects containing targets with special characters so we rename directories and CMake targets back to reproc++. ## 11.0.0 ### General - Compilation now happens with compiler extensions disabled (`-std=c99` and `-std=c++11`). ### reproc - Add `inherit` and `discard` options as shorthands to set all members of the `redirect` options to `REPROC_REDIRECT_INHERIT` and `REPROC_REDIRECT_DISCARD` respectively. - Add `reproc_run` and `reproc_run_ex` which allows running a process using only a single function. Running a simple process with reproc required calling `reproc_new`, `reproc_start`, `reproc_wait` and optionally `reproc_drain` with all the associated error handling. `reproc_run` encapsulates all this boilerplate. - Add `input` option that writes the given to the child process stdin pipe before starting the process. This allows passing input to the process when using `reproc_run`. - Add `deadline` option that specifies a point in time beyond which `reproc_poll` will return `REPROC_ETIMEDOUT`. - Add `REPROC_DEADLINE` that makes `reproc_wait` wait until the deadline specified in the `deadline` option has expired. By default, if the `deadline` option is set, `reproc_destroy` waits until the deadline expires before sending a `SIGTERM` signal to the child process. - Add (POSIX only) `fork` option that makes `reproc_start` a safe alternative to `fork` with support for all of reproc's other features. - Return the amount of bytes written from `reproc_write` and stop handling partial writes. Now that reproc uses nonblocking pipes, it doesn't make sense to handle partial writes anymore. The `input` option can be used as an alternative. `reproc_start` keeps writing until all data from `input` is written to the child process stdin pipe or until a call to `reproc_write` returns an error. - Add `reproc_poll` to query child process events of one or more child processes. We now support polling multiple child processes for events. `reproc_poll` mimicks the POSIX `poll` function but instead of pollfds, it takes a list of event sources which consist out of a process, the events we're interested in and an output field which is filled in by `reproc_poll` that contains the events that occurred for that child process. - Stop reading from both stdout and stderr in `reproc_read`. Because we now have `reproc_poll`, `reproc_read` was simplified to again read from the given stream which is passed again as an argument. To avoid deadlocks, call `reproc_poll` to figure out the first stream that has data available to read. - Support polling for process exit events. By adding `REPROC_EVENT_EXIT` to the list of interested events, we can poll for child process exit events. - Add a dependency on Winsock2 on Windows. To implement `reproc_poll`, we redirect to sockets on Windows which allows us to use `WSAPoll` which is required to implement `reproc_poll`. Using sockets on Windows requires linking with the `ws2_32` library. This dependency is automatically handled by CMake and pkg-config. - Move `in`, `out` and `err` options out of `stdio` directly into `redirect`. This reduces the amount of boilerplate when redirecting streams. - Rename `REPROC_REDIRECT_INHERIT` to `REPROC_REDIRECT_PARENT`. `REPROC_REDIRECT_PARENT` more clearly indicates that we're redirecting to the parent's corresponding standard stream. - Add support for redirecting to operating system handles. This allows redirecting to operating system-specific handles. On POSIX systems, this feature expects file descriptors. On Windows, it expects `HANDLE`s or `SOCKET`s. - Add support for redirecting to `FILE *`s. This gives users a cross-platform way to redirect standard streams to files. - Add support for redirecting stderr to stdout. For high-traffic scenarios, it doesn't make sense to allocate a separate pipe for stderr if its output is only going to be combined with the output of stdout. By using `REPROC_REDIRECT_STDOUT` for stderr, its output is written directly to stdout by the child process. - Turn `redirect`'s `in`, `out` and `err` options into instances of the new `reproc_redirecŧ` struct. An enum didn't cut it anymore for the new file and handle redirect options since those require extra fields to allow specifying which file or handle to redirect to. - Add `redirect.file` option as a shorthand for redirecting stdout and stderr to the same file. ### reproc++ - reproc++ includes mostly the same changes done to reproc so we only document the differences. - Add `fork` method instead of `fork` option. Adding a `fork` option would have required changing `start` to return `std::pair` to allow determining whether we're in the parent or the child process after a fork. However, the bool return value would only be valid when the fork option was enabled. Thus, this approach would unnecessarily complicate all other use cases of `start` that don't require `fork`. To solve the issue, we made `fork` a separate method instead. ## 10.0.3 ### reproc - Fixed issue where `reproc_wait` would assert when invoked with a timeout of zero on POSIX. - Fixed issue where `reproc_wait` would not return `REPROC_ETIMEDOUT` when invoked with a timeout of zero on POSIX. ## 10.0.2 - Update CMake project version. ## 10.0.1 ### reproc - Pass `timeout` once via `reproc_options` instead of passing it via `reproc_read`, `reproc_write` and `reproc_drain`. ### reproc++ - Pass `timeout` once via `reproc::options` instead of passing it via `process::read`, `process::write` and `reproc::drain`. ## 10.0.0 ### reproc - Remove `reproc_parse`. Instead of checking for `REPROC_EPIPE` (previously `REPROC_ERROR_STREAM_CLOSED`), simply check if the given parser has a full message available. If it doesn't, the output streams closed unexpectedly. - Remove `reproc_running` and `reproc_exit_status`. When calling `reproc_running`, it would wait with a zero timeout if the process was still running and check if the wait timed out. However, a call to wait can fail for other reasons as well which were all ignored by `reproc_running`. Instead of `reproc_running`, A call to `reproc_wait` with a timeout of zero should be used to check if a process is still running. `reproc_wait` now also returns the exit status if the process exits or has already exited which removes the need for `reproc_exit_status`. - Read from both stdout and stderr in `reproc_read` to avoid deadlocks and indicate which stream `reproc_read` was read from. Previously, users would indicate the stream they wanted to read from when calling `reproc_read`. However, this lead to issues with programs that write to both stdout and stderr as a user wouldn't know whether stdout or stderr would have output available to read. Reading from only the stdout stream didn't work as the parent could be blocked on reading from stdout while the child was simultaneously blocked on writing to stderr leading to a deadlock. To get around this, users had to start up a separate thread to read from both stdout and stderr at the same time which was a lot of extra work just to get the output of external programs that write to both stdout and stderr. Now, reproc takes care of avoiding the deadlock by checking which of stdout/stderr can be read from, doing the actual read and indicating to the user which stream was read from. Practically, instead of passing `REPROC_STREAM_OUT` or `REPROC_STREAM_ERR` to `reproc_read`, you now pass a pointer to a `REPROC_STREAM` variable instead which `reproc_read` will set to `REPROC_STREAM_OUT` or `REPROC_STREAM_ERR` depending on which stream it read from. If both streams have been closed by the child process, `reproc_read` returns `REPROC_EPIPE`. Because of the changes to `reproc_read`, `reproc_drain` now also reads from both stdout and stderr and indicates the stream that was read from to the given sink function via an extra argument passed to the sink. - Read the output of both stdout and stderr into a single contiguous null-terminated string in `reproc_sink_string`. - Remove the `bytes_written` parameter of `reproc_write`. `reproc_write` now always writes `size` bytes to the standard input of the child process. Partial writes do not have to be handled by users anymore and are instead handled by reproc internally. - Define `_GNU_SOURCE` and `_WIN32_WINNT` only in the implementation files that need them. This helps keep track of where we're using functionality that requires extra definitions and makes building reproc in all kinds of build systems simpler as the compiler invocations to build reproc get smaller as a result. - Change the error handling in the public API to return negative `errno` (POSIX) or `GetLastError` (Windows) style values. `REPROC_ERROR` is replaced by extern constants that are assigned the correct error value based on the platform reproc is built for. Instead of returning `REPROC_ERROR`, most functions in reproc's API now return `int` when they can fail. Because system errors are now returned directly, there's no need anymore for `REPROC_ERROR` and `reproc_error_system` and they has been removed. Error handling before 10.0.0: ```c REPROC_ERROR error = reproc_start(...); if (error) { goto finish; } finish: if (error) { fprintf(stderr, "%s", reproc_strerror(error)); } ``` Error handling from 10.0.0 onwards: ```c int r = reproc_start(...); if (r < 0) { goto finish; } finish: if (r < 0) { fprintf(stderr, "%s", reproc_strerror(r)); } ``` - Hide the internals of `reproc_t`. Instances of `reproc_t` are now allocated on the heap by calling `reproc_new`. `reproc_destroy` releases the memory allocated by `reproc_new`. - Take optional arguments via the `reproc_options` struct in `reproc_start`. When using designated initializers, calls to `reproc_start` are now much more readable than before. Using a struct also makes it much easier to set all options to their default values (`reproc_options options = { 0 };`). Finally, we can add more options in further releases without requiring existing users to change their code. - Support redirecting the child process standard streams to `/dev/null` (POSIX) or `NUL` (Windows) in `reproc_start` via the `redirect` field in `reproc_options`. This is especially useful when you're not interested in the output of a child process as redirecting to `/dev/null` doesn't require regularly flushing the output pipes of the process to prevent deadlocks as is the case when redirecting to pipes. - Support redirecting the child process standard streams to the parent process standard streams in `reproc_starŧ` via the `redirect` field in `reproc_options`. This is useful when you want to interleave child process output with the parent process output. - Modify `reproc_start` and `reproc_destroy` to work like the reproc++ `process` class constructor and destructor. The `stop_actions` field in `reproc_options` can be used to define up to three stop actions that are executed when `reproc_destroy` is called if the child process is still running. If no explicit stop actions are given, `reproc_destroy` defaults to waiting indefinitely for the child process to exit. - Return the amount of bytes read from `reproc_read` if it succeeds. This is made possible by the new error handling scheme. Because errors are all negative values, we can use the positive range of an `int` as the normal return value if no errors occur. - Return the exit status from `reproc_wait` and `reproc_stop` if they succeed. Same reasoning as above. If the child process has already exited, `reproc_wait` and `reproc_stop` simply returns the exit status again. - Do nothing when `NULL` is passed to `reproc_destroy` and always return `NULL` from `reproc_destroy`. This allows `reproc_destroy` to be safely called on the same instance multiple times when assigning the result of `reproc_destroy` to the same instance (`process = reproc_destroy(process)`). - Take stop actions via the `reproc_stop_actions` struct in `reproc_stop`. This makes it easier to store stop action configurations both in and outside of reproc. - Add 256 to signal exit codes returned by `reproc_wait` and `reproc_stop`. This prevents conflicts with normal exit codes. - Add `REPROC_SIGTERM` and `REPROC_SIGKILL` constants to match against signal exit codes. These also work on Windows and correspond to the exit codes returned by sending the `CTRL-BREAK` signal and calling `TerminateProcess` respectively. - Rename `REPROC_CLEANUP` to `REPROC_STOP`. Naming the enum after the function it is passed to (`reproc_stop`) is simpler than using a different name. - Rewrite tests in C using CTest and `assert` and remove doctest. Doctest is a great library but we don't really lose anything major by replacing it with CTest and asserts. On the other hand, we lose a dependency, don't need to download stuff from CMake anymore and tests compile significantly faster. Tests are now executed by running `cmake --build build --target test`. - Return `REPROC_EINVAL` from public API functions when passed invalid arguments. - Make `reproc_strerror` thread-safe. - Move `reproc_drain` to sink.h. - Make `reproc_drain` take a separate sink for each output stream. Sinks are now passed via the `reproc_sink` type. Using separate sinks for both output streams allows for a lot more flexibility. To use a single sink for both output streams, simply pass the same sink to both the `out` and `err` arguments of `reproc_drain`. - Turn `reproc_sink_string` and `reproc_sink_discard` into functions that return sinks and hide the actual functions in sink.c. - Add `reproc_free` to sink.h which must be used to free memory allocated by `reproc_sink_string`. This avoids issues with allocating across module (DLL) boundaries on Windows. - Support passing timeouts to `reproc_read`, `reproc_write` and `reproc_drain`. Pass `REPROC_INFINITE` as the timeout to retain the old behaviour. - Use `int` to represent timeout values. - Renamed `stop_actions` field of `reproc_options` to `stop`. ### reproc++ - Remove `process::parse`, `process::exit_status` and `process::running`. Consequence of the equivalents in reproc being removed. - Take separate `out` and `err` arguments in the `sink::string` and `sink::ostream` constructors that receive output from the stdout and stderr streams of the child process respectively. To combine the output from the stdout and stderr streams, simply pass the same `string` or `ostream` to both the `out` and `err` arguments. - Modify `process::read` to return a tuple of the stream read from, the amount of bytes read and an error code. The stream read from and amount of bytes read are only valid if `process::read` succeeds. `std::tie` can be used pre-C++17 to assign the tuple's contents to separate variables. - Modify `process::wait` and `process::stop` to return a pair of exit status and error code. The exit status is only valid if `process::wait` or `process::stop` succeeds. - Alias `reproc::error` to `std::errc`. As OS errors are now used everywhere, we can simply use `std::errc` for all error handling instead of defining our own error code. - Add `signal::terminate` and `signal::kill` constants. These are aliases for `REPROC_SIGTERM` and `REPROC_SIGKILL` respectively. - Inline all sink implementations in sink.hpp. - Add `sink::thread_safe::string` which is a thread-safe version of `sink::string`. - Move `process::drain` out of the `process` class and move it to sink.hpp. `process.drain(...)` becomes `reproc::drain(process, ...)`. - Make `reproc::drain` take a separate sink for each output stream. Same reasoning as `reproc_drain`. - Modify all included sinks to support the new `reproc::drain` behaviour. - Support passing timeouts to `process::read`, `process::write` and `reproc::drain`. They still default to waiting indefinitely which matches their old behaviour. - Renamed `stop_actions` field of `reproc::options` to `stop`. ### CMake - Drop required CMake version to CMake 3.12. - Add CMake 3.16 as a supported CMake version. - Build reproc++ with `-pthread` when `REPROC_MULTITHREADED` is enabled. See https://github.com/DaanDeMeyer/reproc/issues/24 for more information. - Add `REPROC_WARNINGS` option (default: `OFF`) to build with compiler warnings. - Add `REPROC_DEVELOP` option (default: `OFF`) which enables a lot of options to simplify developing reproc. By default, most of reproc's CMake options are disabled to make including reproc in other projects as simple as possible. However, when working on reproc, we usually wants most options enabled instead. To make enabling all options simpler, `REPROC_DEVELOP` was added from which most other options take their default value. As a result, enabling `REPROC_DEVELOP` enables all options related to developing reproc. Additionally, `REPROC_DEVELOP` takes its initial value from an environment variable of the same name so it can be set once and always take effect whenever running CMake on reproc's source tree. - Add `REPROC_OBJECT_LIBRARIES` option to build CMake object libraries. In CMake, linking a library against a static library doesn't actually copy the object files from the static library into the library. Instead, both static libraries have to be installed and depended on by the final executable. By using CMake object libraries, the object files are copied into the depending static library and no extra artifacts are produced. - Enable `REPROC_INSTALL` by default unless `REPROC_OBJECT_LIBRARIES` is enabled. As `REPROC_OBJECT_LIBRARIES` can now be used to depend on reproc without generating extra artifacts, we assume that users not using `REPROC_OBJECT_LIBRARIES` will want to install the produced artifacts. - Rename reproc++ to reprocxx inside the CMake build files. This was done to allow using reproc as a Meson subproject. Meson doesn't accept the '+' character in target names so we use 'x' instead. - Modify the export headers so that the only extra define necessary is `REPROC_SHARED` when using reproc as a shared library on Windows. Naturally, this define is added as a CMake usage requirement and doesn't have to be worried about when using reproc via `add_subdirectory` or `find_package`. ## 9.0.0 ### General - Drop support for Windows XP. - Add support for custom environments. `reproc_start` and `process::start` now take an extra `environment` parameter that allows specifying custom environments. **IMPORTANT**: The `environment` parameter was inserted before the `working_directory` parameter so make sure to update existing usages of `reproc_start` and `process::start` so that the `environment` and `working_directory` arguments are specified in the correct order. To keep the previous behaviour, pass `nullptr` as the environment to `reproc_start`/`process::start` or use the `process::start` overload without the `environment` parameter. - Remove `argc` parameter from `reproc_start` and `process::start`. We can trivially calculate `argc` internally in reproc since `argv` is required to end with a `NULL` value. - Improve implementation of `reproc_wait` with a timeout on POSIX systems. Instead of spawning a new process to implement the timeout, we now use `sigtimedwait` on Linux and `kqueue` on macOS to wait on `SIGCHLD` signals and check if the process we're waiting on has exited after each received `SIGCHLD` signal. - Remove `vfork` usage. Clang analyzer was indicating a host of errors in our usage of `vfork`. We also discovered tests were behaving differently on macOS depending on whether `vfork` was enabled or disabled. As we do not have the expertise to verify if `vfork` is working correctly, we opt to remove it. - Ensure passing a custom working directory and a relative executable path behaves consistently on all supported platforms. Previously, calling `reproc_start` with a relative executable path combined with a custom working directory would behave differently depending on which platform the code was executed on. On POSIX systems, the relative executable path would be resolved relative to the custom working directory. On Windows, the relative executable path would be resolved relative to the parent process working directory. Now, relative executable paths are always resolved relative to the parent process working directory. - Reimplement `reproc_drain`/`process::drain` in terms of `reproc_parse`/`process::parse`. Like `reproc_parse` and `process::parse`, `reproc_drain` and `process::drain` are now guaranteed to always be called once with an empty buffer before reading any actual data. We now also guarantee that the initial empty buffer is not `NULL` or `nullptr` so the received data and size can always be safely passed to `memcpy`. - Add MinGW support. MinGW CI builds were also added to prevent regressions in MinGW support. ### reproc - Update `reproc_strerror` to return the actual system error string of the error code returned by `reproc_system_error` instead of "system error" when passed `REPROC_ERROR_SYSTEM` as argument. This should make debugging reproc errors a lot easier. - Add `reproc_sink_string` in `sink.h`, a sink that stores all process output in a single null-terminated C string. - Add `reproc_sink_discard` in `sink.h`, a sink that discards all process output. ### reproc++ - Move sinks into `sink` namespace and remove `_sink` suffix from all sinks. - Add `discard` sink that discards all output read from a stream. This is useful when a child process produces a lot of output that we're not interested in and cannot handle the output stream being closed or full. When this is the case, simply start a thread that drains the stream with a `discard` sink. - Update `process::start` to work with any kind of string type. Every string type that implements a `size` method and the index operator can now be passed in a container to `process::start`. `working_directory` now takes a `const char *` instead of a `std::string *`. - Fix compilation error when using `process::parse`. ## 8.0.1 - Correctly escape arguments on Windows. See [#18](https://github.com/DaanDeMeyer/reproc/issues/18) for more information. ## 8.0.0 - Change `reproc_parse` and `reproc_drain` argument order. `context` is now the last argument instead of the first. - Use `uint8_t *` as buffer type instead of `char *` or `void *` `uint8_t *` more clearly indicates reproc is working with buffers of bytes than `char *` and `void *`. We choose `uint8_t *` over `char *` to avoid errors caused by passing data read by reproc directly to functions that expect null-terminated strings (data read by reproc is not null-terminated). ## 7.0.0 ### General - Rework error handling. Trying to abstract platform-specific errors in `REPROC_ERROR` and `reproc::errc` turned out to be harder than expected. On POSIX it remains very hard to figure out which errors actually have a chance of happening and matching `reproc::errc` values to `std::errc` values is also ambiguous and prone to errors. On Windows, there's hardly any documentation on which system errors functions can return so 90% of the time we were just returning `REPROC_UNKNOWN_ERROR`. Furthermore, many operating system errors will be fatal for most users and we suspect they'll all be handled similarly (stopping the application or retrying). As a result, in this release we stop trying to abstract system errors in reproc. All system errors in `REPROC_ERROR` were replaced by a single value (`REPROC_ERROR_SYSTEM`). `reproc::errc` was renamed to `reproc::error` and turned into an error code instead of an error condition and only contains the reproc-specific errors. reproc users can still retrieve the specific system error using `reproc_system_error`. reproc++ users can still match against specific system errors using the `std::errc` error condition enum () or print a string presentation of the error using the `message` method of `std::error_code`. All values from `REPROC_ERROR` are now prefixed with `REPROC_ERROR` instead of `REPROC` which helps reduce clutter in code completion. - Azure Pipelines CI now includes Visual Studio 2019. - Various smaller improvements and fixes. ### CMake - Introduce `REPROC_MULTITHREADED` to configure whether reproc should link against pthreads. By default, `REPROC_MULTITHREADED` is enabled to prevent accidental undefined behaviour caused by forgetting to enable `REPROC_MULTITHREADED`. Advanced users might want to disable `REPROC_MULTITHREADED` when they know for certain their code won't use more than a single thread. - doctest is now downloaded at configure time instead of being vendored inside the reproc repository. doctest is only downloaded if `REPROC_TEST` is enabled. ## 6.0.0 ### General - Added Azure Pipelines CI. Azure Pipelines provides 10 parallel jobs which is more than Travis and Appveyor combined. If it turns out to be reliable Appveyor and Travis will likely be dropped in the future. For now, all three are enabled. - Code cleanup and refactoring. ### CMake - Renamed `REPROC_TESTS` to `REPROC_TEST`. - Renamed test executable from `tests` to `test`. ### reproc - Renamed `reproc_type` to `reproc_t`. We chose `reproc_type` initially because `_t` belongs to POSIX but we switch to using `_t` because `reproc` is a sufficiently unique name that we don't have to worry about naming conflicts. - reproc now keeps track of whether a process has exited and its exit status. Keeping track of whether the child process has exited allows us to remove the restriction that `reproc_wait`, `reproc_terminate`, `reproc_kill` and `reproc_stop` cannot be called again on the same process after completing successfully once. Now, if the process has already exited, these methods don't do anything and return `REPROC_SUCCESS`. - Added `reproc_running` to allow checking whether a child process is still running. - Added `reproc_exit_status` to allow querying the exit status of a process after it has exited. - `reproc_wait` and `reproc_stop` lost their `exit_status` output parameter. Use `reproc_exit_status` instead to retrieve the exit status. ### reproc++ - Added `process::running` and `process::exit_status`. These delegate to `reproc_running` and `reproc_exit_status` respectively. - `process::wait` and `process::stop` lost their `exit_status` output parameter. Use `process::exit_status` instead. ## 5.0.1 ### reproc++ - Fixed compilation error caused by defining `reproc::process`'s move assignment operator as default in the header which is not allowed when a `std::unique_ptr` member of an incomplete type is present. ## 5.0.0 ### General - Added and rewrote implementation documentation. - General refactoring and simplification of the source code. ### CMake - Raised minimum CMake version to 3.13. Tests are now added to a single target `reproc-tests` in each subdirectory included with `add_subdirectory`. Dependencies required to run the added tests are added to `reproc-tests` with `target_link_libraries`. Before CMake 3.13, `target_link_libraries` could not modify targets created outside of the current directory which is why CMake 3.13 is needed. - `REPROC_CI` was renamed to `REPROC_WARNINGS_AS_ERRORS`. This is a side effect of upgrading cddm. The variable was renamed in cddm to more clearly indicate its purpose. - Removed namespace from reproc's targets. To link against reproc or reproc++, you now have to link against the target without a namespace prefix: ```cmake find_package(reproc) # or add_subdirectory(external/reproc) target_link_libraries(myapp PRIVATE reproc) find_package(reproc++) # or add_subdirectory(external/reproc++) target_link_libraries(myapp PRIVATE reproc++) ``` This change was made because of a change in cddm (a collection of CMake functions to make setting up new projects easier) that removed namespacing and aliases of library targets in favor of namespacing within the target name itself. This change was made because the original target can still conflict with other targets even after adding an alias. This can cause problems when using generic names for targets inside the library itself. An example clarifies the problem: Imagine reproc added a target for working with processes asynchronously. In the previous naming scheme, we'd do the following in reproc's CMake build files: ```cmake add_library(async "") add_library(reproc::async ALIAS async) ``` However, there's a non-negligible chance that someone using reproc might also have a target named async which would result in a conflict when using reproc with `add_subdirectory` since there'd be two targets with the same name. With the new naming scheme, we'd do the following instead: ```cmake add_library(reproc-async "") ``` This has almost zero chance of conflicting with user's target names. The advantage is that with this scheme we can use common target names without conflicting with user's target names which was not the case with the previous naming scheme. ### reproc - Removed undefined behaviour in Windows implementation caused by casting an int to an unsigned int. - Added a note to `reproc_start` docs about the behaviour of using a executable path relative to the working directory combined with a custom working directory for the child process on different platforms. - We now retrieve the file descriptor limit in the parent process (using `sysconf`) instead of in the child process because `sysconf` is not guaranteed to be async-signal-safe which all functions called in a child process after forking should be. - Fixed compilation issue when `ATTRIBUTE_LIST_FOUND` was undefined (#15). ### reproc++ - Generified `process::start` so it works with any container of `std::string` satisfying the [SequenceContainer](https://en.cppreference.com/w/cpp/named_req/SequenceContainer) interface. ## 4.0.0 ### General - Internal improvements and documentation fixes. ### reproc - Added `reproc_parse` which mimics reproc++'s `process::parse`. - Added `reproc_drain` which mimics reproc++'s `process::drain` along with an example that explains how to use it. Because C doesn't support lambda's, both of these functions take a function pointer and an extra context argument which is passed to the function pointer each time it is called. The context argument can be used to store any data needed by the given function pointer. ### reproc++ - Renamed the `process::read` overload which takes a parser to `process::parse`. This breaking change was done to keep consistency with reproc where we added `reproc_parse`. We couldn't add another `reproc_read` since C doesn't support overloading so we made the decision to rename `process::read` to `process::parse` instead. - Changed `process::drain` sinks to return a boolean instead of `void`. Before this change, the only way to stop draining a process was to throw an exception from the sink. By changing sinks to return `bool`, a sink can tell `drain` to stop if an error occurs by returning `false`. The error itself can be stored in the sink if needed. ## 3.1.3 ### CMake - Update project version in CMakeLists.txt from 3.0.0 to the actual latest version (3.1.3). ## 3.1.2 ### pkg-config - Fix pkg-config install prefix. ## 3.1.0 ### CMake - Added `REPROC_INSTALL_PKGCONFIG` to control whether pkg-config files are installed or not (default: `ON`). The vcpkg package manager has no need for the pkg-config files so we added an option to disable installing them. - Added `REPROC_INSTALL_CMAKECONFIGDIR` and `REPROC_INSTALL_PKGCONFIGDIR` to control where cmake config files and pkg-config files are installed respectively (default: `${CMAKE_INSTALL_LIBDIR}/cmake` and `${CMAKE_INSTALL_LIBDIR}/pkgconfig`). reproc already uses the values from `GNUInstallDirs` when generating its install rules which are cache variables that be overridden by users. However, `GNUInstallDirs` does not include variables for the installation directories of CMake config files and pkg-config files. vcpkg requires cmake config files to be installed to a different directory than the directory reproc used until now. These options were added to allow vcpkg to control where the config files are installed to. ## 3.0.0 ### General - Removed support for Doxygen (and as a result `REPROC_DOCS`). All the Doxygen directives made the header docstrings rather hard to read directly. Doxygen's output was also too complicated for a simple library such as reproc. Finally, Doxygen doesn't really provide any intuitive support for documenting a set of libraries. I have an idea for a Doxygen alternative using libclang and cmark but I'm not sure when I'll be able to implement it. ### CMake - Renamed `REPROCXX` option to `REPROC++`. `REPROCXX` was initially chosen because CMake didn't recommend using anything other than letters and underscores for variable names. However, `REPROC++` turns out to work without any problems so we use it since it's the expected name for an option to build reproc++. - Stopped modifying the default `CMAKE_INSTALL_PREFIX` on Windows. In 2.0.0, when installing to the default `CMAKE_INSTALL_PREFIX`, you would end up with `C:\Program Files (x86)\reproc` and `C:\Program Files (x86)\reproc++` when installing reproc. In 3.0.0, the default `CMAKE_INSTALL_PREFIX` isn't modified anymore and all libraries are installed to `CMAKE_INSTALL_PREFIX` in exactly the same way as they are on UNIX systems (include and lib subdirectories directly beneath the installation directory). Sticking to the defaults makes it easy to include reproc in various package managers such as vcpkg. ### reproc - `reproc_terminate` and `reproc_kill` don't call `reproc_wait` internally anymore. `reproc_stop` has been changed to call `reproc_wait` after calling `reproc_terminate` or `reproc_kill` so it still behaves the same. Previously, calling `reproc_terminate` on a list of processes would only call `reproc_terminate` on the next process after the previous process had exited or the timeout had expired. This made terminating multiple processes take longer than required. By removing the `reproc_wait` call from `reproc_terminate`, users can first call `reproc_terminate` on all processes before waiting for each of them with `reproc_wait` which makes terminating multiple processes much faster. - Default to using `vfork` instead of `fork` on POSIX systems. This change was made to increase `reproc_start`'s performance when the parent process is using a large amount of memory. In these scenario's, `vfork` can be a lot faster than `fork`. Care is taken to make sure signal handlers in the child don't corrupt the state of the parent process. This change induces an extra constraint in that `set*id` functions cannot be called while a call to `reproc_start` is in process, but this situation is rare enough that the tradeoff for better performance seems worth it. A dependency on pthreads had to be added in order to safely use `vfork` (we needed access to `pthread_sigmask`). The CMake and pkg-config files have been updated to automatically find pthreads so users don't have to find it themselves. - Renamed `reproc_error_to_string` to `reproc_strerror`. The C standard library has `strerror` for retrieving a string representation of an error. By using the same function name (prefixed with reproc) for a function that does the same for reproc's errors, new users will immediately know what the function does. ### reproc++ - reproc++ now takes timeouts as `std::chrono::duration` values (more specific `reproc::milliseconds`) instead of unsigned ints. Taking the `reproc::milliseconds` type explains a lot more about the expected argument than taking an unsigned int. C++14 also added chrono literals which make constructing `reproc::milliseconds` values a lot more concise (`reproc::milliseconds(2000)` => `2000ms`). reproc-14.2.5/CMakeLists.txt000066400000000000000000000013401460055552200156370ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12...3.21) project( reproc VERSION 14.2.4 DESCRIPTION "Cross-platform C99/C++11 process library" HOMEPAGE_URL "https://github.com/DaanDeMeyer/reproc" LANGUAGES C ) # Common options and functions separated for easier reuse in other projects. include(cmake/reproc.cmake) option(REPROC++ "Build reproc++" ${REPROC_DEVELOP}) option( REPROC_MULTITHREADED "Use `pthread_sigmask` and link against the system's thread library" ON ) if(REPROC_MULTITHREADED) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) set(REPROC_THREAD_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) endif() add_subdirectory(reproc) if(REPROC++) enable_language(CXX) add_subdirectory(reproc++) endif() reproc-14.2.5/LICENSE000066400000000000000000000020511460055552200141040ustar00rootroot00000000000000MIT License Copyright (c) Daan De Meyer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. reproc-14.2.5/README.md000066400000000000000000000313501460055552200143620ustar00rootroot00000000000000# reproc - [What is reproc?](#what-is-reproc) - [Features](#features) - [Questions](#questions) - [Installation](#installation) - [Dependencies](#dependencies) - [CMake options](#cmake-options) - [Documentation](#documentation) - [Error handling](#error-handling) - [Multithreading](#multithreading) - [Gotchas](#gotchas) ## What is reproc? 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++. ## Features - 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. ## Usage ```c #include int main(void) { const char *args[] = { "echo", "Hello, world!", NULL }; return reproc_run(args, (reproc_options) { 0 }); } ``` ## Questions 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](https://gitter.im/reproc/Lobby) channel. ## Installation **Note: Building reproc requires CMake 3.12 or higher.** 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: - When using CMake 3.11 or later, you can use the CMake `FetchContent` API to download reproc when running CMake. See for an example. - Another option is to include reproc's repository as a git submodule. 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. After including reproc's source code in your project, it can be built from the root CMakeLists.txt file as follows: ```cmake add_subdirectory() # For example: add_subdirectory(external/reproc) ``` CMake options can be specified before calling `add_subdirectory`: ```cmake set(REPROC++ ON) add_subdirectory() ``` **Note: If the option has already been cached in a previous CMake run, you'll have to clear CMake's cache to apply the new default value.** For more information on configuring reproc's build, see [CMake options](#cmake-options). You can also depend on an installed version of reproc. You can either build and install reproc yourself or install reproc via a package manager. reproc is available in the following package repositories: - Arch User Repository () - vcpkg (https://github.com/microsoft/vcpkg/tree/master/ports/reproc) If using a package manager is not an option, you can build and install reproc from source (CMake 3.13+): ```sh cmake -B build cmake --build build cmake --install build ``` Enable the `REPROC_TEST` option and build the `test` target to run the tests (CMake 3.13+): ```sh cmake -B build -DREPROC_TEST=ON cmake --build build cmake --build build --target test ``` After installing reproc your build system will have to find it. reproc provides both CMake config files and pkg-config files to simplify finding a reproc installation 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 want to use. To find an installed version of reproc using CMake: ```cmake find_package(reproc) # Find reproc. find_package(reproc++) # Find reproc++. ``` After building reproc as part of your project or finding a installed version of reproc, you can link against it from within your CMakeLists.txt file as follows: ```cmake target_link_libraries(myapp reproc) # Link against reproc. target_link_libraries(myapp reproc++) # Link against reproc++. ``` From Meson 0.53.2 onwards, reproc can be included as a CMake subproject in Meson build scripts. See https://mesonbuild.com/CMake-module.html for more information. ## Dependencies By default, reproc has a dependency on pthreads on POSIX systems (`-pthread`) and a dependency on Winsock 2.2 on Windows systems (`-lws2_32`). CMake and pkg-config handle these dependencies automatically. ## CMake options reproc's build can be configured using the following CMake options: ### User - `REPROC++`: Build reproc++ (default: `${REPROC_DEVELOP}`) - `REPROC_TEST`: Build tests (default: `${REPROC_DEVELOP}`) Run the tests by running the `test` binary which can be found in the build directory after building reproc. - `REPROC_EXAMPLES`: Build examples (default: `${REPROC_DEVELOP}`) The resulting binaries will be located in the examples folder of each project subdirectory in the build directory after building reproc. ### Advanced - `REPROC_OBJECT_LIBRARIES`: Build CMake object libraries (default: `${REPROC_DEVELOP}`) This is useful to directly include reproc in another library. When building reproc as a static or shared library, it has to be installed alongside the consuming library which makes distributing the consuming library harder. When using object libraries, reproc's object files are included directly into the consuming library and no extra installation is necessary. **Note: reproc's object libraries will only link correctly from CMake 3.14 onwards.** **Note: This option overrides `BUILD_SHARED_LIBS`.** - `REPROC_INSTALL`: Generate installation rules (default: `ON` unless `REPROC_OBJECT_LIBRARIES` is enabled) - `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`) - `REPROC_MULTITHREADED`: Use `pthread_sigmask` and link against the system's thread library (default: `ON`) ### Developer - `REPROC_DEVELOP`: Configure option default values for development (default: `OFF` unless the `REPROC_DEVELOP` environment variable is set) - `REPROC_SANITIZERS`: Build with sanitizers (default: `${REPROC_DEVELOP}`) - `REPROC_TIDY`: Run clang-tidy when building (default: `${REPROC_DEVELOP}`) - `REPROC_WARNINGS`: Enable compiler warnings (default: `${REPROC_DEVELOP}`) - `REPROC_WARNINGS_AS_ERRORS`: Add -Werror or equivalent to the compile flags and clang-tidy (default: `OFF`) ## Documentation Each function and class is documented extensively in its header file. Examples can be found in the examples subdirectory of [reproc](reproc/examples) and [reproc++](reproc++/examples). ## Error handling On failure, Most functions in reproc's API return a negative `errno` (POSIX) or `GetLastError` (Windows) style error code. For actionable errors, reproc provides constants (`REPROC_ETIMEDOUT`, `REPROC_EPIPE`, ...) that can be used to match against the error without having to write platform-specific code. To get a string representation of an error, pass it to `reproc_strerror`. reproc++'s API integrates with the C++ standard library error codes mechanism (`std::error_code` and `std::error_condition`). Most methods in reproc++'s API return `std::error_code` values that contain the actual system error that occurred. You can test against these error codes using values from the `std::errc` enum. See the examples for more information on how to handle errors when using reproc. Note: Both reproc and reproc++ APIs take `options` argument that may define one or more `stop` actions such as `terminate` or `kill`. For that reason if the child process is being terminated or killed using a signal on POSIX, the error code will **not** reflect an error. It's up to the downstream project to *interpret* status codes reflecting unexpected behaviors alongside error codes (see this [example](https://github.com/DaanDeMeyer/reproc/issues/68#issuecomment-959074504)). ## Multithreading Don't call the same operation on the same child process from more than one thread at the same time. For example: reading and writing to a child process from different threads is fine but waiting on the same child process from two different threads at the same time will result in issues. ## Gotchas - (POSIX) It is strongly recommended to not call `waitpid` on pids of processes started by reproc. reproc uses `waitpid` to wait until a process has exited. Unfortunately, `waitpid` cannot be called twice on the same process. This means that `reproc_wait` won't work correctly if `waitpid` has already been called on a child process beforehand outside of reproc. - It is strongly recommended to make sure each child process actually exits using `reproc_wait` or `reproc_stop`. On POSIX, a child process that has exited is a zombie process until the parent process waits on it using `waitpid`. A zombie process takes up resources and can be seen as a resource leak so it is important to make sure all processes exit correctly in a timely fashion. - It is strongly recommended to try terminating a child process by waiting for it to exit or by calling `reproc_terminate` before resorting to `reproc_kill`. 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 calling `reproc_terminate` before calling `reproc_kill`. `reproc_stop` is a handy helper function that can be used to perform multiple stop actions in a row with timeouts inbetween. - (POSIX) It is strongly recommended to ignore the `SIGPIPE` signal in the parent process. On POSIX, writing to a closed stdin pipe of a child process will terminate the parent process with the `SIGPIPE` signal by default. To avoid this, the `SIGPIPE` signal has to be ignored in the parent process. If the `SIGPIPE` signal is ignored `reproc_write` will return `REPROC_EPIPE` as expected when writing to a closed stdin pipe. - 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. - (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 Windows `TerminateProcess` function that reproc uses to kill child processes on Windows. - Child processes spawned via reproc inherit a single extra file handle which is used to wait for the child process to exit. If the child process closes this file handle manually, reproc will wrongly detect the child process has exited. If this handle is further inherited by other processes that outlive the child process, reproc will detect the child process is still running even if it has exited. If data is written to this handle, reproc will also wrongly detect the child process has exited. - (Windows) It's not possible to detect if a child process closes its stdout or stderr stream before exiting. The parent process will only be notified that a child process output stream is closed once that child process exits. - (Windows) reproc assumes that Windows creates sockets that are usable as file system objects. More specifically, the default sockets returned by `WSASocket` should have the `XP1_IFS_HANDLES ` flag set. This might not be the case if there are external LSP providers installed on a Windows machine. If this is the case, we recommend removing the software that's providing the extra service providers since they're deprecated and should not be used anymore (see https://docs.microsoft.com/en-us/windows/win32/winsock/categorizing-layered-service-providers-and-applications). reproc-14.2.5/cmake/000077500000000000000000000000001460055552200141615ustar00rootroot00000000000000reproc-14.2.5/cmake/reproc.cmake000066400000000000000000000263131460055552200164620ustar00rootroot00000000000000include(CheckCCompilerFlag) include(CMakePackageConfigHelpers) include(GenerateExportHeader) include(GNUInstallDirs) # Developer options option(REPROC_DEVELOP "Enable all developer options" $ENV{REPROC_DEVELOP}) option(REPROC_TEST "Build tests" ${REPROC_DEVELOP}) option(REPROC_EXAMPLES "Build examples" ${REPROC_DEVELOP}) option(REPROC_WARNINGS "Enable compiler warnings" ${REPROC_DEVELOP}) option(REPROC_TIDY "Run clang-tidy when building" ${REPROC_DEVELOP}) option( REPROC_SANITIZERS "Build with sanitizers on configurations that support it" ${REPROC_DEVELOP} ) option( REPROC_WARNINGS_AS_ERRORS "Add -Werror or equivalent to the compile flags and clang-tidy" ) mark_as_advanced( REPROC_TIDY REPROC_SANITIZERS REPROC_WARNINGS_AS_ERRORS ) # Installation options option(REPROC_OBJECT_LIBRARIES "Build CMake object libraries" ${REPROC_DEVELOP}) if(NOT REPROC_OBJECT_LIBRARIES) set(REPROC_INSTALL_DEFAULT ON) endif() option(REPROC_INSTALL "Generate installation rules" ${REPROC_INSTALL_DEFAULT}) option(REPROC_INSTALL_PKGCONFIG "Install pkg-config files" ON) set( REPROC_INSTALL_CMAKECONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake CACHE STRING "CMake config files installation directory" ) set( REPROC_INSTALL_PKGCONFIGDIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING "pkg-config files installation directory" ) mark_as_advanced( REPROC_OBJECT_LIBRARIES REPROC_INSTALL REPROC_INSTALL_PKGCONFIG REPROC_INSTALL_CMAKECONFIGDIR REPROC_INSTALL_PKGCONFIGDIR ) # Testing if(REPROC_TEST) enable_testing() endif() # Build type if(REPROC_DEVELOP AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) set_property( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo ) endif() # clang-tidy if(REPROC_TIDY) find_program(REPROC_TIDY_PROGRAM clang-tidy) mark_as_advanced(REPROC_TIDY_PROGRAM) if(NOT REPROC_TIDY_PROGRAM) message(FATAL_ERROR "clang-tidy not found") endif() if(REPROC_WARNINGS_AS_ERRORS) set(REPROC_TIDY_PROGRAM ${REPROC_TIDY_PROGRAM} -warnings-as-errors=*) endif() endif() # Functions function(reproc_common TARGET LANGUAGE NAME DIRECTORY) if(LANGUAGE STREQUAL C) set(STANDARD 99) target_compile_features(${TARGET} PUBLIC c_std_99) else() # clang-tidy uses the MSVC standard library instead of MinGW's standard # library so we have to use C++14 (because MSVC headers use C++14). if(MINGW AND REPROC_TIDY) set(STANDARD 14) else() set(STANDARD 11) endif() target_compile_features(${TARGET} PUBLIC cxx_std_11) endif() set_target_properties(${TARGET} PROPERTIES ${LANGUAGE}_STANDARD ${STANDARD} ${LANGUAGE}_STANDARD_REQUIRED ON ${LANGUAGE}_EXTENSIONS OFF OUTPUT_NAME "${NAME}" RUNTIME_OUTPUT_DIRECTORY "${DIRECTORY}" ARCHIVE_OUTPUT_DIRECTORY "${DIRECTORY}" LIBRARY_OUTPUT_DIRECTORY "${DIRECTORY}" ) if(REPROC_TIDY AND REPROC_TIDY_PROGRAM) set_property( TARGET ${TARGET} # `REPROC_TIDY_PROGRAM` is a list so we surround it with quotes to pass it # as a single argument. PROPERTY ${LANGUAGE}_CLANG_TIDY "${REPROC_TIDY_PROGRAM}" ) endif() # Common development flags (warnings + sanitizers + colors) if(REPROC_WARNINGS) if(MSVC) check_c_compiler_flag(/permissive- REPROC_HAVE_PERMISSIVE) target_compile_options(${TARGET} PRIVATE /nologo # Silence MSVC compiler version output. $<$:/WX> # -Werror $<$:/permissive-> ) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15.0) # CMake 3.15 does not add /W3 to the compiler flags by default anymore # so we add /W4 instead. target_compile_options(${TARGET} PRIVATE /W4) endif() if(LANGUAGE STREQUAL C) # Disable MSVC warnings that flag C99 features as non-standard. target_compile_options(${TARGET} PRIVATE /wd4204 /wd4221) endif() else() target_compile_options(${TARGET} PRIVATE -Wall -Wextra -pedantic -Wconversion -Wsign-conversion $<$:-Werror> $<$:-pedantic-errors> ) if(LANGUAGE STREQUAL C OR CMAKE_CXX_COMPILER_ID MATCHES Clang) target_compile_options(${TARGET} PRIVATE -Wmissing-prototypes) endif() endif() if(WIN32) target_compile_definitions(${TARGET} PRIVATE _CRT_SECURE_NO_WARNINGS) endif() target_compile_options(${TARGET} PRIVATE $<$<${LANGUAGE}_COMPILER_ID:GNU>:-fdiagnostics-color> $<$<${LANGUAGE}_COMPILER_ID:Clang>:-fcolor-diagnostics> ) endif() if(REPROC_SANITIZERS AND NOT MSVC AND NOT MINGW) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15.0) set_property( TARGET ${TARGET} PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded ) endif() target_compile_options(${TARGET} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer ) target_link_libraries(${TARGET} PRIVATE -fsanitize=address,undefined) endif() endfunction() function(reproc_library TARGET LANGUAGE) if(REPROC_OBJECT_LIBRARIES) add_library(${TARGET} OBJECT) else() add_library(${TARGET}) endif() reproc_common(${TARGET} ${LANGUAGE} "" lib) if(BUILD_SHARED_LIBS AND NOT REPROC_OBJECT_LIBRARIES) # Enable -fvisibility=hidden and -fvisibility-inlines-hidden. set_target_properties(${TARGET} PROPERTIES ${LANGUAGE}_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN true ) # clang-tidy errors with: unknown argument: '-fno-keep-inline-dllexport' # when enabling `VISIBILITY_INLINES_HIDDEN` on MinGW so we disable it when # running clang-tidy on MinGW. if(MINGW AND REPROC_TIDY) set_property(TARGET ${TARGET} PROPERTY VISIBILITY_INLINES_HIDDEN false) endif() # Disable CMake's default export definition. set_property(TARGET ${TARGET} PROPERTY DEFINE_SYMBOL "") string(TOUPPER ${TARGET} TARGET_UPPER) string(REPLACE + X TARGET_SANITIZED ${TARGET_UPPER}) target_compile_definitions(${TARGET} PRIVATE ${TARGET_SANITIZED}_BUILDING) if(WIN32) target_compile_definitions(${TARGET} PUBLIC ${TARGET_SANITIZED}_SHARED) endif() endif() # Make sure we follow the popular naming convention for shared libraries on # UNIX systems. set_target_properties(${TARGET} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) # Only use the headers from the repository when building. When installing we # want to use the install location of the headers (e.g. /usr/include) as the # include directory instead. target_include_directories(${TARGET} PUBLIC $ ) # Adapted from https://codingnest.com/basic-cmake-part-2/. # Each library is installed separately (with separate config files). if(REPROC_INSTALL) # Headers install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT ${TARGET}-development ) # Library install( TARGETS ${TARGET} EXPORT ${TARGET}-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${TARGET}-runtime LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${TARGET}-runtime NAMELINK_COMPONENT ${TARGET}-development ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${TARGET}-development INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) if(NOT APPLE) set_property( TARGET ${TARGET} PROPERTY INSTALL_RPATH $ORIGIN ) endif() # CMake config configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config.cmake INSTALL_DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET} ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config-version.cmake COMPATIBILITY SameMajorVersion ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config-version.cmake DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET} COMPONENT ${TARGET}-development ) install( EXPORT ${TARGET}-targets DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET} COMPONENT ${TARGET}-development ) # pkg-config if(REPROC_INSTALL_PKGCONFIG) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.pc @ONLY ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.pc DESTINATION ${REPROC_INSTALL_PKGCONFIGDIR} COMPONENT ${TARGET}-development ) endif() endif() endfunction() function(reproc_test TARGET NAME LANGUAGE) if(NOT REPROC_TEST) return() endif() if(LANGUAGE STREQUAL C) set(EXTENSION c) else() set(EXTENSION cpp) endif() add_executable(${TARGET}-test-${NAME} test/${NAME}.${EXTENSION}) reproc_common(${TARGET}-test-${NAME} ${LANGUAGE} ${NAME} test) target_link_libraries(${TARGET}-test-${NAME} PRIVATE ${TARGET}) if(MINGW) target_compile_definitions(${TARGET}-test-${NAME} PRIVATE __USE_MINGW_ANSI_STDIO=1 # Add %zu on Mingw ) endif() add_test(NAME ${TARGET}-test-${NAME} COMMAND ${TARGET}-test-${NAME}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources/${NAME}.c) target_compile_definitions(${TARGET}-test-${NAME} PRIVATE RESOURCE_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/resources" ) if (NOT TARGET ${TARGET}-resource-${NAME}) add_executable(${TARGET}-resource-${NAME} resources/${NAME}.c) reproc_common(${TARGET}-resource-${NAME} C ${NAME} resources) endif() # Make sure the test resource is available when running the test. add_dependencies(${TARGET}-test-${NAME} ${TARGET}-resource-${NAME}) endif() endfunction() function(reproc_example TARGET NAME LANGUAGE) cmake_parse_arguments(OPT "" "" "ARGS;DEPENDS" ${ARGN}) if(NOT REPROC_EXAMPLES) return() endif() if(LANGUAGE STREQUAL C) set(EXTENSION c) else() set(EXTENSION cpp) endif() add_executable(${TARGET}-example-${NAME} examples/${NAME}.${EXTENSION}) reproc_common(${TARGET}-example-${NAME} ${LANGUAGE} ${NAME} examples) target_link_libraries(${TARGET}-example-${NAME} PRIVATE ${TARGET} ${OPT_DEPENDS}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources/${NAME}.c) target_compile_definitions(${TARGET}-example-${NAME} PRIVATE RESOURCE_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/resources" ) if (NOT TARGET ${TARGET}-resource-${NAME}) add_executable(${TARGET}-resource-${NAME} resources/${NAME}.c) reproc_common(${TARGET}-resource-${NAME} C ${NAME} resources) endif() # Make sure the example resource is available when running the example. add_dependencies(${TARGET}-example-${NAME} ${TARGET}-resource-${NAME}) endif() if(REPROC_TEST) if(NOT DEFINED OPT_ARGS) set(OPT_ARGS cmake --help) endif() add_test( NAME ${TARGET}-example-${NAME} COMMAND ${TARGET}-example-${NAME} ${OPT_ARGS} ) endif() endfunction() reproc-14.2.5/reproc++/000077500000000000000000000000001460055552200145215ustar00rootroot00000000000000reproc-14.2.5/reproc++/CMakeLists.txt000066400000000000000000000011001460055552200172510ustar00rootroot00000000000000reproc_library(reproc++ CXX) target_link_libraries(reproc++ PRIVATE reproc $<$:Threads::Threads> ) target_sources( reproc++ PRIVATE src/reproc.cpp # We manually propagate reproc's object files until CMake adds support for # doing it automatically. INTERFACE $<$:$> ) reproc_example(reproc++ drain CXX) reproc_example(reproc++ forward CXX) reproc_example(reproc++ run CXX) if(REPROC_MULTITHREADED) reproc_example(reproc++ background CXX DEPENDS Threads::Threads) endif() reproc-14.2.5/reproc++/examples/000077500000000000000000000000001460055552200163375ustar00rootroot00000000000000reproc-14.2.5/reproc++/examples/background.cpp000066400000000000000000000047571460055552200211770ustar00rootroot00000000000000#include #include #include #include #include #include static int fail(std::error_code ec) { std::cerr << ec.message(); return ec.value(); } // The background example reads the output of a child process in a background // thread and shows how to access the current output in the main thread while // the background thread is still running. // Like the forward example it forwards its arguments to a child process and // prints the child process output on stdout. int main(int argc, const char **argv) { if (argc <= 1) { std::cerr << "No arguments provided. Example usage: " << "./background cmake --help"; return EXIT_FAILURE; } reproc::process process; reproc::stop_actions stop = { { reproc::stop::terminate, reproc::milliseconds(5000) }, { reproc::stop::kill, reproc::milliseconds(2000) }, {} }; reproc::options options; options.stop = stop; std::error_code ec = process.start(argv + 1, options); if (ec == std::errc::no_such_file_or_directory) { std::cerr << "Program not found. Make sure it's available from the PATH."; return ec.value(); } else if (ec) { return fail(ec); } // We need a mutex along with `output` to prevent the main thread and // background thread from modifying `output` at the same time (`std::string` // is not thread safe). std::string output; std::mutex mutex; auto drain_async = std::async(std::launch::async, [&process, &output, &mutex]() { // `sink::thread_safe::string` locks a given mutex before appending to the // given string, allowing working with the string across multiple threads if // the mutex is locked in the other threads as well. reproc::sink::thread_safe::string sink(output, mutex); return reproc::drain(process, sink, sink); }); // Show new output every 2 seconds. while (drain_async.wait_for(std::chrono::seconds(2)) != std::future_status::ready) { std::lock_guard lock(mutex); std::cout << output; // Clear output that's already been flushed to `std::cout`. output.clear(); } // Flush any remaining output of `process`. std::cout << output; // Check if any errors occurred in the background thread. ec = drain_async.get(); if (ec) { return fail(ec); } int status = 0; std::tie(status, ec) = process.stop(options.stop); if (ec) { return fail(ec); } return status; } reproc-14.2.5/reproc++/examples/drain.cpp000066400000000000000000000053271460055552200201470ustar00rootroot00000000000000#include #include #include #include static int fail(std::error_code ec) { std::cerr << ec.message(); return ec.value(); } // Uses `reproc::drain` to show the output of the given command. int main(int argc, const char **argv) { if (argc <= 1) { std::cerr << "No arguments provided. Example usage: " << "./drain cmake --help"; return EXIT_FAILURE; } reproc::process process; // reproc++ uses error codes to report errors. If exceptions are preferred, // convert `std::error_code`'s to exceptions using `std::system_error`. std::error_code ec = process.start(argv + 1); // reproc++ converts system errors to `std::error_code`'s of the system // category. These can be matched against using values from the `std::errc` // error condition. See https://en.cppreference.com/w/cpp/error/errc for more // information. if (ec == std::errc::no_such_file_or_directory) { std::cerr << "Program not found. Make sure it's available from the PATH."; return ec.value(); } else if (ec) { return fail(ec); } // `reproc::drain` reads from the stdout and stderr streams of `process` until // both are closed or an error occurs. Providing it with a string sink for a // specific stream makes it store all output of that stream in the string // passed to the string sink. Passing the same sink to both the `out` and // `err` arguments of `reproc::drain` causes the stdout and stderr output to // get stored in the same string. std::string output; reproc::sink::string sink(output); // By default, reproc only redirects stdout to a pipe and not stderr so we // pass `reproc::sink::null` as the sink for stderr here. We could also pass // `sink` but it wouldn't receive any data from stderr. ec = reproc::drain(process, sink, reproc::sink::null); if (ec) { return fail(ec); } std::cout << output << std::flush; // It's easy to define your own sinks as well. Take a look at `drain.hpp` in // the repository to see how `sink::string` and other sinks are implemented. // The documentation of `reproc::drain` also provides more information on the // requirements a sink should fulfill. // By default, The `process` destructor waits indefinitely for the child // process to exit to ensure proper cleanup. See the forward example for // information on how this can be configured. However, when relying on the // `process` destructor, we cannot check the exit status of the process so it // usually makes sense to explicitly wait for the process to exit and check // its exit status. int status = 0; std::tie(status, ec) = process.wait(reproc::infinite); if (ec) { return fail(ec); } return status; } reproc-14.2.5/reproc++/examples/forward.cpp000066400000000000000000000056741460055552200205230ustar00rootroot00000000000000#include #include #include static int fail(std::error_code ec) { std::cerr << ec.message(); return ec.value(); } // The forward example forwards the program arguments to a child process and // prints its output on stdout. // // Example: "./forward cmake --help" will print CMake's help output. // // This program can be used to verify that manually executing a command and // executing it using reproc produces the same output. int main(int argc, const char **argv) { if (argc <= 1) { std::cerr << "No arguments provided. Example usage: ./forward cmake --help"; return EXIT_FAILURE; } reproc::process process; // Stop actions can be passed to both `process::start` (via `options`) and // `process::stop`. Stop actions passed to `process::start` are passed to // `process::stop` in the `process` destructor. This can be used to make sure // that a child process is always stopped correctly when its corresponding // `process` instance is destroyed. // // Any program can be started with forward so we make sure the child process // is cleaned up correctly by specifying `reproc::terminate` which sends // `SIGTERM` (POSIX) or `CTRL-BREAK` (Windows) and waits five seconds. We also // add the `reproc::kill` flag which sends `SIGKILL` (POSIX) or calls // `TerminateProcess` (Windows) if the process hasn't exited after five // seconds and waits two more seconds for the child process to exit. // // If the `stop_actions` struct passed to `process::start` is // default-initialized, the `process` destructor will wait indefinitely for // the child process to exit. // // Note that C++14 has chrono literals which allows // `reproc::milliseconds(5000)` to be replaced with `5000ms`. reproc::stop_actions stop = { { reproc::stop::noop, reproc::milliseconds(0) }, { reproc::stop::terminate, reproc::milliseconds(5000) }, { reproc::stop::kill, reproc::milliseconds(2000) } }; reproc::options options; options.stop = stop; // We have the child process inherit the parent's standard streams so the // child process reads directly from the stdin and writes directly to the // stdout/stderr of the parent process. options.redirect.parent = true; // Exclude `argv[0]` which is the current program's name. std::error_code ec = process.start(argv + 1, options); if (ec == std::errc::no_such_file_or_directory) { std::cerr << "Program not found. Make sure it's available from the PATH."; return ec.value(); } else if (ec) { return fail(ec); } // Call `process::stop` manually so we can access the exit status. We add // `reproc::wait` with a timeout of ten seconds to give the process time to // exit on its own before sending `SIGTERM`. options.stop.first = { reproc::stop::wait, reproc::milliseconds(10000) }; int status = 0; std::tie(status, ec) = process.stop(options.stop); if (ec) { return fail(ec); } return status; } reproc-14.2.5/reproc++/examples/run.cpp000066400000000000000000000007451460055552200176550ustar00rootroot00000000000000#include #include // Equivalent to reproc's run example but implemented using reproc++. int main(int argc, const char **argv) { (void) argc; int status = -1; std::error_code ec; reproc::options options; options.redirect.parent = true; options.deadline = reproc::milliseconds(5000); std::tie(status, ec) = reproc::run(argv + 1, options); if (ec) { std::cerr << ec.message() << std::endl; } return ec ? ec.value() : status; } reproc-14.2.5/reproc++/include/000077500000000000000000000000001460055552200161445ustar00rootroot00000000000000reproc-14.2.5/reproc++/include/reproc++/000077500000000000000000000000001460055552200175645ustar00rootroot00000000000000reproc-14.2.5/reproc++/include/reproc++/arguments.hpp000066400000000000000000000026751460055552200223140ustar00rootroot00000000000000#pragma once #include #include namespace reproc { class arguments : public detail::array { public: arguments(const char *const *argv) // NOLINT : detail::array(argv, false) {} /*! `Arguments` must be iterable as a sequence of strings. Examples of types that satisfy this requirement are `std::vector` and `std::array`. `arguments` has the same restrictions as `argv` in `reproc_start` except that it should not end with `NULL` (`start` allocates a new array which includes the missing `NULL` value). */ template > arguments(const Arguments &arguments) // NOLINT : detail::array(from(arguments), true) {} private: template static const char *const *from(const Arguments &arguments); }; template const char *const *arguments::from(const Arguments &arguments) { using size_type = typename Arguments::value_type::size_type; const char **argv = new const char *[arguments.size() + 1]; std::size_t current = 0; for (const auto &argument : arguments) { char *string = new char[argument.size() + 1]; argv[current++] = string; for (size_type i = 0; i < argument.size(); i++) { *string++ = argument[i]; } *string = '\0'; } argv[current] = nullptr; return argv; } } reproc-14.2.5/reproc++/include/reproc++/detail/000077500000000000000000000000001460055552200210265ustar00rootroot00000000000000reproc-14.2.5/reproc++/include/reproc++/detail/array.hpp000066400000000000000000000014741460055552200226630ustar00rootroot00000000000000#pragma once #include namespace reproc { namespace detail { class array { const char *const *data_; bool owned_; public: array(const char *const *data, bool owned) noexcept : data_(data), owned_(owned) {} array(array &&other) noexcept : data_(other.data_), owned_(other.owned_) { other.data_ = nullptr; other.owned_ = false; } array &operator=(array &&other) noexcept { if (&other != this) { data_ = other.data_; owned_ = other.owned_; other.data_ = nullptr; other.owned_ = false; } return *this; } ~array() noexcept { if (owned_) { for (size_t i = 0; data_[i] != nullptr; i++) { delete[] data_[i]; } delete[] data_; } } const char *const *data() const noexcept { return data_; } }; } } reproc-14.2.5/reproc++/include/reproc++/detail/type_traits.hpp000066400000000000000000000005451460055552200241120ustar00rootroot00000000000000#pragma once #include namespace reproc { namespace detail { template using enable_if = typename std::enable_if::type; template using is_char_array = std::is_convertible; template using enable_if_not_char_array = enable_if::value>; } } reproc-14.2.5/reproc++/include/reproc++/drain.hpp000066400000000000000000000065651460055552200214060ustar00rootroot00000000000000#pragma once #include #include #include #include namespace reproc { /*! `reproc_drain` but takes lambdas as sinks. Return an error code from a sink to break out of `drain` early. `out` and `err` expect the following signature: ```c++ std::error_code sink(stream stream, const uint8_t *buffer, size_t size); ``` */ template std::error_code drain(process &process, Out &&out, Err &&err) { static constexpr uint8_t initial = 0; std::error_code ec; // A single call to `read` might contain multiple messages. By always calling // both sinks once with no data before reading, we give them the chance to // process all previous output before reading from the child process again. ec = out(stream::in, &initial, 0); if (ec) { return ec; } ec = err(stream::in, &initial, 0); if (ec) { return ec; } static constexpr size_t BUFFER_SIZE = 4096; uint8_t buffer[BUFFER_SIZE] = {}; for (;;) { int events = 0; std::tie(events, ec) = process.poll(event::out | event::err, infinite); if (ec) { ec = ec == error::broken_pipe ? std::error_code() : ec; break; } if (events & event::deadline) { ec = std::make_error_code(std::errc::timed_out); break; } stream stream = events & event::out ? stream::out : stream::err; size_t bytes_read = 0; std::tie(bytes_read, ec) = process.read(stream, buffer, BUFFER_SIZE); if (ec && ec != error::broken_pipe) { break; } bytes_read = ec == error::broken_pipe ? 0 : bytes_read; // This used to be `auto &sink = stream == stream::out ? out : err;` but // that doesn't actually work if `out` and `err` are not the same type. if (stream == stream::out) { ec = out(stream, buffer, bytes_read); } else { ec = err(stream, buffer, bytes_read); } if (ec) { break; } } return ec; } namespace sink { /*! Reads all output into `string`. */ class string { std::string &string_; public: explicit string(std::string &string) noexcept : string_(string) {} std::error_code operator()(stream stream, const uint8_t *buffer, size_t size) { (void) stream; string_.append(reinterpret_cast(buffer), size); return {}; } }; /*! Forwards all output to `ostream`. */ class ostream { std::ostream &ostream_; public: explicit ostream(std::ostream &ostream) noexcept : ostream_(ostream) {} std::error_code operator()(stream stream, const uint8_t *buffer, size_t size) { (void) stream; ostream_.write(reinterpret_cast(buffer), static_cast(size)); return {}; } }; /*! Discards all output. */ class discard { public: std::error_code operator()(stream stream, const uint8_t *buffer, size_t size) const noexcept { (void) stream; (void) buffer; (void) size; return {}; } }; constexpr discard null = discard(); namespace thread_safe { /*! `sink::string` but locks the given mutex before invoking the sink. */ class string { sink::string sink_; std::mutex &mutex_; public: string(std::string &string, std::mutex &mutex) noexcept : sink_(string), mutex_(mutex) {} std::error_code operator()(stream stream, const uint8_t *buffer, size_t size) { std::lock_guard lock(mutex_); return sink_(stream, buffer, size); } }; } } } reproc-14.2.5/reproc++/include/reproc++/env.hpp000066400000000000000000000035501460055552200210700ustar00rootroot00000000000000#pragma once #include #include namespace reproc { class env : public detail::array { public: enum type { extend, empty, }; env(const char *const *envp = nullptr) // NOLINT : detail::array(envp, false) {} /*! `Env` must be iterable as a sequence of string pairs. Examples of types that satisfy this requirement are `std::vector>` and `std::map`. The pairs in `env` represent the extra environment variables of the child process and are converted to the right format before being passed as the environment to `reproc_start` via the `env.extra` field of `reproc_options`. */ template > env(const Env &env) // NOLINT : detail::array(from(env), true) {} private: template static const char *const *from(const Env &env); }; template const char *const *env::from(const Env &env) { using name_size_type = typename Env::value_type::first_type::size_type; using value_size_type = typename Env::value_type::second_type::size_type; const char **envp = new const char *[env.size() + 1]; std::size_t current = 0; for (const auto &entry : env) { const auto &name = entry.first; const auto &value = entry.second; // We add 2 to the size to reserve space for the '=' sign and the NUL // terminator at the end of the string. char *string = new char[name.size() + value.size() + 2]; envp[current++] = string; for (name_size_type i = 0; i < name.size(); i++) { *string++ = name[i]; } *string++ = '='; for (value_size_type i = 0; i < value.size(); i++) { *string++ = value[i]; } *string = '\0'; } envp[current] = nullptr; return envp; } } reproc-14.2.5/reproc++/include/reproc++/export.hpp000066400000000000000000000007251460055552200216220ustar00rootroot00000000000000#pragma once #ifndef REPROCXX_EXPORT #ifdef _WIN32 #ifdef REPROCXX_SHARED #ifdef REPROCXX_BUILDING #define REPROCXX_EXPORT __declspec(dllexport) #else #define REPROCXX_EXPORT __declspec(dllimport) #endif #else #define REPROCXX_EXPORT #endif #else #ifdef REPROCXX_BUILDING #define REPROCXX_EXPORT __attribute__((visibility("default"))) #else #define REPROCXX_EXPORT #endif #endif #endif reproc-14.2.5/reproc++/include/reproc++/input.hpp000066400000000000000000000012031460055552200214300ustar00rootroot00000000000000#pragma once #include #include namespace reproc { class input { const uint8_t *data_ = nullptr; size_t size_ = 0; public: input() = default; input(const uint8_t *data, size_t size) : data_(data), size_(size) {} /*! Implicitly convert from string literals. */ template input(const char (&data)[N]) // NOLINT : data_(reinterpret_cast(data)), size_(N) {} input(const input &other) = default; input &operator=(const input &) = default; const uint8_t *data() const noexcept { return data_; } size_t size() const noexcept { return size_; } }; } reproc-14.2.5/reproc++/include/reproc++/reproc.hpp000066400000000000000000000134301460055552200215700ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include // Forward declare `reproc_t` so we don't have to include reproc.h in the // header. struct reproc_t; /*! The `reproc` namespace wraps all reproc++ declarations. `process` wraps reproc's API inside a C++ class. To avoid exposing reproc's API when using reproc++ all structs, enums and constants of reproc have a replacement in reproc++. Only differences in behaviour compared to reproc are documented. Refer to reproc.h and the examples for general information on how to use reproc. */ namespace reproc { /*! Conversion from reproc `errno` constants to `std::errc` constants: https://en.cppreference.com/w/cpp/error/errc */ using error = std::errc; namespace signal { REPROCXX_EXPORT extern const int kill; REPROCXX_EXPORT extern const int terminate; } /*! Timeout values are passed as `reproc::milliseconds` instead of `int` in reproc++. */ using milliseconds = std::chrono::duration; REPROCXX_EXPORT extern const milliseconds infinite; REPROCXX_EXPORT extern const milliseconds deadline; enum class stop { noop, wait, terminate, kill, }; struct stop_action { stop action; milliseconds timeout; }; struct stop_actions { stop_action first; stop_action second; stop_action third; }; #if defined(_WIN32) using handle = void *; #else using handle = int; #endif struct redirect { enum type { default_, // Unfortunately, both `default` and `auto` are keywords. pipe, parent, discard, // stdout would conflict with a macro on Windows. stdout_, // Unfortunately, class members and nested enum members can't have the same // name. handle_, file_, path_, }; enum type type; reproc::handle handle; FILE *file; const char *path; }; struct options { struct { reproc::env::type behavior; /*! Implicitly converts from any STL container of string pairs to the environment format expected by `reproc_start`. */ reproc::env extra; } env = {}; const char *working_directory = nullptr; struct { struct redirect in; struct redirect out; struct redirect err; bool parent; bool discard; FILE *file; const char *path; } redirect = {}; struct stop_actions stop = {}; reproc::milliseconds timeout = reproc::milliseconds(0); reproc::milliseconds deadline = reproc::milliseconds(0); /*! Implicitly converts from string literals to the pointer size pair expected by `reproc_start`. */ class input input; bool nonblocking = false; /*! Make a shallow copy of `options`. */ static options clone(const options &other) { struct options clone; clone.env.behavior = other.env.behavior; // Make sure we make a shallow copy of `environment`. clone.env.extra = other.env.extra.data(); clone.working_directory = other.working_directory; clone.redirect = other.redirect; clone.stop = other.stop; clone.timeout = other.timeout; clone.deadline = other.deadline; clone.input = other.input; return clone; } }; enum class stream { in, out, err, }; namespace event { struct source; } /*! Improves on reproc's API by adding RAII and changing the API of some functions to be more idiomatic C++. */ class process { public: REPROCXX_EXPORT process(); REPROCXX_EXPORT ~process() noexcept; // Enforce unique ownership of child processes. REPROCXX_EXPORT process(process &&other) noexcept; REPROCXX_EXPORT process &operator=(process &&other) noexcept; /*! `reproc_start` but implicitly converts from STL containers to the arguments format expected by `reproc_start`. */ REPROCXX_EXPORT std::error_code start(const arguments &arguments, const options &options = {}) noexcept; REPROCXX_EXPORT std::pair pid() noexcept; /*! Sets the `fork` option in `reproc_options` and calls `start`. Returns `true` in the child process and `false` in the parent process. */ REPROCXX_EXPORT std::pair fork(const options &options = {}) noexcept; /*! Shorthand for `reproc::poll` that only polls this process. Returns a pair of (events, error). */ REPROCXX_EXPORT std::pair poll(int interests, milliseconds timeout = infinite); /*! `reproc_read` but returns a pair of (bytes read, error). */ REPROCXX_EXPORT std::pair read(stream stream, uint8_t *buffer, size_t size) noexcept; /*! reproc_write` but returns a pair of (bytes_written, error). */ REPROCXX_EXPORT std::pair write(const uint8_t *buffer, size_t size) noexcept; REPROCXX_EXPORT std::error_code close(stream stream) noexcept; /*! `reproc_wait` but returns a pair of (status, error). */ REPROCXX_EXPORT std::pair wait(milliseconds timeout) noexcept; REPROCXX_EXPORT std::error_code terminate() noexcept; REPROCXX_EXPORT std::error_code kill() noexcept; /*! `reproc_stop` but returns a pair of (status, error). */ REPROCXX_EXPORT std::pair stop(stop_actions stop) noexcept; private: REPROCXX_EXPORT friend std::error_code poll(event::source *sources, size_t num_sources, milliseconds timeout); std::unique_ptr impl_; }; namespace event { enum { in = 1 << 0, out = 1 << 1, err = 1 << 2, exit = 1 << 3, deadline = 1 << 4, }; struct source { class process process; int interests; int events; }; } REPROCXX_EXPORT std::error_code poll(event::source *sources, size_t num_sources, milliseconds timeout = infinite); } reproc-14.2.5/reproc++/include/reproc++/run.hpp000066400000000000000000000016531460055552200211060ustar00rootroot00000000000000#pragma once #include #include namespace reproc { template std::pair run(const arguments &arguments, const options &options, Out &&out, Err &&err) { process process; std::error_code ec; ec = process.start(arguments, options); if (ec) { return { -1, ec }; } ec = drain(process, std::forward(out), std::forward(err)); if (ec) { return { -1, ec }; } return process.stop(options.stop); } inline std::pair run(const arguments &arguments, const options &options = {}) { struct options modified = options::clone(options); if (!options.redirect.discard && options.redirect.file == nullptr && options.redirect.path == nullptr) { modified.redirect.parent = true; } return run(arguments, modified, sink::null, sink::null); } } reproc-14.2.5/reproc++/reproc++-config.cmake.in000066400000000000000000000004541460055552200210160ustar00rootroot00000000000000@PACKAGE_INIT@ set(REPROC_MULTITHREADED @REPROC_MULTITHREADED@) include(CMakeFindDependencyMacro) find_dependency(reproc @PROJECT_VERSION@) if(REPROC_MULTITHREADED) set(THREADS_PREFER_PTHREAD_FLAG ON) find_dependency(Threads) endif() include(${CMAKE_CURRENT_LIST_DIR}/@TARGET@-targets.cmake) reproc-14.2.5/reproc++/reproc++.pc.in000066400000000000000000000006031460055552200170710ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ Name: @TARGET@ Description: @PROJECT_DESCRIPTION@ URL: @PROJECT_HOMEPAGE_URL@ Version: @PROJECT_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -l@TARGET@ Libs.private: @REPROC_THREAD_LIBRARY@ Requires.private: reproc = @PROJECT_VERSION@ reproc-14.2.5/reproc++/src/000077500000000000000000000000001460055552200153105ustar00rootroot00000000000000reproc-14.2.5/reproc++/src/reproc.cpp000066400000000000000000000112701460055552200173070ustar00rootroot00000000000000#include #include namespace reproc { namespace signal { const int kill = REPROC_SIGKILL; const int terminate = REPROC_SIGTERM; } const milliseconds infinite = milliseconds(REPROC_INFINITE); const milliseconds deadline = milliseconds(REPROC_DEADLINE); static std::error_code error_code_from(int r) { if (r >= 0) { return {}; } if (r == REPROC_EPIPE) { // https://github.com/microsoft/STL/pull/406 return { static_cast(std::errc::broken_pipe), std::generic_category() }; } return { -r, std::system_category() }; } static reproc_stop_actions reproc_stop_actions_from(stop_actions stop) { return { { static_cast(stop.first.action), stop.first.timeout.count() }, { static_cast(stop.second.action), stop.second.timeout.count() }, { static_cast(stop.third.action), stop.third.timeout.count() } }; } static reproc_redirect reproc_redirect_from(redirect redirect) { return { static_cast(redirect.type), redirect.handle, redirect.file, redirect.path }; } static reproc_options reproc_options_from(const options &options, bool fork) { return { options.working_directory, { static_cast(options.env.behavior), options.env.extra.data() }, { reproc_redirect_from(options.redirect.in), reproc_redirect_from(options.redirect.out), reproc_redirect_from(options.redirect.err), options.redirect.parent, options.redirect.discard, options.redirect.file, options.redirect.path }, reproc_stop_actions_from(options.stop), options.deadline.count(), { options.input.data(), options.input.size() }, options.nonblocking, fork }; } process::process() : impl_(reproc_new(), reproc_destroy) {} process::~process() noexcept = default; process::process(process &&other) noexcept = default; process &process::operator=(process &&other) noexcept = default; std::error_code process::start(const arguments &arguments, const options &options) noexcept { reproc_options reproc_options = reproc_options_from(options, false); int r = reproc_start(impl_.get(), arguments.data(), reproc_options); return error_code_from(r); } std::pair process::fork(const options &options) noexcept { reproc_options reproc_options = reproc_options_from(options, true); int r = reproc_start(impl_.get(), nullptr, reproc_options); return { r == 0, error_code_from(r) }; } std::pair process::poll(int interests, milliseconds timeout) { event::source source{ std::move(*this), interests, 0 }; std::error_code ec = ::reproc::poll(&source, 1, timeout); *this = std::move(source.process); return { source.events, ec }; } std::pair process::read(stream stream, uint8_t *buffer, size_t size) noexcept { int r = reproc_read(impl_.get(), static_cast(stream), buffer, size); return { r, error_code_from(r) }; } std::pair process::write(const uint8_t *buffer, size_t size) noexcept { int r = reproc_write(impl_.get(), buffer, size); return { r, error_code_from(r) }; } std::error_code process::close(stream stream) noexcept { int r = reproc_close(impl_.get(), static_cast(stream)); return error_code_from(r); } std::pair process::wait(milliseconds timeout) noexcept { int r = reproc_wait(impl_.get(), timeout.count()); return { r, error_code_from(r) }; } std::error_code process::terminate() noexcept { int r = reproc_terminate(impl_.get()); return error_code_from(r); } std::error_code process::kill() noexcept { int r = reproc_kill(impl_.get()); return error_code_from(r); } std::pair process::stop(stop_actions stop) noexcept { int r = reproc_stop(impl_.get(), reproc_stop_actions_from(stop)); return { r, error_code_from(r) }; } std::pair process::pid() noexcept { int r = reproc_pid(impl_.get()); return { r, error_code_from(r) }; } std::error_code poll(event::source *sources, size_t num_sources, milliseconds timeout) { auto *reproc_sources = new reproc_event_source[num_sources]; for (size_t i = 0; i < num_sources; i++) { reproc_sources[i] = { sources[i].process.impl_.get(), sources[i].interests, 0 }; } int r = reproc_poll(reproc_sources, num_sources, timeout.count()); if (r >= 0) { for (size_t i = 0; i < num_sources; i++) { sources[i].events = reproc_sources[i].events; } } delete[] reproc_sources; return error_code_from(r); } } reproc-14.2.5/reproc/000077500000000000000000000000001460055552200143735ustar00rootroot00000000000000reproc-14.2.5/reproc/CMakeLists.txt000066400000000000000000000027141460055552200171370ustar00rootroot00000000000000if(WIN32) set(REPROC_WINSOCK_LIBRARY ws2_32) elseif(CMAKE_SYSTEM_NAME MATCHES Linux) set(REPROC_RT_LIBRARY rt) # clock_gettime endif() reproc_library(reproc C) if(REPROC_MULTITHREADED) target_compile_definitions(reproc PRIVATE REPROC_MULTITHREADED) target_link_libraries(reproc PRIVATE Threads::Threads) endif() if(WIN32) set(PLATFORM windows) target_compile_definitions(reproc PRIVATE WIN32_LEAN_AND_MEAN) target_link_libraries(reproc PRIVATE ${REPROC_WINSOCK_LIBRARY}) else() set(PLATFORM posix) if(NOT APPLE) target_link_libraries(reproc PRIVATE ${REPROC_RT_LIBRARY}) endif() endif() target_sources(reproc PRIVATE src/clock.${PLATFORM}.c src/drain.c src/error.${PLATFORM}.c src/handle.${PLATFORM}.c src/init.${PLATFORM}.c src/options.c src/pipe.${PLATFORM}.c src/process.${PLATFORM}.c src/redirect.${PLATFORM}.c src/redirect.c src/reproc.c src/run.c src/strv.c src/utf.${PLATFORM}.c ) reproc_test(reproc argv C) reproc_test(reproc deadline C) reproc_test(reproc env C) reproc_test(reproc io C) reproc_test(reproc overflow C) reproc_test(reproc path C) reproc_test(reproc stop C) reproc_test(reproc working-directory C) reproc_test(reproc pid C) if(UNIX) reproc_test(reproc fork C) endif() reproc_example(reproc drain C) reproc_example(reproc env C ARGS PROJECT=REPROC) reproc_example(reproc path C) reproc_example(reproc poll C) reproc_example(reproc read C) reproc_example(reproc parent C) reproc_example(reproc run C) reproc-14.2.5/reproc/examples/000077500000000000000000000000001460055552200162115ustar00rootroot00000000000000reproc-14.2.5/reproc/examples/drain.c000066400000000000000000000032041460055552200174510ustar00rootroot00000000000000#include #include #include // Shows the output of the given command using `reproc_drain`. int main(int argc, const char **argv) { (void) argc; reproc_t *process = NULL; char *output = NULL; int r = REPROC_ENOMEM; process = reproc_new(); if (process == NULL) { goto finish; } r = reproc_start(process, argv + 1, (reproc_options){ 0 }); if (r < 0) { goto finish; } r = reproc_close(process, REPROC_STREAM_IN); if (r < 0) { goto finish; } // `reproc_drain` reads from a child process and passes the output to the // given sinks. A sink consists of a function pointer and a context pointer // which is always passed to the function. reproc provides several built-in // sinks such as `reproc_sink_string` which stores all provided output in the // given string. Passing the same sink to both output streams makes sure the // output from both streams is combined into a single string. reproc_sink sink = reproc_sink_string(&output); // By default, reproc only redirects stdout to a pipe and not stderr so we // pass `REPROC_SINK_NULL` as the sink for stderr here. We could also pass // `sink` but it wouldn't receive any data from stderr. r = reproc_drain(process, sink, REPROC_SINK_NULL); if (r < 0) { goto finish; } printf("%s", output); r = reproc_wait(process, REPROC_INFINITE); if (r < 0) { goto finish; } finish: // Memory allocated by `reproc_sink_string` must be freed with `reproc_free`. reproc_free(output); reproc_destroy(process); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/examples/env.c000066400000000000000000000010631460055552200171450ustar00rootroot00000000000000#include #include // Runs a binary as a child process that prints all its environment variables to // stdout and exits. Additional environment variables (in the format A=B) passed // via the command line are added to the child process environment variables. int main(int argc, const char **argv) { (void) argc; const char *args[] = { RESOURCE_DIRECTORY "/env", NULL }; int r = reproc_run(args, (reproc_options){ .env.extra = argv + 1 }); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/examples/parent.c000066400000000000000000000015411460055552200176470ustar00rootroot00000000000000#include #include // Forwards the provided command to `reproc_start` and redirects the standard // streams of the child process to the standard streams of the parent process. int main(int argc, const char **argv) { if (argc <= 1) { fprintf(stderr, "No arguments provided. Example usage: ./inherit cmake --help"); return EXIT_FAILURE; } reproc_t *process = NULL; int r = REPROC_ENOMEM; process = reproc_new(); if (process == NULL) { goto finish; } r = reproc_start(process, argv + 1, (reproc_options){ .redirect.parent = true }); if (r < 0) { goto finish; } r = reproc_wait(process, REPROC_INFINITE); if (r < 0) { goto finish; } finish: reproc_destroy(process); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/examples/path.c000066400000000000000000000005541460055552200173150ustar00rootroot00000000000000#include #include // Redirects the output of the given command to the reproc.out file. int main(int argc, const char **argv) { (void) argc; int r = reproc_run(argv + 1, (reproc_options){ .redirect.path = "reproc.out" }); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/examples/poll.c000066400000000000000000000047431460055552200173330ustar00rootroot00000000000000#ifdef _WIN32 #include static void millisleep(long ms) { Sleep((DWORD) ms); } static int getpid() { return (int) GetCurrentProcessId(); } #else #define _POSIX_C_SOURCE 200809L #include #include static inline void millisleep(long ms) { nanosleep(&(struct timespec){ .tv_sec = (ms) / 1000, .tv_nsec = ((ms) % 1000L) * 1000000 }, NULL); } #endif #include #include #include enum { NUM_CHILDREN = 20 }; static int parent(const char *program) { reproc_event_source children[NUM_CHILDREN] = { { 0 } }; int r = -1; for (int i = 0; i < NUM_CHILDREN; i++) { reproc_t *process = reproc_new(); const char *args[] = { program, "child", NULL }; r = reproc_start(process, args, (reproc_options){ .nonblocking = true }); if (r < 0) { goto finish; } children[i].process = process; children[i].interests = REPROC_EVENT_OUT; } for (;;) { r = reproc_poll(children, NUM_CHILDREN, REPROC_INFINITE); if (r < 0) { r = r == REPROC_EPIPE ? 0 : r; goto finish; } for (int i = 0; i < NUM_CHILDREN; i++) { if (children[i].process == NULL || !children[i].events) { continue; } uint8_t output[4096]; r = reproc_read(children[i].process, REPROC_STREAM_OUT, output, sizeof(output)); if (r == REPROC_EPIPE) { // `reproc_destroy` returns `NULL`. Event sources with their process set // to `NULL` are ignored by `reproc_poll`. children[i].process = reproc_destroy(children[i].process); continue; } if (r < 0) { goto finish; } output[r] = '\0'; printf("%s\n", output); } } finish: for (int i = 0; i < NUM_CHILDREN; i++) { reproc_destroy(children[i].process); } if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } static int child(void) { srand(((unsigned int) getpid())); int ms = rand() % NUM_CHILDREN * 4; // NOLINT millisleep(ms); printf("Process %i slept %i milliseconds.", getpid(), ms); return EXIT_SUCCESS; } // Starts a number of child processes that each sleep a random amount of // milliseconds before printing a message and exiting. The parent process polls // each of the child processes and prints their messages to stdout. int main(int argc, const char **argv) { return argc > 1 && strcmp(argv[1], "child") == 0 ? child() : parent(argv[0]); } reproc-14.2.5/reproc/examples/read.c000066400000000000000000000066621460055552200173020ustar00rootroot00000000000000#include #include #include // Prints the output of the given command using `reproc_read`. Usually, using // `reproc_run` or `reproc_drain` is a better solution when dealing with a // single child process. int main(int argc, const char **argv) { (void) argc; // `reproc_t` stores necessary information between calls to reproc's API. reproc_t *process = NULL; char *output = NULL; size_t size = 0; int r = REPROC_ENOMEM; process = reproc_new(); if (process == NULL) { goto finish; } // `reproc_start` takes a child process instance (`reproc_t`), argv and // a set of options including the working directory and environment of the // child process. If the working directory is `NULL` the working directory of // the parent process is used. If the environment is `NULL`, the environment // of the parent process is used. r = reproc_start(process, argv + 1, (reproc_options){ 0 }); // On failure, reproc's API functions return a negative `errno` (POSIX) or // `GetLastError` (Windows) style error code. To check against common error // codes, reproc provides cross platform constants such as `REPROC_EPIPE` and // `REPROC_ETIMEDOUT`. if (r < 0) { goto finish; } // Close the stdin stream since we're not going to write any input to the // child process. r = reproc_close(process, REPROC_STREAM_IN); if (r < 0) { goto finish; } // Read the entire output of the child process. I've found this pattern to be // the most readable when reading the entire output of a child process. The // while loop keeps running until an error occurs in `reproc_read` (the child // process closing its output stream is also reported as an error). for (;;) { uint8_t buffer[4096]; r = reproc_read(process, REPROC_STREAM_OUT, buffer, sizeof(buffer)); if (r < 0) { break; } // On success, `reproc_read` returns the amount of bytes read. size_t bytes_read = (size_t) r; // Increase the size of `output` to make sure it can hold the new output. // This is definitely not the most performant way to grow a buffer so keep // that in mind. Add 1 to size to leave space for the NUL terminator which // isn't included in `output_size`. char *result = realloc(output, size + bytes_read + 1); if (result == NULL) { r = REPROC_ENOMEM; goto finish; } output = result; // Copy new data into `output`. memcpy(output + size, buffer, bytes_read); output[size + bytes_read] = '\0'; size += bytes_read; } // Check that the while loop stopped because the output stream of the child // process was closed and not because of any other error. if (r != REPROC_EPIPE) { goto finish; } printf("%s", output); // Wait for the process to exit. This should always be done since some systems // (POSIX) don't clean up system resources allocated to a child process until // the parent process explicitly waits for it after it has exited. r = reproc_wait(process, REPROC_INFINITE); if (r < 0) { goto finish; } finish: free(output); // Clean up all the resources allocated to the child process (including the // memory allocated by `reproc_new`). Unless custom stop actions are passed to // `reproc_start`, `reproc_destroy` will first wait indefinitely for the child // process to exit. reproc_destroy(process); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/examples/run.c000066400000000000000000000006741460055552200171700ustar00rootroot00000000000000#include #include // Start a process from the arguments given on the command line. Inherit the // parent's standard streams and allow the process to run for maximum 5 seconds // before terminating it. int main(int argc, const char **argv) { (void) argc; int r = reproc_run(argv + 1, (reproc_options){ .deadline = 5000 }); if (r < 0) { fprintf(stderr, "%s\n", reproc_strerror(r)); } return abs(r); } reproc-14.2.5/reproc/include/000077500000000000000000000000001460055552200160165ustar00rootroot00000000000000reproc-14.2.5/reproc/include/reproc/000077500000000000000000000000001460055552200173105ustar00rootroot00000000000000reproc-14.2.5/reproc/include/reproc/drain.h000066400000000000000000000055631460055552200205670ustar00rootroot00000000000000#pragma once #include #ifdef __cplusplus extern "C" { #endif /*! Used by `reproc_drain` to provide data to the caller. Each time data is read, `function` is called with `context`. If a sink returns a non-zero value, `reproc_drain` will return immediately with the same value. */ typedef struct reproc_sink { int (*function)(REPROC_STREAM stream, const uint8_t *buffer, size_t size, void *context); void *context; } reproc_sink; /*! Pass `REPROC_SINK_NULL` as the sink for output streams that have not been redirected to a pipe. */ REPROC_EXPORT extern const reproc_sink REPROC_SINK_NULL; /*! Reads from the child process stdout and stderr until an error occurs or both streams are closed. The `out` and `err` sinks receive the output from stdout and stderr respectively. The same sink may be passed to both `out` and `err`. `reproc_drain` always starts by calling both sinks once with an empty buffer and `stream` set to `REPROC_STREAM_IN` to give each sink the chance to process all output from the previous call to `reproc_drain` one by one. When a stream is closed, its corresponding `sink` is called once with `size` set to zero. Note that his function returns 0 instead of `REPROC_EPIPE` when both output streams of the child process are closed. Actionable errors: - `REPROC_ETIMEDOUT` */ REPROC_EXPORT int reproc_drain(reproc_t *process, reproc_sink out, reproc_sink err); /*! Appends the output of a process (stdout and stderr) to the value of `output`. `output` must point to either `NULL` or a NUL-terminated string. Calls `realloc` as necessary to make space in `output` to store the output of the child process. Make sure to always call `reproc_free` on the value of `output` after calling `reproc_drain` (even if it fails). Because the resulting sink does not store the output size, `strlen` is called each time data is read to calculate the current size of the output. This might cause performance problems when draining processes that produce a lot of output. Similarly, this sink will not work on processes that have NUL terminators in their output because `strlen` is used to calculate the current output size. Returns `REPROC_ENOMEM` if a call to `realloc` fails. `output` will contain any output read from the child process, preceeded by whatever was stored in it at the moment its corresponding sink was passed to `reproc_drain`. The `drain` example shows how to use `reproc_sink_string`. ``` */ REPROC_EXPORT reproc_sink reproc_sink_string(char **output); /*! Discards the output of a process. */ REPROC_EXPORT reproc_sink reproc_sink_discard(void); /*! Calls `free` on `ptr` and returns `NULL`. Use this function to free memory allocated by `reproc_sink_string`. This avoids issues with allocating across module (DLL) boundaries on Windows. */ REPROC_EXPORT void *reproc_free(void *ptr); #ifdef __cplusplus } #endif reproc-14.2.5/reproc/include/reproc/export.h000066400000000000000000000007031460055552200210020ustar00rootroot00000000000000#pragma once #ifndef REPROC_EXPORT #ifdef _WIN32 #ifdef REPROC_SHARED #ifdef REPROC_BUILDING #define REPROC_EXPORT __declspec(dllexport) #else #define REPROC_EXPORT __declspec(dllimport) #endif #else #define REPROC_EXPORT #endif #else #ifdef REPROC_BUILDING #define REPROC_EXPORT __attribute__((visibility("default"))) #else #define REPROC_EXPORT #endif #endif #endif reproc-14.2.5/reproc/include/reproc/reproc.h000066400000000000000000000443401460055552200207600ustar00rootroot00000000000000#pragma once #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*! Used to store information about a child process. `reproc_t` is an opaque type and can be allocated and released via `reproc_new` and `reproc_destroy` respectively. */ typedef struct reproc_t reproc_t; /*! reproc error naming follows POSIX errno naming prefixed with `REPROC`. */ /*! An invalid argument was passed to an API function */ REPROC_EXPORT extern const int REPROC_EINVAL; /*! A timeout value passed to an API function expired. */ REPROC_EXPORT extern const int REPROC_ETIMEDOUT; /*! The child process closed one of its streams (and in the case of stdout/stderr all of the data remaining in that stream has been read). */ REPROC_EXPORT extern const int REPROC_EPIPE; /*! A memory allocation failed. */ REPROC_EXPORT extern const int REPROC_ENOMEM; /*! A call to `reproc_read` or `reproc_write` would have blocked. */ REPROC_EXPORT extern const int REPROC_EWOULDBLOCK; /*! Signal exit status constants. */ REPROC_EXPORT extern const int REPROC_SIGKILL; REPROC_EXPORT extern const int REPROC_SIGTERM; /*! Tells a function that takes a timeout value to wait indefinitely. */ REPROC_EXPORT extern const int REPROC_INFINITE; /*! Tells `reproc_wait` to wait until the deadline passed to `reproc_start` expires. */ REPROC_EXPORT extern const int REPROC_DEADLINE; /*! Stream identifiers used to indicate which stream to act on. */ typedef enum { /*! stdin */ REPROC_STREAM_IN, /*! stdout */ REPROC_STREAM_OUT, /*! stderr */ REPROC_STREAM_ERR, } REPROC_STREAM; /*! Used to tell reproc where to redirect the streams of the child process. */ typedef enum { /*! Use the default redirect behavior, see the documentation for `redirect` in `reproc_options`. */ REPROC_REDIRECT_DEFAULT, /*! Redirect to a pipe. */ REPROC_REDIRECT_PIPE, /*! Redirect to the corresponding stream from the parent process. */ REPROC_REDIRECT_PARENT, /*! Redirect to /dev/null (or NUL on Windows). */ REPROC_REDIRECT_DISCARD, /*! Redirect to child process stdout. Only valid for stderr. */ REPROC_REDIRECT_STDOUT, /*! Redirect to a handle (fd on Linux, HANDLE/SOCKET on Windows). */ REPROC_REDIRECT_HANDLE, /*! Redirect to a `FILE *`. */ REPROC_REDIRECT_FILE, /*! Redirect to a specific path. */ REPROC_REDIRECT_PATH, } REPROC_REDIRECT; /*! Used to tell `reproc_stop` how to stop a child process. */ typedef enum { /*! noop (no operation) */ REPROC_STOP_NOOP, /*! `reproc_wait` */ REPROC_STOP_WAIT, /*! `reproc_terminate` */ REPROC_STOP_TERMINATE, /*! `reproc_kill` */ REPROC_STOP_KILL, } REPROC_STOP; typedef struct reproc_stop_action { REPROC_STOP action; int timeout; } reproc_stop_action; typedef struct reproc_stop_actions { reproc_stop_action first; reproc_stop_action second; reproc_stop_action third; } reproc_stop_actions; // clang-format off #define REPROC_STOP_ACTIONS_NULL (reproc_stop_actions) { \ { REPROC_STOP_NOOP, 0 }, \ { REPROC_STOP_NOOP, 0 }, \ { REPROC_STOP_NOOP, 0 }, \ } // clang-format on #if defined(_WIN32) typedef void *reproc_handle; // `HANDLE` #else typedef int reproc_handle; // fd #endif typedef struct reproc_redirect { /*! Type of redirection. */ REPROC_REDIRECT type; /*! Redirect a stream to an operating system handle. The given handle must be in blocking mode ( `O_NONBLOCK` and `OVERLAPPED` handles are not supported). Note that reproc does not take ownership of the handle. The user is responsible for closing the handle after passing it to `reproc_start`. Since the operating system will copy the handle to the child process, the handle can be closed immediately after calling `reproc_start` if the handle is not needed in the parent process anymore. If `handle` is set, `type` must be unset or set to `REPROC_REDIRECT_HANDLE` and `file`, `path` must be unset. */ reproc_handle handle; /*! Redirect a stream to a file stream. Note that reproc does not take ownership of the file. The user is responsible for closing the file after passing it to `reproc_start`. Just like with `handles`, the operating system will copy the file handle to the child process so the file can be closed immediately after calling `reproc_start` if it isn't needed anymore by the parent process. Any file passed to `file.in` must have been opened in read mode. Likewise, any files passed to `file.out` or `file.err` must have been opened in write mode. If `file` is set, `type` must be unset or set to `REPROC_REDIRECT_FILE` and `handle`, `path` must be unset. */ FILE *file; /*! Redirect a stream to a given path. reproc will create or open the file at the given path. Depending on the stream, the file is opened in read or write mode. If `path` is set, `type` must be unset or set to `REPROC_REDIRECT_PATH` and `handle`, `file` must be unset. */ const char *path; } reproc_redirect; typedef enum { REPROC_ENV_EXTEND, REPROC_ENV_EMPTY, } REPROC_ENV; typedef struct reproc_options { /*! `working_directory` specifies the working directory for the child process. If `working_directory` is `NULL`, the child process runs in the working directory of the parent process. */ const char *working_directory; struct { /*! `behavior` specifies whether the child process should start with a copy of the parent process environment variables or an empty environment. By default, the child process starts with a copy of the parent's environment variables (`REPROC_ENV_EXTEND`). If `behavior` is set to `REPROC_ENV_EMPTY`, the child process starts with an empty environment. */ REPROC_ENV behavior; /*! `extra` is an array of UTF-8 encoded, NUL-terminated strings that specifies extra environment variables for the child process. It has the following layout: - All elements except the final element must be of the format `NAME=VALUE`. - The final element must be `NULL`. Example: ["IP=127.0.0.1", "PORT=8080", `NULL`] If `env` is `NULL`, no extra environment variables are added to the environment of the child process. */ const char *const *extra; } env; /*! `redirect` specifies where to redirect the streams from the child process. By default each stream is redirected to a pipe which can be written to (stdin) or read from (stdout/stderr) using `reproc_write` and `reproc_read` respectively. */ struct { /*! `in`, `out` and `err` specify where to redirect the standard I/O streams of the child process. When not set, `in` and `out` default to `REPROC_REDIRECT_PIPE` while `err` defaults to `REPROC_REDIRECT_PARENT`. */ reproc_redirect in; reproc_redirect out; reproc_redirect err; /*! Use `REPROC_REDIRECT_PARENT` instead of `REPROC_REDIRECT_PIPE` when `type` is unset. When this option is set, `discard`, `file` and `path` must be unset. */ bool parent; /*! Use `REPROC_REDIRECT_DISCARD` instead of `REPROC_REDIRECT_PIPE` when `type` is unset. When this option is set, `parent`, `file` and `path` must be unset. */ bool discard; /*! Shorthand for redirecting stdout and stderr to the same file. If this option is set, `out`, `err`, `parent`, `discard` and `path` must be unset. */ FILE *file; /*! Shorthand for redirecting stdout and stderr to the same path. If this option is set, `out`, `err`, `parent`, `discard` and `file` must be unset. */ const char *path; } redirect; /*! Stop actions that are passed to `reproc_stop` in `reproc_destroy` to stop the child process. See `reproc_stop` for more information on how `stop` is interpreted. */ reproc_stop_actions stop; /*! Maximum allowed duration in milliseconds the process is allowed to run in milliseconds. If the deadline is exceeded, Any ongoing and future calls to `reproc_poll` return `REPROC_ETIMEDOUT`. Note that only `reproc_poll` takes the deadline into account. More specifically, if the `nonblocking` option is not enabled, `reproc_read` and `reproc_write` can deadlock waiting on the child process to perform I/O. If this is a problem, enable the `nonblocking` option and use `reproc_poll` together with a deadline/timeout to avoid any deadlocks. If `REPROC_DEADLINE` is passed as the timeout to `reproc_wait`, it waits until the deadline expires. When `deadline` is zero, no deadline is set for the process. */ int deadline; /*! `input` is written to the stdin pipe before the child process is started. Because `input` is written to the stdin pipe before the process starts, `input.size` must be smaller than the system's default pipe size (64KB). If `input` is set, the stdin pipe is closed after `input` is written to it. If `redirect.in` is set, this option may not be set. */ struct { const uint8_t *data; size_t size; } input; /*! This option can only be used on POSIX systems. If enabled on Windows, an error will be returned. If `fork` is enabled, `reproc_start` forks a child process and returns 0 in the child process and > 0 in the parent process. In the child process, only `reproc_destroy` may be called on the `reproc_t` instance to free its associated memory. When `fork` is enabled. `argv` must be `NULL` when calling `reproc_start`. */ bool fork; /*! Put pipes created by reproc in nonblocking mode. This makes `reproc_read` and `reproc_write` nonblocking operations. If needed, use `reproc_poll` to wait until streams becomes readable/writable. */ bool nonblocking; } reproc_options; enum { /*! Data can be written to stdin. */ REPROC_EVENT_IN = 1 << 0, /*! Data can be read from stdout. */ REPROC_EVENT_OUT = 1 << 1, /*! Data can be read from stderr. */ REPROC_EVENT_ERR = 1 << 2, /*! The process finished running. */ REPROC_EVENT_EXIT = 1 << 3, /*! The deadline of the process expired. This event is added by default to the list of interested events. */ REPROC_EVENT_DEADLINE = 1 << 4, }; typedef struct reproc_event_source { /*! Process to poll for events. */ reproc_t *process; /*! Events of the process that we're interested in. Takes a combo of `REPROC_EVENT` flags. */ int interests; /*! Combo of `REPROC_EVENT` flags that indicate the events that occurred. This field is filled in by `reproc_poll`. */ int events; } reproc_event_source; /*! Allocate a new `reproc_t` instance on the heap. */ REPROC_EXPORT reproc_t *reproc_new(void); /*! Starts the process specified by `argv` in the given working directory and redirects its input, output and error streams. If this function does not return an error the child process will have started running and can be inspected using the operating system's tools for process inspection (e.g. ps on Linux). Every successful call to this function should be followed by a successful call to `reproc_wait` or `reproc_stop` and a call to `reproc_destroy`. If an error occurs during `reproc_start` all allocated resources are cleaned up before `reproc_start` returns and no further action is required. `argv` is an array of UTF-8 encoded, NUL-terminated strings that specifies the program to execute along with its arguments. It has the following layout: - The first element indicates the executable to run as a child process. This can be an absolute path, a path relative to the working directory of the parent process or the name of an executable located in the PATH. It cannot be `NULL`. - The following elements indicate the whitespace delimited arguments passed to the executable. None of these elements can be `NULL`. - The final element must be `NULL`. Example: ["cmake", "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", `NULL`] */ REPROC_EXPORT int reproc_start(reproc_t *process, const char *const *argv, reproc_options options); /*! Returns the process ID of the child or `REPROC_EINVAL` on error. Note that if `reproc_wait` has been called successfully on this process already, the returned pid will be that of the just ended child process. The operating system will have cleaned up the resources allocated to the process and the operating system is free to reuse the same pid for another process. Generally, only pass the result of this function to system calls that need a valid pid if `reproc_wait` hasn't been called successfully on the process yet. */ REPROC_EXPORT int reproc_pid(reproc_t *process); /*! Polls each process in `sources` for its corresponding events in `interests` and stores events that occurred for each process in `events`. If an event source process member is `NULL`, the event source is ignored. Pass `REPROC_INFINITE` to `timeout` to have `reproc_poll` wait forever for an event to occur. If one or more events occur, returns the number of processes with events. If the timeout expires, returns zero. Returns `REPROC_EPIPE` if none of the sources have valid pipes remaining that can be polled. Actionable errors: - `REPROC_EPIPE` */ REPROC_EXPORT int reproc_poll(reproc_event_source *sources, size_t num_sources, int timeout); /*! Reads up to `size` bytes into `buffer` from the child process output stream indicated by `stream`. Actionable errors: - `REPROC_EPIPE` - `REPROC_EWOULDBLOCK` */ REPROC_EXPORT int reproc_read(reproc_t *process, REPROC_STREAM stream, uint8_t *buffer, size_t size); /*! Writes up to `size` bytes from `buffer` to the standard input (stdin) of the child process. (POSIX) By default, writing to a closed stdin pipe terminates the parent process with the `SIGPIPE` signal. `reproc_write` will only return `REPROC_EPIPE` if this signal is ignored by the parent process. Returns the amount of bytes written. If `buffer` is `NULL` and `size` is zero, this function returns 0. If the standard input of the child process wasn't opened with `REPROC_REDIRECT_PIPE`, this function returns `REPROC_EPIPE` unless `buffer` is `NULL` and `size` is zero. Actionable errors: - `REPROC_EPIPE` - `REPROC_EWOULDBLOCK` */ REPROC_EXPORT int reproc_write(reproc_t *process, const uint8_t *buffer, size_t size); /*! Closes the child process standard stream indicated by `stream`. This function is necessary when a child process reads from stdin until it is closed. After writing all the input to the child process using `reproc_write`, the standard input stream can be closed using this function. */ REPROC_EXPORT int reproc_close(reproc_t *process, REPROC_STREAM stream); /*! Waits `timeout` milliseconds for the child process to exit. If the child process has already exited or exits within the given timeout, its exit status is returned. If `timeout` is 0, the function will only check if the child process is still running without waiting. If `timeout` is `REPROC_INFINITE`, this function will wait indefinitely for the child process to exit. If `timeout` is `REPROC_DEADLINE`, this function waits until the deadline passed to `reproc_start` expires. Actionable errors: - `REPROC_ETIMEDOUT` */ REPROC_EXPORT int reproc_wait(reproc_t *process, int timeout); /*! Sends the `SIGTERM` signal (POSIX) or the `CTRL-BREAK` signal (Windows) to the child process. Remember that successful calls to `reproc_wait` and `reproc_destroy` are required to make sure the child process is completely cleaned up. */ REPROC_EXPORT int reproc_terminate(reproc_t *process); /*! Sends the `SIGKILL` signal to the child process (POSIX) or calls `TerminateProcess` (Windows) on the child process. Remember that successful calls to `reproc_wait` and `reproc_destroy` are required to make sure the child process is completely cleaned up. */ REPROC_EXPORT int reproc_kill(reproc_t *process); /*! Simplifies calling combinations of `reproc_wait`, `reproc_terminate` and `reproc_kill`. The function executes each specified step and waits (using `reproc_wait`) until the corresponding timeout expires before continuing with the next step. Example: Wait 10 seconds for the child process to exit on its own before sending `SIGTERM` (POSIX) or `CTRL-BREAK` (Windows) and waiting five more seconds for the child process to exit. ```c REPROC_ERROR error = reproc_stop(process, REPROC_STOP_WAIT, 10000, REPROC_STOP_TERMINATE, 5000, REPROC_STOP_NOOP, 0); ``` Call `reproc_wait`, `reproc_terminate` and `reproc_kill` directly if you need extra logic such as logging between calls. `stop` can contain up to three stop actions that instruct this function how the child process should be stopped. The first element of each stop action specifies which action should be called on the child process. The second element of each stop actions specifies how long to wait after executing the operation indicated by the first element. When `stop` is 3x `REPROC_STOP_NOOP`, `reproc_destroy` will wait until the deadline expires (or forever if there is no deadline). If the process is still running after the deadline expires, `reproc_stop` then calls `reproc_terminate` and waits forever for the process to exit. Note that when a stop action specifies `REPROC_STOP_WAIT`, the function will just wait for the specified timeout instead of performing an action to stop the child process. If the child process has already exited or exits during the execution of this function, its exit status is returned. Actionable errors: - `REPROC_ETIMEDOUT` */ REPROC_EXPORT int reproc_stop(reproc_t *process, reproc_stop_actions stop); /*! Release all resources associated with `process` including the memory allocated by `reproc_new`. Calling this function before a succesfull call to `reproc_wait` can result in resource leaks. Does nothing if `process` is an invalid `reproc_t` instance and always returns an invalid `reproc_t` instance (`NULL`). By assigning the result of `reproc_destroy` to the instance being destroyed, it can be safely called multiple times on the same instance. Example: `process = reproc_destroy(process)`. */ REPROC_EXPORT reproc_t *reproc_destroy(reproc_t *process); /*! Returns a string describing `error`. This string must not be modified by the caller. */ REPROC_EXPORT const char *reproc_strerror(int error); #ifdef __cplusplus } #endif reproc-14.2.5/reproc/include/reproc/run.h000066400000000000000000000015621460055552200202710ustar00rootroot00000000000000#pragma once #include #include #ifdef __cplusplus extern "C" { #endif /*! Sets `options.redirect.parent = true` unless `discard` is set and calls `reproc_run_ex` with `REPROC_SINK_NULL` for the `out` and `err` sinks. */ REPROC_EXPORT int reproc_run(const char *const *argv, reproc_options options); /*! Wrapper function that starts a process with the given arguments, drain its output and waits until it exits. Have a look at its (trivial) implementation and the documentation of the functions it calls to see exactly what it does: https://github.com/DaanDeMeyer/reproc/blob/master/reproc/src/run.c */ REPROC_EXPORT int reproc_run_ex(const char *const *argv, reproc_options options, reproc_sink out, reproc_sink err); #ifdef __cplusplus } #endif reproc-14.2.5/reproc/reproc-config.cmake.in000066400000000000000000000004021460055552200205330ustar00rootroot00000000000000@PACKAGE_INIT@ set(REPROC_MULTITHREADED @REPROC_MULTITHREADED@) include(CMakeFindDependencyMacro) if(REPROC_MULTITHREADED) set(THREADS_PREFER_PTHREAD_FLAG ON) find_dependency(Threads) endif() include(${CMAKE_CURRENT_LIST_DIR}/@TARGET@-targets.cmake) reproc-14.2.5/reproc/reproc.pc.in000066400000000000000000000006031460055552200166150ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ Name: @TARGET@ Description: @PROJECT_DESCRIPTION@ URL: @PROJECT_HOMEPAGE_URL@ Version: @PROJECT_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -l@TARGET@ Libs.private: @REPROC_THREAD_LIBRARY@ @REPROC_WINSOCK_LIBRARY@ @REPROC_RT_LIBRARY@ reproc-14.2.5/reproc/resources/000077500000000000000000000000001460055552200164055ustar00rootroot00000000000000reproc-14.2.5/reproc/resources/argv.c000066400000000000000000000002171460055552200175100ustar00rootroot00000000000000#include int main(int argc, const char **argv) { for (int i = 0; i < argc; i++) { printf("%s\n", argv[i]); } return 0; } reproc-14.2.5/reproc/resources/deadline.c000066400000000000000000000001101460055552200203060ustar00rootroot00000000000000#include "sleep.h" int main(void) { millisleep(25000); return 0; } reproc-14.2.5/reproc/resources/env.c000066400000000000000000000003131460055552200173360ustar00rootroot00000000000000#include int main(int argc, const char **argv, const char **envp) { (void) argc; (void) argv; for (size_t i = 0; envp[i] != NULL; i++) { printf("%s\n", envp[i]); } return 0; } reproc-14.2.5/reproc/resources/io.c000066400000000000000000000003201460055552200171530ustar00rootroot00000000000000#include int main(void) { char input[8096]; if (fgets(input, sizeof(input), stdin) == NULL) { return 1; } fprintf(stdout, "%s", input); fprintf(stderr, "%s", input); return 0; } reproc-14.2.5/reproc/resources/overflow.c000066400000000000000000000003361460055552200204160ustar00rootroot00000000000000#include #include int main() { char buffer[8192]; for (int i = 0; i < 200; i++) { FILE *stream = rand() % 2 ? stdout : stderr; // NOLINT fprintf(stream, "%s", buffer); } return 0; } reproc-14.2.5/reproc/resources/path.c000066400000000000000000000001641460055552200175060ustar00rootroot00000000000000#include int main(int argc, const char **argv) { (void) argc; printf("%s", argv[0]); return 0; } reproc-14.2.5/reproc/resources/pid.c000066400000000000000000000003021460055552200173200ustar00rootroot00000000000000#include #ifdef _WIN32 #include #define getpid (int) GetCurrentProcessId #else #include #endif int main(void) { printf("%d", getpid()); return 0; } reproc-14.2.5/reproc/resources/sleep.h000066400000000000000000000005621460055552200176710ustar00rootroot00000000000000#pragma once #ifdef _WIN32 #include static inline void millisleep(long ms) { Sleep((DWORD) ms); } #else #define _POSIX_C_SOURCE 200809L #include static inline void millisleep(long ms) { nanosleep(&(struct timespec){ .tv_sec = (ms) / 1000, .tv_nsec = ((ms) % 1000L) * 1000000 }, NULL); } #endif reproc-14.2.5/reproc/resources/stop.c000066400000000000000000000001101460055552200175260ustar00rootroot00000000000000#include "sleep.h" int main(void) { millisleep(25000); return 0; } reproc-14.2.5/reproc/resources/working-directory.c000066400000000000000000000004641460055552200222370ustar00rootroot00000000000000#include #if defined(_WIN32) #include #define getcwd _getcwd #else #include #endif int main() { char working_directory[8096]; if (getcwd(working_directory, sizeof(working_directory)) == NULL) { return 1; } printf("%s", working_directory); return 0; } reproc-14.2.5/reproc/src/000077500000000000000000000000001460055552200151625ustar00rootroot00000000000000reproc-14.2.5/reproc/src/clock.h000066400000000000000000000000661460055552200164300ustar00rootroot00000000000000#pragma once #include int64_t now(void); reproc-14.2.5/reproc/src/clock.posix.c000066400000000000000000000004431460055552200175630ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "clock.h" #include #include "error.h" int64_t now(void) { struct timespec timespec = { 0 }; int r = clock_gettime(CLOCK_REALTIME, ×pec); ASSERT_UNUSED(r == 0); return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000; } reproc-14.2.5/reproc/src/clock.windows.c000066400000000000000000000004331460055552200201120ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "clock.h" #include int64_t now(void) { return (int64_t) GetTickCount64(); } reproc-14.2.5/reproc/src/drain.c000066400000000000000000000053041460055552200164250ustar00rootroot00000000000000#include #include #include #include "error.h" #include "macro.h" int reproc_drain(reproc_t *process, reproc_sink out, reproc_sink err) { ASSERT_EINVAL(process); ASSERT_EINVAL(out.function); ASSERT_EINVAL(err.function); const uint8_t initial = 0; int r = -1; // A single call to `read` might contain multiple messages. By always calling // both sinks once with no data before reading, we give them the chance to // process all previous output one by one before reading from the child // process again. r = out.function(REPROC_STREAM_IN, &initial, 0, out.context); if (r != 0) { return r; } r = err.function(REPROC_STREAM_IN, &initial, 0, err.context); if (r != 0) { return r; } uint8_t buffer[4096]; for (;;) { reproc_event_source source = { process, REPROC_EVENT_OUT | REPROC_EVENT_ERR, 0 }; r = reproc_poll(&source, 1, REPROC_INFINITE); if (r < 0) { r = r == REPROC_EPIPE ? 0 : r; break; } if (source.events & REPROC_EVENT_DEADLINE) { r = REPROC_ETIMEDOUT; break; } REPROC_STREAM stream = source.events & REPROC_EVENT_OUT ? REPROC_STREAM_OUT : REPROC_STREAM_ERR; r = reproc_read(process, stream, buffer, ARRAY_SIZE(buffer)); if (r < 0 && r != REPROC_EPIPE) { break; } size_t bytes_read = r == REPROC_EPIPE ? 0 : (size_t) r; reproc_sink sink = stream == REPROC_STREAM_OUT ? out : err; r = sink.function(stream, buffer, bytes_read, sink.context); if (r != 0) { break; } } return r; } static int sink_string(REPROC_STREAM stream, const uint8_t *buffer, size_t size, void *context) { (void) stream; char **string = (char **) context; size_t string_size = *string == NULL ? 0 : strlen(*string); char *r = (char *) realloc(*string, string_size + size + 1); if (r == NULL) { return REPROC_ENOMEM; } *string = r; memcpy(*string + string_size, buffer, size); (*string)[string_size + size] = '\0'; return 0; } reproc_sink reproc_sink_string(char **output) { return (reproc_sink){ sink_string, output }; } static int sink_discard(REPROC_STREAM stream, const uint8_t *buffer, size_t size, void *context) { (void) stream; (void) buffer; (void) size; (void) context; return 0; } reproc_sink reproc_sink_discard(void) { return (reproc_sink){ sink_discard, NULL }; } const reproc_sink REPROC_SINK_NULL = { sink_discard, NULL }; void *reproc_free(void *ptr) { free(ptr); return NULL; } reproc-14.2.5/reproc/src/error.h000066400000000000000000000021401460055552200164610ustar00rootroot00000000000000#pragma once #include #define ASSERT(expression) assert(expression) // Avoid unused assignment warnings in release mode when the result of an // assignment is only used in an assert statement. #define ASSERT_UNUSED(expression) \ do { \ (void) !(expression); \ ASSERT((expression)); \ } while (0) // Returns `r` if `expression` is false. #define ASSERT_RETURN(expression, r) \ do { \ if (!(expression)) { \ return (r); \ } \ } while (0) #define ASSERT_EINVAL(expression) ASSERT_RETURN(expression, REPROC_EINVAL) const char *error_string(int error); reproc-14.2.5/reproc/src/error.posix.c000066400000000000000000000013251460055552200176210ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "error.h" #include #include #include #include #include #include "macro.h" const int REPROC_EINVAL = -EINVAL; const int REPROC_EPIPE = -EPIPE; const int REPROC_ETIMEDOUT = -ETIMEDOUT; const int REPROC_ENOMEM = -ENOMEM; const int REPROC_EWOULDBLOCK = -EWOULDBLOCK; enum { ERROR_STRING_MAX_SIZE = 512 }; const char *error_string(int error) { static THREAD_LOCAL char string[ERROR_STRING_MAX_SIZE]; if(error == INT_MIN) return "Failed to retrieve error string"; int r = strerror_r(abs(error), string, ARRAY_SIZE(string)); if (r != 0) { return "Failed to retrieve error string"; } return string; } reproc-14.2.5/reproc/src/error.windows.c000066400000000000000000000033361460055552200201550ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "error.h" #include #include #include #include #include #include "macro.h" const int REPROC_EINVAL = -ERROR_INVALID_PARAMETER; const int REPROC_EPIPE = -ERROR_BROKEN_PIPE; const int REPROC_ETIMEDOUT = -WAIT_TIMEOUT; const int REPROC_ENOMEM = -ERROR_NOT_ENOUGH_MEMORY; const int REPROC_EWOULDBLOCK = -WSAEWOULDBLOCK; enum { ERROR_STRING_MAX_SIZE = 512 }; const char *error_string(int error) { wchar_t *wstring = NULL; int r = -1; if(error == INT_MIN) return "Failed to retrieve error string"; wstring = malloc(sizeof(wchar_t) * ERROR_STRING_MAX_SIZE); if (wstring == NULL) { return "Failed to allocate memory for error string"; } // We don't expect message sizes larger than the maximum possible int. r = (int) FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) abs(error), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wstring, ERROR_STRING_MAX_SIZE, NULL); if (r == 0) { free(wstring); return "Failed to retrieve error string"; } static THREAD_LOCAL char string[ERROR_STRING_MAX_SIZE]; r = WideCharToMultiByte(CP_UTF8, 0, wstring, -1, string, ARRAY_SIZE(string), NULL, NULL); free(wstring); if (r == 0) { return "Failed to convert error string to UTF-8"; } // Remove trailing whitespace and period. if (r >= 4) { string[r - 4] = '\0'; } return string; } reproc-14.2.5/reproc/src/handle.h000066400000000000000000000010341460055552200165640ustar00rootroot00000000000000#pragma once #include #include #if defined(_WIN32) typedef void *handle_type; // `HANDLE` #else typedef int handle_type; // fd #endif extern const handle_type HANDLE_INVALID; // Sets the `FD_CLOEXEC` flag on the file descriptor. POSIX only. int handle_cloexec(handle_type handle, bool enable); // Closes `handle` if it is not an invalid handle and returns an invalid handle. // Does not overwrite the last system error if an error occurs while closing // `handle`. handle_type handle_destroy(handle_type handle); reproc-14.2.5/reproc/src/handle.posix.c000066400000000000000000000011441460055552200177220ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "handle.h" #include #include #include #include "error.h" const int HANDLE_INVALID = -1; int handle_cloexec(int handle, bool enable) { int r = -1; r = fcntl(handle, F_GETFD, 0); if (r < 0) { return -errno; } r = enable ? r | FD_CLOEXEC : r & ~FD_CLOEXEC; r = fcntl(handle, F_SETFD, r); if (r < 0) { return -errno; } return 0; } int handle_destroy(int handle) { if (handle == HANDLE_INVALID) { return HANDLE_INVALID; } int r = close(handle); ASSERT_UNUSED(r == 0); return HANDLE_INVALID; } reproc-14.2.5/reproc/src/handle.windows.c000066400000000000000000000010471460055552200202540ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "handle.h" #include #include "error.h" const HANDLE HANDLE_INVALID = INVALID_HANDLE_VALUE; // NOLINT // `handle_cloexec` is POSIX-only. HANDLE handle_destroy(HANDLE handle) { if (handle == NULL || handle == HANDLE_INVALID) { return HANDLE_INVALID; } int r = CloseHandle(handle); ASSERT_UNUSED(r != 0); return HANDLE_INVALID; } reproc-14.2.5/reproc/src/init.h000066400000000000000000000000621460055552200162740ustar00rootroot00000000000000#pragma once int init(void); void deinit(void); reproc-14.2.5/reproc/src/init.posix.c000066400000000000000000000001511460055552200174270ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "init.h" int init(void) { return 0; } void deinit(void) {} reproc-14.2.5/reproc/src/init.windows.c000066400000000000000000000007261460055552200177670ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "init.h" #include #include "error.h" int init(void) { WSADATA data; int r = WSAStartup(MAKEWORD(2, 2), &data); return -r; } void deinit(void) { int saved = WSAGetLastError(); int r = WSACleanup(); ASSERT_UNUSED(r == 0); WSASetLastError(saved); } reproc-14.2.5/reproc/src/macro.h000066400000000000000000000003571460055552200164410ustar00rootroot00000000000000#pragma once #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define MIN(a, b) (a) < (b) ? (a) : (b) #if defined(_WIN32) && !defined(__MINGW32__) #define THREAD_LOCAL __declspec(thread) #else #define THREAD_LOCAL __thread #endif reproc-14.2.5/reproc/src/options.c000066400000000000000000000076211460055552200170270ustar00rootroot00000000000000#include "options.h" #include "error.h" static bool redirect_is_set(reproc_redirect redirect) { return redirect.type || redirect.handle || redirect.file || redirect.path; } static int parse_redirect(reproc_redirect *redirect, REPROC_STREAM stream, bool parent, bool discard, FILE *file, const char *path) { ASSERT(redirect); if (file) { ASSERT_EINVAL(!redirect_is_set(*redirect)); ASSERT_EINVAL(!parent && !discard && !path); redirect->type = REPROC_REDIRECT_FILE; redirect->file = file; } if (path) { ASSERT_EINVAL(!redirect_is_set(*redirect)); ASSERT_EINVAL(!parent && !discard && !file); redirect->type = REPROC_REDIRECT_PATH; redirect->path = path; } if (redirect->type == REPROC_REDIRECT_HANDLE || redirect->handle) { ASSERT_EINVAL(redirect->type == REPROC_REDIRECT_DEFAULT || redirect->type == REPROC_REDIRECT_HANDLE); ASSERT_EINVAL(redirect->handle); ASSERT_EINVAL(!redirect->file && !redirect->path); redirect->type = REPROC_REDIRECT_HANDLE; } if (redirect->type == REPROC_REDIRECT_FILE || redirect->file) { ASSERT_EINVAL(redirect->type == REPROC_REDIRECT_DEFAULT || redirect->type == REPROC_REDIRECT_FILE); ASSERT_EINVAL(redirect->file); ASSERT_EINVAL(!redirect->handle && !redirect->path); redirect->type = REPROC_REDIRECT_FILE; } if (redirect->type == REPROC_REDIRECT_PATH || redirect->path) { ASSERT_EINVAL(redirect->type == REPROC_REDIRECT_DEFAULT || redirect->type == REPROC_REDIRECT_PATH); ASSERT_EINVAL(redirect->path); ASSERT_EINVAL(!redirect->handle && !redirect->file); redirect->type = REPROC_REDIRECT_PATH; } if (redirect->type == REPROC_REDIRECT_DEFAULT) { if (parent) { ASSERT_EINVAL(!discard); redirect->type = REPROC_REDIRECT_PARENT; } else if (discard) { ASSERT_EINVAL(!parent); redirect->type = REPROC_REDIRECT_DISCARD; } else { redirect->type = stream == REPROC_STREAM_ERR ? REPROC_REDIRECT_PARENT : REPROC_REDIRECT_PIPE; } } return 0; } reproc_stop_actions parse_stop_actions(reproc_stop_actions stop) { bool is_noop = stop.first.action == REPROC_STOP_NOOP && stop.second.action == REPROC_STOP_NOOP && stop.third.action == REPROC_STOP_NOOP; if (is_noop) { stop.first.action = REPROC_STOP_WAIT; stop.first.timeout = REPROC_DEADLINE; stop.second.action = REPROC_STOP_TERMINATE; stop.second.timeout = REPROC_INFINITE; } return stop; } int parse_options(reproc_options *options, const char *const *argv) { ASSERT(options); int r = -1; r = parse_redirect(&options->redirect.in, REPROC_STREAM_IN, options->redirect.parent, options->redirect.discard, NULL, NULL); if (r < 0) { return r; } r = parse_redirect(&options->redirect.out, REPROC_STREAM_OUT, options->redirect.parent, options->redirect.discard, options->redirect.file, options->redirect.path); if (r < 0) { return r; } r = parse_redirect(&options->redirect.err, REPROC_STREAM_ERR, options->redirect.parent, options->redirect.discard, options->redirect.file, options->redirect.path); if (r < 0) { return r; } if (options->input.data != NULL) { ASSERT_EINVAL(options->redirect.in.type == REPROC_REDIRECT_PIPE); } if (options->input.size > 0) { ASSERT_EINVAL(options->input.data != NULL); } if (options->fork) { ASSERT_EINVAL(argv == NULL); } else { ASSERT_EINVAL(argv != NULL && argv[0] != NULL); } if (options->deadline == 0) { options->deadline = REPROC_INFINITE; } options->stop = parse_stop_actions(options->stop); return 0; } reproc-14.2.5/reproc/src/options.h000066400000000000000000000002621460055552200170260ustar00rootroot00000000000000#pragma once #include reproc_stop_actions parse_stop_actions(reproc_stop_actions stop); int parse_options(reproc_options *options, const char *const *argv); reproc-14.2.5/reproc/src/pipe.h000066400000000000000000000023461460055552200162750ustar00rootroot00000000000000#pragma once #include #include #include #ifdef _WIN64 typedef uint64_t pipe_type; // `SOCKET` #elif _WIN32 typedef uint32_t pipe_type; // `SOCKET` #else typedef int pipe_type; // fd #endif extern const pipe_type PIPE_INVALID; extern const short PIPE_EVENT_IN; extern const short PIPE_EVENT_OUT; typedef struct { pipe_type pipe; short interests; short events; } pipe_event_source; // Creates a new anonymous pipe. `parent` and `child` are set to the parent and // child endpoint of the pipe respectively. int pipe_init(pipe_type *read, pipe_type *write); // Sets `pipe` to nonblocking mode. int pipe_nonblocking(pipe_type pipe, bool enable); // Reads up to `size` bytes into `buffer` from the pipe indicated by `pipe` and // returns the amount of bytes read. int pipe_read(pipe_type pipe, uint8_t *buffer, size_t size); // Writes up to `size` bytes from `buffer` to the pipe indicated by `pipe` and // returns the amount of bytes written. int pipe_write(pipe_type pipe, const uint8_t *buffer, size_t size); // Polls the given event sources for events. int pipe_poll(pipe_event_source *sources, size_t num_sources, int timeout); int pipe_shutdown(pipe_type pipe); pipe_type pipe_destroy(pipe_type pipe); reproc-14.2.5/reproc/src/pipe.posix.c000066400000000000000000000044441460055552200174320ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "pipe.h" #include #include #include #include #include #include #include "error.h" #include "handle.h" const int PIPE_INVALID = -1; const short PIPE_EVENT_IN = POLLIN; const short PIPE_EVENT_OUT = POLLOUT; int pipe_init(int *read, int *write) { ASSERT(read); ASSERT(write); int pair[] = { PIPE_INVALID, PIPE_INVALID }; int r = -1; r = pipe(pair); if (r < 0) { r = -errno; goto finish; } r = handle_cloexec(pair[0], true); if (r < 0) { goto finish; } r = handle_cloexec(pair[1], true); if (r < 0) { goto finish; } *read = pair[0]; *write = pair[1]; pair[0] = PIPE_INVALID; pair[1] = PIPE_INVALID; finish: pipe_destroy(pair[0]); pipe_destroy(pair[1]); return r; } int pipe_nonblocking(int pipe, bool enable) { int r = -1; r = fcntl(pipe, F_GETFL, 0); if (r < 0) { return -errno; } r = enable ? r | O_NONBLOCK : r & ~O_NONBLOCK; r = fcntl(pipe, F_SETFL, r); return r < 0 ? -errno : 0; } int pipe_read(int pipe, uint8_t *buffer, size_t size) { ASSERT(pipe != PIPE_INVALID); ASSERT(buffer); int r = (int) read(pipe, buffer, size); if (r == 0) { // `read` returns 0 to indicate the other end of the pipe was closed. return -EPIPE; } return r < 0 ? -errno : r; } int pipe_write(int pipe, const uint8_t *buffer, size_t size) { ASSERT(pipe != PIPE_INVALID); ASSERT(buffer); int r = (int) write(pipe, buffer, size); return r < 0 ? -errno : r; } int pipe_poll(pipe_event_source *sources, size_t num_sources, int timeout) { ASSERT(num_sources <= INT_MAX); struct pollfd *pollfds = NULL; int r = -1; pollfds = calloc(num_sources, sizeof(struct pollfd)); if (pollfds == NULL) { r = -errno; goto finish; } for (size_t i = 0; i < num_sources; i++) { pollfds[i].fd = sources[i].pipe; pollfds[i].events = sources[i].interests; } r = poll(pollfds, (nfds_t) num_sources, timeout); if (r < 0) { r = -errno; goto finish; } for (size_t i = 0; i < num_sources; i++) { sources[i].events = pollfds[i].revents; } finish: free(pollfds); return r; } int pipe_shutdown(int pipe) { (void) pipe; return 0; } int pipe_destroy(int pipe) { return handle_destroy(pipe); } reproc-14.2.5/reproc/src/pipe.windows.c000066400000000000000000000124621460055552200177610ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "pipe.h" #include #include #include #include #include "error.h" #include "handle.h" #include "macro.h" const SOCKET PIPE_INVALID = INVALID_SOCKET; const short PIPE_EVENT_IN = POLLIN; const short PIPE_EVENT_OUT = POLLOUT; // Inspired by https://gist.github.com/geertj/4325783. static int socketpair(int domain, int type, int protocol, SOCKET *out) { ASSERT(out); SOCKET server = PIPE_INVALID; SOCKET pair[] = { PIPE_INVALID, PIPE_INVALID }; int r = -1; server = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, 0); if (server == INVALID_SOCKET) { r = -WSAGetLastError(); goto finish; } SOCKADDR_IN localhost = { 0 }; localhost.sin_family = AF_INET; localhost.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK); localhost.sin_port = 0; r = bind(server, (SOCKADDR *) &localhost, sizeof(localhost)); if (r < 0) { r = -WSAGetLastError(); goto finish; } r = listen(server, 1); if (r < 0) { r = -WSAGetLastError(); goto finish; } SOCKADDR_STORAGE name = { 0 }; int size = sizeof(name); r = getsockname(server, (SOCKADDR *) &name, &size); if (r < 0) { r = -WSAGetLastError(); goto finish; } pair[0] = WSASocketW(domain, type, protocol, NULL, 0, 0); if (pair[0] == INVALID_SOCKET) { r = -WSAGetLastError(); goto finish; } struct { WSAPROTOCOL_INFOW data; int size; } info = { { 0 }, sizeof(WSAPROTOCOL_INFOW) }; r = getsockopt(pair[0], SOL_SOCKET, SO_PROTOCOL_INFOW, (char *) &info.data, &info.size); if (r < 0) { goto finish; } // We require the returned sockets to be usable as Windows file handles. This // might not be the case if extra LSP providers are installed. if (!(info.data.dwServiceFlags1 & XP1_IFS_HANDLES)) { r = -ERROR_NOT_SUPPORTED; goto finish; } r = pipe_nonblocking(pair[0], true); if (r < 0) { goto finish; } r = connect(pair[0], (SOCKADDR *) &name, size); if (r < 0 && WSAGetLastError() != WSAEWOULDBLOCK) { r = -WSAGetLastError(); goto finish; } r = pipe_nonblocking(pair[0], false); if (r < 0) { goto finish; } pair[1] = accept(server, NULL, NULL); if (pair[1] == INVALID_SOCKET) { r = -WSAGetLastError(); goto finish; } out[0] = pair[0]; out[1] = pair[1]; pair[0] = PIPE_INVALID; pair[1] = PIPE_INVALID; finish: pipe_destroy(server); pipe_destroy(pair[0]); pipe_destroy(pair[1]); return r; } int pipe_init(SOCKET *read, SOCKET *write) { ASSERT(read); ASSERT(write); SOCKET pair[] = { PIPE_INVALID, PIPE_INVALID }; int r = -1; // Use sockets instead of pipes so we can use `WSAPoll` which only works with // sockets. r = socketpair(AF_INET, SOCK_STREAM, 0, pair); if (r < 0) { goto finish; } r = SetHandleInformation((HANDLE) pair[0], HANDLE_FLAG_INHERIT, 0); if (r == 0) { r = -(int) GetLastError(); goto finish; } r = SetHandleInformation((HANDLE) pair[1], HANDLE_FLAG_INHERIT, 0); if (r == 0) { r = -(int) GetLastError(); goto finish; } // Make the connection unidirectional to better emulate a pipe. r = shutdown(pair[0], SD_SEND); if (r < 0) { r = -WSAGetLastError(); goto finish; } r = shutdown(pair[1], SD_RECEIVE); if (r < 0) { r = -WSAGetLastError(); goto finish; } *read = pair[0]; *write = pair[1]; pair[0] = PIPE_INVALID; pair[1] = PIPE_INVALID; finish: pipe_destroy(pair[0]); pipe_destroy(pair[1]); return r; } int pipe_nonblocking(SOCKET pipe, bool enable) { u_long mode = enable; int r = ioctlsocket(pipe, (long) FIONBIO, &mode); return r < 0 ? -WSAGetLastError() : 0; } int pipe_read(SOCKET pipe, uint8_t *buffer, size_t size) { ASSERT(pipe != PIPE_INVALID); ASSERT(buffer); ASSERT(size <= INT_MAX); int r = recv(pipe, (char *) buffer, (int) size, 0); if (r == 0) { return -ERROR_BROKEN_PIPE; } return r < 0 ? -WSAGetLastError() : r; } int pipe_write(SOCKET pipe, const uint8_t *buffer, size_t size) { ASSERT(pipe != PIPE_INVALID); ASSERT(buffer); ASSERT(size <= INT_MAX); int r = send(pipe, (const char *) buffer, (int) size, 0); return r < 0 ? -WSAGetLastError() : r; } int pipe_poll(pipe_event_source *sources, size_t num_sources, int timeout) { ASSERT(num_sources <= INT_MAX); WSAPOLLFD *pollfds = NULL; int r = -1; pollfds = calloc(num_sources, sizeof(WSAPOLLFD)); if (pollfds == NULL) { r = -ERROR_NOT_ENOUGH_MEMORY; goto finish; } for (size_t i = 0; i < num_sources; i++) { pollfds[i].fd = sources[i].pipe; pollfds[i].events = sources[i].interests; } r = WSAPoll(pollfds, (ULONG) num_sources, timeout); if (r < 0) { r = -WSAGetLastError(); goto finish; } for (size_t i = 0; i < num_sources; i++) { sources[i].events = pollfds[i].revents; } finish: free(pollfds); return r; } int pipe_shutdown(SOCKET pipe) { if (pipe == PIPE_INVALID) { return 0; } int r = shutdown(pipe, SD_SEND); return r < 0 ? -WSAGetLastError() : 0; } SOCKET pipe_destroy(SOCKET pipe) { if (pipe == PIPE_INVALID) { return PIPE_INVALID; } int r = closesocket(pipe); ASSERT_UNUSED(r == 0); return PIPE_INVALID; } reproc-14.2.5/reproc/src/process.h000066400000000000000000000041321460055552200170110ustar00rootroot00000000000000#pragma once #include "handle.h" #include #include #if defined(_WIN32) typedef void *process_type; // `HANDLE` #else typedef int process_type; // `pid_t` #endif extern const process_type PROCESS_INVALID; struct process_options { // If `NULL`, the child process inherits the environment of the current // process. struct { REPROC_ENV behavior; const char *const *extra; } env; // If not `NULL`, the working directory of the child process is set to // `working_directory`. const char *working_directory; // The standard streams of the child process are redirected to the `in`, `out` // and `err` handles. If a handle is `HANDLE_INVALID`, the corresponding child // process standard stream is closed. The `exit` handle is simply inherited by // the child process. struct { handle_type in; handle_type out; handle_type err; handle_type exit; } handle; }; // Spawns a child process that executes the command stored in `argv`. // // If `argv` is `NULL` on POSIX, `exec` is not called after fork and this // function returns 0 in the child process and > 0 in the parent process. On // Windows, if `argv` is `NULL`, an error is returned. // // The process handle of the new child process is assigned to `process`. int process_start(process_type *process, const char *const *argv, struct process_options options); // Returns the process ID associated with the given handle. On posix systems the // handle is the process ID and so its returned directly. On WIN32 the process // ID is returned from GetProcessId on the pointer. int process_pid(process_type process); // Returns the process's exit status if it has finished running. int process_wait(process_type process); // Sends the `SIGTERM` (POSIX) or `CTRL-BREAK` (Windows) signal to the process // indicated by `process`. int process_terminate(process_type process); // Sends the `SIGKILL` signal to `process` (POSIX) or calls `TerminateProcess` // on `process` (Windows). int process_kill(process_type process); process_type process_destroy(process_type process); reproc-14.2.5/reproc/src/process.posix.c000066400000000000000000000264741460055552200201620ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "process.h" #include #include #include #include #include #include #include #include #include #include "error.h" #include "macro.h" #include "pipe.h" #include "strv.h" #define CWD_BUF_SIZE_INCREMENT 4096 const pid_t PROCESS_INVALID = -1; static int signal_mask(int how, const sigset_t *newmask, sigset_t *oldmask) { int r = -1; #if defined(REPROC_MULTITHREADED) // `pthread_sigmask` returns positive errno values so we negate them. r = -pthread_sigmask(how, newmask, oldmask); #else r = sigprocmask(how, newmask, oldmask); r = r < 0 ? -errno : 0; #endif return r; } // Returns true if the NUL-terminated string indicated by `path` is a relative // path. A path is relative if any character except the first is a forward slash // ('/'). static bool path_is_relative(const char *path) { return strlen(path) > 0 && path[0] != '/' && strchr(path + 1, '/') != NULL; } // Prepends the NUL-terminated string indicated by `path` with the current // working directory. The caller is responsible for freeing the result of this // function. If an error occurs, `NULL` is returned and `errno` is set to // indicate the error. static char *path_prepend_cwd(const char *path) { ASSERT(path); size_t path_size = strlen(path); size_t cwd_size = CWD_BUF_SIZE_INCREMENT; // We always allocate sufficient space for `path` but do not include this // space in `cwd_size` so we can be sure that when `getcwd` succeeds there is // sufficient space left in `cwd` to append `path`. // +2 reserves space to add a NUL terminator and potentially a missing '/' // after the current working directory. char *cwd = calloc(cwd_size + path_size + 2, sizeof(char)); if (cwd == NULL) { return cwd; } while (getcwd(cwd, cwd_size) == NULL) { if (errno != ERANGE) { free(cwd); return NULL; } cwd_size += CWD_BUF_SIZE_INCREMENT; char *result = realloc(cwd, cwd_size + path_size + 1); if (result == NULL) { free(cwd); return result; } cwd = result; } cwd_size = strlen(cwd); // Add a forward slash after `cwd` if there is none. if (cwd[cwd_size - 1] != '/') { cwd[cwd_size] = '/'; cwd[cwd_size + 1] = '\0'; cwd_size++; } // We've made sure there's sufficient space left in `cwd` to add `path` and a // NUL terminator. memcpy(cwd + cwd_size, path, path_size); cwd[cwd_size + path_size] = '\0'; return cwd; } static const int MAX_FD_LIMIT = 1024 * 1024; static int get_max_fd(void) { struct rlimit limit = { 0 }; int r = getrlimit(RLIMIT_NOFILE, &limit); if (r < 0) { return -errno; } rlim_t soft = limit.rlim_cur; if (soft == RLIM_INFINITY || soft > INT_MAX) { return INT_MAX; } return (int) (soft - 1); } static bool fd_in_set(int fd, const int *fd_set, size_t size) { for (size_t i = 0; i < size; i++) { if (fd == fd_set[i]) { return true; } } return false; } static pid_t process_fork(const int *except, size_t num_except) { struct { sigset_t old; sigset_t new; } mask; int r = -1; // We don't want signal handlers of the parent to run in the child process so // we block all signals before forking. r = sigfillset(&mask.new); if (r < 0) { return -errno; } r = signal_mask(SIG_SETMASK, &mask.new, &mask.old); if (r < 0) { return r; } struct { int read; int write; } pipe = { PIPE_INVALID, PIPE_INVALID }; r = pipe_init(&pipe.read, &pipe.write); if (r < 0) { return r; } r = fork(); if (r < 0) { // `fork` error. r = -errno; // Save `errno`. int q = signal_mask(SIG_SETMASK, &mask.new, &mask.old); ASSERT_UNUSED(q == 0); pipe_destroy(pipe.read); pipe_destroy(pipe.write); return r; } if (r > 0) { // Parent process pid_t child = r; // From now on, the child process might have started so we don't report // errors from `signal_mask` and `read`. This puts the responsibility // for cleaning up the process in the hands of the caller. int q = signal_mask(SIG_SETMASK, &mask.old, &mask.old); ASSERT_UNUSED(q == 0); // Close the error pipe write end on the parent's side so `read` will return // when it is closed on the child side as well. pipe_destroy(pipe.write); int child_errno = 0; q = (int) read(pipe.read, &child_errno, sizeof(child_errno)); ASSERT_UNUSED(q >= 0); if (child_errno > 0) { // If the child writes to the error pipe and exits, we're certain the // child process exited on its own and we can report errors as usual. r = waitpid(child, NULL, 0); ASSERT(r < 0 || r == child); r = r < 0 ? -errno : -child_errno; } pipe_destroy(pipe.read); return r < 0 ? r : child; } // Child process // Reset all signal handlers so they don't run in the child process. By // default, a child process inherits the parent's signal handlers but we // override this as most signal handlers won't be written in a way that they // can deal with being run in a child process. struct sigaction action = { .sa_handler = SIG_DFL }; r = sigemptyset(&action.sa_mask); if (r < 0) { r = -errno; goto finish; } // NSIG is not standardized so we use a fixed limit instead. for (int signal = 0; signal < 32; signal++) { r = sigaction(signal, &action, NULL); if (r < 0 && errno != EINVAL) { r = -errno; goto finish; } } // Reset the child's signal mask to the default signal mask. By default, a // child process inherits the parent's signal mask (even over an `exec` call) // but we override this as most processes won't be written in a way that they // can deal with starting with a custom signal mask. r = sigemptyset(&mask.new); if (r < 0) { r = -errno; goto finish; } r = signal_mask(SIG_SETMASK, &mask.new, NULL); if (r < 0) { goto finish; } // Not all file descriptors might have been created with the `FD_CLOEXEC` // flag so we manually close all file descriptors to prevent file descriptors // leaking into the child process. r = get_max_fd(); if (r < 0) { goto finish; } int max_fd = r; if (max_fd > MAX_FD_LIMIT) { // Refuse to try to close too many file descriptors. r = -EMFILE; goto finish; } for (int i = 0; i < max_fd; i++) { // Make sure we don't close the error pipe file descriptors twice. if (i == pipe.read || i == pipe.write) { continue; } if (fd_in_set(i, except, num_except)) { continue; } // Check if `i` is a valid file descriptor before trying to close it. r = fcntl(i, F_GETFD); if (r >= 0) { handle_destroy(i); } } r = 0; finish: if (r < 0) { (void) !write(pipe.write, &errno, sizeof(errno)); _exit(EXIT_FAILURE); } pipe_destroy(pipe.write); pipe_destroy(pipe.read); return 0; } int process_start(pid_t *process, const char *const *argv, struct process_options options) { ASSERT(process); if (argv != NULL) { ASSERT(argv[0] != NULL); } struct { int read; int write; } pipe = { PIPE_INVALID, PIPE_INVALID }; char *program = NULL; char **env = NULL; int r = -1; // We create an error pipe to receive errors from the child process. r = pipe_init(&pipe.read, &pipe.write); if (r < 0) { goto finish; } if (argv != NULL) { // We prepend the parent working directory to `program` if it is a // relative path so that it will always be searched for relative to the // parent working directory even after executing `chdir`. program = options.working_directory && path_is_relative(argv[0]) ? path_prepend_cwd(argv[0]) : strdup(argv[0]); if (program == NULL) { r = -errno; goto finish; } } extern char **environ; // NOLINT char *const *parent = options.env.behavior == REPROC_ENV_EMPTY ? NULL : environ; env = strv_concat(parent, options.env.extra); if (env == NULL) { goto finish; } int except[] = { options.handle.in, options.handle.out, options.handle.err, pipe.read, pipe.write, options.handle.exit }; r = process_fork(except, ARRAY_SIZE(except)); if (r < 0) { goto finish; } if (r == 0) { // Redirect stdin, stdout and stderr. int redirect[] = { options.handle.in, options.handle.out, options.handle.err }; for (int i = 0; i < (int) ARRAY_SIZE(redirect); i++) { // `i` corresponds to the standard stream we need to redirect. r = dup2(redirect[i], i); if (r < 0) { r = -errno; goto child; } // Make sure we don't accidentally cloexec the standard streams of the // child process when we're inheriting the parent standard streams. If we // don't call `exec`, the caller is responsible for closing the redirect // and exit handles. if (redirect[i] != i) { // Make sure the pipe is closed when we call exec. r = handle_cloexec(redirect[i], true); if (r < 0) { goto child; } } } // Make sure the `exit` file descriptor is inherited. r = handle_cloexec(options.handle.exit, false); if (r < 0) { goto child; } if (options.working_directory != NULL) { r = chdir(options.working_directory); if (r < 0) { r = -errno; goto child; } } // `environ` is carried over calls to `exec`. environ = env; if (argv != NULL) { ASSERT(program); r = execvp(program, (char *const *) argv); if (r < 0) { r = -errno; goto child; } } env = NULL; child: if (r < 0) { (void) !write(pipe.write, &errno, sizeof(errno)); _exit(EXIT_FAILURE); } pipe_destroy(pipe.read); pipe_destroy(pipe.write); free(program); strv_free(env); return 0; } pid_t child = r; // Close the error pipe write end on the parent's side so `read` will return // when it is closed on the child side as well. pipe.write = pipe_destroy(pipe.write); int child_errno = 0; r = (int) read(pipe.read, &child_errno, sizeof(child_errno)); ASSERT_UNUSED(r >= 0); if (child_errno > 0) { r = waitpid(child, NULL, 0); r = r < 0 ? -errno : -child_errno; goto finish; } *process = child; r = 0; finish: pipe_destroy(pipe.read); pipe_destroy(pipe.write); free(program); strv_free(env); return r < 0 ? r : 1; } static int parse_status(int status) { return WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status) + 128; } int process_pid(process_type process) { return process; } int process_wait(pid_t process) { ASSERT(process != PROCESS_INVALID); int status = 0; int r = waitpid(process, &status, 0); if (r < 0) { return -errno; } ASSERT(r == process); return parse_status(status); } int process_terminate(pid_t process) { ASSERT(process != PROCESS_INVALID); int r = kill(process, SIGTERM); return r < 0 ? -errno : 0; } int process_kill(pid_t process) { ASSERT(process != PROCESS_INVALID); int r = kill(process, SIGKILL); return r < 0 ? -errno : 0; } pid_t process_destroy(pid_t process) { // `waitpid` already cleans up the process for us. (void) process; return PROCESS_INVALID; } reproc-14.2.5/reproc/src/process.windows.c000066400000000000000000000327211460055552200205020ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "process.h" #include #include #include #include "error.h" #include "macro.h" #include "utf.h" const HANDLE PROCESS_INVALID = INVALID_HANDLE_VALUE; // NOLINT static const DWORD CREATION_FLAGS = // Create each child process in a new process group so we don't send // `CTRL-BREAK` signals to more than one child process in // `process_terminate`. CREATE_NEW_PROCESS_GROUP | // Create each child process with a Unicode environment as we accept any // UTF-16 encoded environment (including Unicode characters). Create each CREATE_UNICODE_ENVIRONMENT | // Create each child with an extended STARTUPINFOEXW structure so we can // specify which handles should be inherited. EXTENDED_STARTUPINFO_PRESENT; // Argument escaping implementation is based on the following blog post: // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ static bool argument_should_escape(const char *argument) { ASSERT(argument); bool should_escape = false; for (size_t i = 0; i < strlen(argument); i++) { should_escape = should_escape || argument[i] == ' ' || argument[i] == '\t' || argument[i] == '\n' || argument[i] == '\v' || argument[i] == '\"'; } return should_escape; } static size_t argument_escaped_size(const char *argument) { ASSERT(argument); size_t argument_size = strlen(argument); if (!argument_should_escape(argument)) { return argument_size; } size_t size = 2; // double quotes for (size_t i = 0; i < argument_size; i++) { size_t num_backslashes = 0; while (i < argument_size && argument[i] == '\\') { i++; num_backslashes++; } if (i == argument_size) { size += num_backslashes * 2; } else if (argument[i] == '"') { size += num_backslashes * 2 + 2; } else { size += num_backslashes + 1; } } return size; } static size_t argument_escape(char *dest, const char *argument) { ASSERT(dest); ASSERT(argument); size_t argument_size = strlen(argument); if (!argument_should_escape(argument)) { strcpy(dest, argument); // NOLINT return argument_size; } const char *begin = dest; *dest++ = '"'; for (size_t i = 0; i < argument_size; i++) { size_t num_backslashes = 0; while (i < argument_size && argument[i] == '\\') { i++; num_backslashes++; } if (i == argument_size) { memset(dest, '\\', num_backslashes * 2); dest += num_backslashes * 2; } else if (argument[i] == '"') { memset(dest, '\\', num_backslashes * 2 + 1); dest += num_backslashes * 2 + 1; *dest++ = '"'; } else { memset(dest, '\\', num_backslashes); dest += num_backslashes; *dest++ = argument[i]; } } *dest++ = '"'; return (size_t)(dest - begin); } static char *argv_join(const char *const *argv) { ASSERT(argv); // Determine the size of the concatenated string first. size_t joined_size = 1; // Count the NUL terminator. for (int i = 0; argv[i] != NULL; i++) { joined_size += argument_escaped_size(argv[i]); if (argv[i + 1] != NULL) { joined_size++; // Count whitespace. } } char *joined = calloc(joined_size, sizeof(char)); if (joined == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } char *current = joined; for (int i = 0; argv[i] != NULL; i++) { current += argument_escape(current, argv[i]); // We add a space after each argument in the joined arguments string except // for the final argument. if (argv[i + 1] != NULL) { *current++ = ' '; } } *current = '\0'; return joined; } static size_t env_join_size(const char *const *env) { ASSERT(env); size_t joined_size = 1; // Count the NUL terminator. for (int i = 0; env[i] != NULL; i++) { joined_size += strlen(env[i]) + 1; // Count the NUL terminator. } return joined_size; } static char *env_join(const char *const *env) { ASSERT(env); char *joined = calloc(env_join_size(env), sizeof(char)); if (joined == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } char *current = joined; for (int i = 0; env[i] != NULL; i++) { size_t to_copy = strlen(env[i]) + 1; // Include NUL terminator. memcpy(current, env[i], to_copy); current += to_copy; } *current = '\0'; return joined; } static const DWORD NUM_ATTRIBUTES = 1; static LPPROC_THREAD_ATTRIBUTE_LIST setup_attribute_list(HANDLE *handles, size_t num_handles) { ASSERT(handles); int r = -1; // Make sure all the given handles can be inherited. for (size_t i = 0; i < num_handles; i++) { r = SetHandleInformation(handles[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); if (r == 0) { return NULL; } } // Get the required size for `attribute_list`. SIZE_T attribute_list_size = 0; r = InitializeProcThreadAttributeList(NULL, NUM_ATTRIBUTES, 0, &attribute_list_size); if (r == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { return NULL; } LPPROC_THREAD_ATTRIBUTE_LIST attribute_list = malloc(attribute_list_size); if (attribute_list == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } r = InitializeProcThreadAttributeList(attribute_list, NUM_ATTRIBUTES, 0, &attribute_list_size); if (r == 0) { free(attribute_list); return NULL; } // Add the handles to be inherited to `attribute_list`. r = UpdateProcThreadAttribute(attribute_list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handles, num_handles * sizeof(HANDLE), NULL, NULL); if (r == 0) { DeleteProcThreadAttributeList(attribute_list); free(attribute_list); return NULL; } return attribute_list; } #define NULSTR_FOREACH(i, l) \ for ((i) = (l); (i) && *(i) != L'\0'; (i) = wcschr((i), L'\0') + 1) static wchar_t *env_concat(const wchar_t *a, const wchar_t *b) { const wchar_t *i = NULL; size_t size = 1; wchar_t *c = NULL; NULSTR_FOREACH(i, a) { size += wcslen(i) + 1; } NULSTR_FOREACH(i, b) { size += wcslen(i) + 1; } wchar_t *r = calloc(size, sizeof(wchar_t)); if (!r) { return NULL; } c = r; NULSTR_FOREACH(i, a) { wcscpy(c, i); c += wcslen(i) + 1; } NULSTR_FOREACH(i, b) { wcscpy(c, i); c += wcslen(i) + 1; } *c = L'\0'; return r; } static wchar_t *env_setup(REPROC_ENV behavior, const char *const *extra) { wchar_t *env_parent_wstring = NULL; char *env_extra = NULL; wchar_t *env_extra_wstring = NULL; wchar_t *env_wstring = NULL; if (behavior == REPROC_ENV_EXTEND) { env_parent_wstring = GetEnvironmentStringsW(); } if (extra != NULL) { env_extra = env_join(extra); if (env_extra == NULL) { goto finish; } size_t joined_size = env_join_size(extra); ASSERT(joined_size <= INT_MAX); env_extra_wstring = utf16_from_utf8(env_extra, (int) joined_size); if (env_extra_wstring == NULL) { goto finish; } } env_wstring = env_concat(env_parent_wstring, env_extra_wstring); if (env_wstring == NULL) { goto finish; } finish: FreeEnvironmentStringsW(env_parent_wstring); free(env_extra); free(env_extra_wstring); return env_wstring; } int process_start(HANDLE *process, const char *const *argv, struct process_options options) { ASSERT(process); if (argv == NULL) { return -ERROR_CALL_NOT_IMPLEMENTED; } ASSERT(argv[0] != NULL); char *command_line = NULL; wchar_t *command_line_wstring = NULL; wchar_t *env_wstring = NULL; wchar_t *working_directory_wstring = NULL; LPPROC_THREAD_ATTRIBUTE_LIST attribute_list = NULL; PROCESS_INFORMATION info = { PROCESS_INVALID, HANDLE_INVALID, 0, 0 }; int r = -1; // Join `argv` to a whitespace delimited string as required by // `CreateProcessW`. command_line = argv_join(argv); if (command_line == NULL) { r = -(int) GetLastError(); goto finish; } // Convert UTF-8 to UTF-16 as required by `CreateProcessW`. command_line_wstring = utf16_from_utf8(command_line, -1); if (command_line_wstring == NULL) { r = -(int) GetLastError(); goto finish; } // Idem for `working_directory` if it isn't `NULL`. if (options.working_directory != NULL) { working_directory_wstring = utf16_from_utf8(options.working_directory, -1); if (working_directory_wstring == NULL) { r = -(int) GetLastError(); goto finish; } } env_wstring = env_setup(options.env.behavior, options.env.extra); if (env_wstring == NULL) { r = -(int) GetLastError(); goto finish; } // Windows Vista added the `STARTUPINFOEXW` structure in which we can put a // list of handles that should be inherited. Only these handles are inherited // by the child process. Other code in an application that calls // `CreateProcess` without passing a `STARTUPINFOEXW` struct containing the // handles it should inherit can still unintentionally inherit handles meant // for a reproc child process. See https://stackoverflow.com/a/2345126 for // more information. HANDLE handles[] = { options.handle.exit, options.handle.in, options.handle.out, options.handle.err }; size_t num_handles = ARRAY_SIZE(handles); if (options.handle.out == options.handle.err) { // CreateProcess doesn't like the same handle being specified twice in the // `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` attribute. num_handles--; } attribute_list = setup_attribute_list(handles, num_handles); if (attribute_list == NULL) { r = -(int) GetLastError(); goto finish; } STARTUPINFOEXW extended_startup_info = { .StartupInfo = { .cb = sizeof(extended_startup_info), .dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW, // `STARTF_USESTDHANDLES` .hStdInput = options.handle.in, .hStdOutput = options.handle.out, .hStdError = options.handle.err, // `STARTF_USESHOWWINDOW`. Make sure the console window of // the child process isn't visible. See // https://github.com/DaanDeMeyer/reproc/issues/6 and // https://github.com/DaanDeMeyer/reproc/pull/7 for more // information. .wShowWindow = SW_HIDE }, .lpAttributeList = attribute_list }; LPSTARTUPINFOW startup_info_address = &extended_startup_info.StartupInfo; // Child processes inherit the error mode of their parents. To avoid child // processes creating error dialogs we set our error mode to not create error // dialogs temporarily which is inherited by the child process. DWORD previous_error_mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); SECURITY_ATTRIBUTES do_not_inherit = { .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = false, .lpSecurityDescriptor = NULL }; r = CreateProcessW(NULL, command_line_wstring, &do_not_inherit, &do_not_inherit, true, CREATION_FLAGS, env_wstring, working_directory_wstring, startup_info_address, &info); SetErrorMode(previous_error_mode); if (r == 0) { r = -(int) GetLastError(); goto finish; } *process = info.hProcess; r = 0; finish: free(command_line); free(command_line_wstring); free(env_wstring); free(working_directory_wstring); DeleteProcThreadAttributeList(attribute_list); free(attribute_list); handle_destroy(info.hThread); return r < 0 ? r : 1; } int process_pid(process_type process) { ASSERT(process); return (int) GetProcessId(process); } int process_wait(HANDLE process) { ASSERT(process); int r = -1; r = (int) WaitForSingleObject(process, INFINITE); if ((DWORD) r == WAIT_FAILED) { return -(int) GetLastError(); } DWORD status = 0; r = GetExitCodeProcess(process, &status); if (r == 0) { return -(int) GetLastError(); } // `GenerateConsoleCtrlEvent` causes a process to exit with this exit code. // Because `GenerateConsoleCtrlEvent` has roughly the same semantics as // `SIGTERM`, we map its exit code to `SIGTERM`. if (status == 3221225786) { status = (DWORD) REPROC_SIGTERM; } return (int) status; } int process_terminate(HANDLE process) { ASSERT(process && process != PROCESS_INVALID); // `GenerateConsoleCtrlEvent` can only be called on a process group. To call // `GenerateConsoleCtrlEvent` on a single child process it has to be put in // its own process group (which we did when starting the child process). BOOL r = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, GetProcessId(process)); return r == 0 ? -(int) GetLastError() : 0; } int process_kill(HANDLE process) { ASSERT(process && process != PROCESS_INVALID); // We use 137 (`SIGKILL`) as the exit status because it is the same exit // status as a process that is stopped with the `SIGKILL` signal on POSIX // systems. BOOL r = TerminateProcess(process, (DWORD) REPROC_SIGKILL); return r == 0 ? -(int) GetLastError() : 0; } HANDLE process_destroy(HANDLE process) { return handle_destroy(process); } reproc-14.2.5/reproc/src/redirect.c000066400000000000000000000064141460055552200171340ustar00rootroot00000000000000#include "redirect.h" #include "error.h" static int redirect_pipe(pipe_type *parent, handle_type *child, REPROC_STREAM stream, bool nonblocking) { ASSERT(parent); ASSERT(child); pipe_type pipe[] = { PIPE_INVALID, PIPE_INVALID }; int r = -1; r = pipe_init(&pipe[0], &pipe[1]); if (r < 0) { goto finish; } r = pipe_nonblocking(stream == REPROC_STREAM_IN ? pipe[1] : pipe[0], nonblocking); if (r < 0) { goto finish; } *parent = stream == REPROC_STREAM_IN ? pipe[1] : pipe[0]; *child = stream == REPROC_STREAM_IN ? (handle_type) pipe[0] : (handle_type) pipe[1]; finish: if (r < 0) { pipe_destroy(pipe[0]); pipe_destroy(pipe[1]); } return r; } int redirect_init(pipe_type *parent, handle_type *child, REPROC_STREAM stream, reproc_redirect redirect, bool nonblocking, handle_type out) { ASSERT(parent); ASSERT(child); int r = REPROC_EINVAL; switch (redirect.type) { case REPROC_REDIRECT_DEFAULT: ASSERT(false); break; case REPROC_REDIRECT_PIPE: r = redirect_pipe(parent, child, stream, nonblocking); break; case REPROC_REDIRECT_PARENT: r = redirect_parent(child, stream); if (r == REPROC_EPIPE) { // Discard if the corresponding parent stream is closed. r = redirect_discard(child, stream); } if (r < 0) { break; } *parent = PIPE_INVALID; break; case REPROC_REDIRECT_DISCARD: r = redirect_discard(child, stream); if (r < 0) { break; } *parent = PIPE_INVALID; break; case REPROC_REDIRECT_HANDLE: ASSERT(redirect.handle); r = 0; *child = redirect.handle; *parent = PIPE_INVALID; break; case REPROC_REDIRECT_FILE: ASSERT(redirect.file); r = redirect_file(child, redirect.file); if (r < 0) { break; } *parent = PIPE_INVALID; break; case REPROC_REDIRECT_STDOUT: ASSERT(stream == REPROC_STREAM_ERR); ASSERT(out != HANDLE_INVALID); r = 0; *child = out; *parent = PIPE_INVALID; break; case REPROC_REDIRECT_PATH: ASSERT(redirect.path); r = redirect_path(child, stream, redirect.path); if (r < 0) { break; } *parent = PIPE_INVALID; break; } return r; } handle_type redirect_destroy(handle_type child, REPROC_REDIRECT type) { if (child == HANDLE_INVALID) { return HANDLE_INVALID; } switch (type) { case REPROC_REDIRECT_DEFAULT: ASSERT(false); break; case REPROC_REDIRECT_PIPE: // We know `handle` is a pipe if `REDIRECT_PIPE` is used so the cast is // safe. This little hack prevents us from having to introduce a generic // handle type. pipe_destroy((pipe_type) child); break; case REPROC_REDIRECT_DISCARD: case REPROC_REDIRECT_PATH: handle_destroy(child); break; case REPROC_REDIRECT_PARENT: case REPROC_REDIRECT_FILE: case REPROC_REDIRECT_HANDLE: case REPROC_REDIRECT_STDOUT: break; } return HANDLE_INVALID; } reproc-14.2.5/reproc/src/redirect.h000066400000000000000000000012351460055552200171350ustar00rootroot00000000000000#pragma once #include #include "handle.h" #include "pipe.h" int redirect_init(pipe_type *parent, handle_type *child, REPROC_STREAM stream, reproc_redirect redirect, bool nonblocking, handle_type out); handle_type redirect_destroy(handle_type child, REPROC_REDIRECT type); // Internal prototypes int redirect_parent(handle_type *child, REPROC_STREAM stream); int redirect_discard(handle_type *child, REPROC_STREAM stream); int redirect_file(handle_type *child, FILE *file); int redirect_path(handle_type *child, REPROC_STREAM stream, const char *path); reproc-14.2.5/reproc/src/redirect.posix.c000066400000000000000000000024001460055552200202640ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include "redirect.h" #include #include #include #include "error.h" #include "pipe.h" static FILE *stream_to_file(REPROC_STREAM stream) { switch (stream) { case REPROC_STREAM_IN: return stdin; case REPROC_STREAM_OUT: return stdout; case REPROC_STREAM_ERR: return stderr; } return NULL; } int redirect_parent(int *child, REPROC_STREAM stream) { ASSERT(child); FILE *file = stream_to_file(stream); if (file == NULL) { return -EINVAL; } int r = fileno(file); if (r < 0) { return errno == EBADF ? -EPIPE : -errno; } *child = r; // `r` contains the duplicated file descriptor. return 0; } int redirect_discard(int *child, REPROC_STREAM stream) { return redirect_path(child, stream, "/dev/null"); } int redirect_file(int *child, FILE *file) { ASSERT(child); int r = fileno(file); if (r < 0) { return -errno; } *child = r; return 0; } int redirect_path(int *child, REPROC_STREAM stream, const char *path) { ASSERT(child); ASSERT(path); int mode = stream == REPROC_STREAM_IN ? O_RDONLY : O_WRONLY; int r = open(path, mode | O_CREAT | O_CLOEXEC, 0640); if (r < 0) { return -errno; } *child = r; return 0; } reproc-14.2.5/reproc/src/redirect.windows.c000066400000000000000000000045441460055552200206270ustar00rootroot00000000000000#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // _WIN32_WINNT_VISTA #elif _WIN32_WINNT < 0x0600 #error "_WIN32_WINNT must be greater than _WIN32_WINNT_VISTA (0x0600)" #endif #include "redirect.h" #include #include #include #include "error.h" #include "pipe.h" #include "utf.h" static DWORD stream_to_id(REPROC_STREAM stream) { switch (stream) { case REPROC_STREAM_IN: return STD_INPUT_HANDLE; case REPROC_STREAM_OUT: return STD_OUTPUT_HANDLE; case REPROC_STREAM_ERR: return STD_ERROR_HANDLE; } return 0; } int redirect_parent(HANDLE *child, REPROC_STREAM stream) { ASSERT(child); DWORD id = stream_to_id(stream); if (id == 0) { return -ERROR_INVALID_PARAMETER; } HANDLE *handle = GetStdHandle(id); if (handle == INVALID_HANDLE_VALUE) { return -(int) GetLastError(); } if (handle == NULL) { return -ERROR_BROKEN_PIPE; } *child = handle; return 0; } enum { FILE_NO_TEMPLATE = 0 }; int redirect_discard(HANDLE *child, REPROC_STREAM stream) { return redirect_path(child, stream, "NUL"); } int redirect_file(HANDLE *child, FILE *file) { ASSERT(child); ASSERT(file); int r = _fileno(file); if (r < 0) { return -ERROR_INVALID_HANDLE; } intptr_t result = _get_osfhandle(r); if (result == -1) { return -ERROR_INVALID_HANDLE; } *child = (HANDLE) result; return 0; } int redirect_path(handle_type *child, REPROC_STREAM stream, const char *path) { ASSERT(child); ASSERT(path); DWORD mode = stream == REPROC_STREAM_IN ? GENERIC_READ : GENERIC_WRITE; HANDLE handle = HANDLE_INVALID; int r = -1; wchar_t *wpath = utf16_from_utf8(path, -1); if (wpath == NULL) { r = -(int) GetLastError(); goto finish; } SECURITY_ATTRIBUTES do_not_inherit = { .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = false, .lpSecurityDescriptor = NULL }; handle = CreateFileW(wpath, mode, FILE_SHARE_READ | FILE_SHARE_WRITE, &do_not_inherit, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) FILE_NO_TEMPLATE); if (handle == INVALID_HANDLE_VALUE) { r = -(int) GetLastError(); goto finish; } *child = handle; handle = HANDLE_INVALID; r = 0; finish: free(wpath); handle_destroy(handle); return r; } reproc-14.2.5/reproc/src/reproc.c000066400000000000000000000422041460055552200166220ustar00rootroot00000000000000#include #include #include "clock.h" #include "error.h" #include "handle.h" #include "init.h" #include "macro.h" #include "options.h" #include "pipe.h" #include "process.h" #include "redirect.h" struct reproc_t { process_type handle; struct { pipe_type in; pipe_type out; pipe_type err; pipe_type exit; } pipe; int status; reproc_stop_actions stop; int64_t deadline; bool nonblocking; struct { pipe_type out; pipe_type err; } child; }; enum { STATUS_NOT_STARTED = -1, STATUS_IN_PROGRESS = -2, STATUS_IN_CHILD = -3, }; #define SIGOFFSET 128 const int REPROC_SIGKILL = SIGOFFSET + 9; const int REPROC_SIGTERM = SIGOFFSET + 15; const int REPROC_INFINITE = -1; const int REPROC_DEADLINE = -2; static int setup_input(pipe_type *pipe, const uint8_t *data, size_t size) { if (data == NULL) { ASSERT(size == 0); return 0; } ASSERT(pipe && *pipe != PIPE_INVALID); // `reproc_write` only needs the child process stdin pipe to be initialized. size_t written = 0; int r = -1; // Make sure we don't block indefinitely when `input` is bigger than the // size of the pipe. r = pipe_nonblocking(*pipe, true); if (r < 0) { return r; } while (written < size) { r = pipe_write(*pipe, data + written, size - written); if (r < 0) { return r; } ASSERT(written + (size_t) r <= size); written += (size_t) r; } *pipe = pipe_destroy(*pipe); return 0; } static int expiry(int timeout, int64_t deadline) { if (timeout == REPROC_INFINITE && deadline == REPROC_INFINITE) { return REPROC_INFINITE; } if (deadline == REPROC_INFINITE) { return timeout; } int64_t n = now(); if (n >= deadline) { return REPROC_DEADLINE; } // `deadline` exceeds `now` by at most a full `int` so the cast is safe. int remaining = (int) (deadline - n); if (timeout == REPROC_INFINITE) { return remaining; } return MIN(timeout, remaining); } static size_t find_earliest_deadline(reproc_event_source *sources, size_t num_sources) { ASSERT(sources); ASSERT(num_sources > 0); size_t earliest = 0; int min = REPROC_INFINITE; for (size_t i = 0; i < num_sources; i++) { reproc_t *process = sources[i].process; if (process == NULL) { continue; } int current = expiry(REPROC_INFINITE, process->deadline); if (current == REPROC_DEADLINE) { return i; } if (min == REPROC_INFINITE || current < min) { earliest = i; min = current; } } return earliest; } reproc_t *reproc_new(void) { reproc_t *process = malloc(sizeof(reproc_t)); if (process == NULL) { return NULL; } *process = (reproc_t){ .handle = PROCESS_INVALID, .pipe = { .in = PIPE_INVALID, .out = PIPE_INVALID, .err = PIPE_INVALID, .exit = PIPE_INVALID }, .child = { .out = PIPE_INVALID, .err = PIPE_INVALID }, .status = STATUS_NOT_STARTED, .deadline = REPROC_INFINITE }; return process; } int reproc_start(reproc_t *process, const char *const *argv, reproc_options options) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status == STATUS_NOT_STARTED); struct { handle_type in; handle_type out; handle_type err; pipe_type exit; } child = { HANDLE_INVALID, HANDLE_INVALID, HANDLE_INVALID, PIPE_INVALID }; int r = -1; r = init(); if (r < 0) { return r; // Make sure we can always call `deinit` in `finish`. } r = parse_options(&options, argv); if (r < 0) { goto finish; } r = redirect_init(&process->pipe.in, &child.in, REPROC_STREAM_IN, options.redirect.in, options.nonblocking, HANDLE_INVALID); if (r < 0) { goto finish; } r = redirect_init(&process->pipe.out, &child.out, REPROC_STREAM_OUT, options.redirect.out, options.nonblocking, HANDLE_INVALID); if (r < 0) { goto finish; } r = redirect_init(&process->pipe.err, &child.err, REPROC_STREAM_ERR, options.redirect.err, options.nonblocking, child.out); if (r < 0) { goto finish; } r = pipe_init(&process->pipe.exit, &child.exit); if (r < 0) { goto finish; } r = setup_input(&process->pipe.in, options.input.data, options.input.size); if (r < 0) { goto finish; } struct process_options process_options = { .env = { .behavior = options.env.behavior, .extra = options.env.extra }, .working_directory = options.working_directory, .handle = { .in = child.in, .out = child.out, .err = child.err, .exit = (handle_type) child.exit } }; r = process_start(&process->handle, argv, process_options); if (r < 0) { goto finish; } if (r > 0) { process->stop = options.stop; if (options.deadline != REPROC_INFINITE) { process->deadline = now() + options.deadline; } process->nonblocking = options.nonblocking; } finish: // Either an error has ocurred or the child pipe endpoints have been copied to // the stdin/stdout/stderr streams of the child process. Either way, they can // be safely closed. redirect_destroy(child.in, options.redirect.in.type); // See `reproc_poll` for why we do this. #ifdef _WIN32 if (r < 0 || options.redirect.out.type != REPROC_REDIRECT_PIPE) { child.out = redirect_destroy(child.out, options.redirect.out.type); } if (r < 0 || options.redirect.err.type != REPROC_REDIRECT_PIPE) { child.err = redirect_destroy(child.err, options.redirect.err.type); } #else child.out = redirect_destroy(child.out, options.redirect.out.type); child.err = redirect_destroy(child.err, options.redirect.err.type); #endif pipe_destroy(child.exit); if (r < 0) { process->handle = process_destroy(process->handle); process->pipe.in = pipe_destroy(process->pipe.in); process->pipe.out = pipe_destroy(process->pipe.out); process->pipe.err = pipe_destroy(process->pipe.err); process->pipe.exit = pipe_destroy(process->pipe.exit); deinit(); } else if (r == 0) { process->handle = PROCESS_INVALID; // `process_start` has already taken care of closing the handles for us. process->pipe.in = PIPE_INVALID; process->pipe.out = PIPE_INVALID; process->pipe.err = PIPE_INVALID; process->pipe.exit = PIPE_INVALID; process->status = STATUS_IN_CHILD; } else { process->child.out = (pipe_type) child.out; process->child.err = (pipe_type) child.err; process->status = STATUS_IN_PROGRESS; } return r; } enum { PIPES_PER_SOURCE = 4 }; static bool contains_valid_pipe(pipe_event_source *sources, size_t num_sources) { for (size_t i = 0; i < num_sources; i++) { if (sources[i].pipe != PIPE_INVALID) { return true; } } return false; } int reproc_poll(reproc_event_source *sources, size_t num_sources, int timeout) { ASSERT_EINVAL(sources); ASSERT_EINVAL(num_sources > 0); size_t earliest = find_earliest_deadline(sources, num_sources); int64_t deadline = sources[earliest].process == NULL ? REPROC_INFINITE : sources[earliest].process->deadline; int first = expiry(timeout, deadline); size_t num_pipes = num_sources * PIPES_PER_SOURCE; int r = REPROC_ENOMEM; if (first == REPROC_DEADLINE) { for (size_t i = 0; i < num_sources; i++) { sources[i].events = 0; } sources[earliest].events = REPROC_EVENT_DEADLINE; return 1; } pipe_event_source *pipes = calloc(num_pipes, sizeof(pipe_event_source)); if (pipes == NULL) { return r; } for (size_t i = 0; i < num_pipes; i++) { pipes[i].pipe = PIPE_INVALID; } for (size_t i = 0; i < num_sources; i++) { size_t j = i * PIPES_PER_SOURCE; reproc_t *process = sources[i].process; int interests = sources[i].interests; if (process == NULL) { continue; } bool in = interests & REPROC_EVENT_IN; pipes[j + 0].pipe = in ? process->pipe.in : PIPE_INVALID; pipes[j + 0].interests = PIPE_EVENT_OUT; bool out = interests & REPROC_EVENT_OUT; pipes[j + 1].pipe = out ? process->pipe.out : PIPE_INVALID; pipes[j + 1].interests = PIPE_EVENT_IN; bool err = interests & REPROC_EVENT_ERR; pipes[j + 2].pipe = err ? process->pipe.err : PIPE_INVALID; pipes[j + 2].interests = PIPE_EVENT_IN; bool exit = (interests & REPROC_EVENT_EXIT) || (interests & REPROC_EVENT_OUT && process->child.out != PIPE_INVALID) || (interests & REPROC_EVENT_ERR && process->child.err != PIPE_INVALID); pipes[j + 3].pipe = exit ? process->pipe.exit : PIPE_INVALID; pipes[j + 3].interests = PIPE_EVENT_IN; } if (!contains_valid_pipe(pipes, num_pipes)) { r = REPROC_EPIPE; goto finish; } r = pipe_poll(pipes, num_pipes, first); if (r < 0) { goto finish; } for (size_t i = 0; i < num_sources; i++) { sources[i].events = 0; } if (r == 0 && first != timeout) { // Differentiate between timeout and deadline expiry. Deadline expiry is an // event, timeouts are not. sources[earliest].events = REPROC_EVENT_DEADLINE; r = 1; } else if (r > 0) { // Convert pipe events to process events. for (size_t i = 0; i < num_pipes; i++) { if (pipes[i].pipe == PIPE_INVALID) { continue; } if (pipes[i].events > 0) { // Index in a set of pipes determines the process pipe and thus the // process event. // 0 = stdin pipe => REPROC_EVENT_IN // 1 = stdout pipe => REPROC_EVENT_OUT // ... int event = 1 << (i % PIPES_PER_SOURCE); sources[i / PIPES_PER_SOURCE].events |= event; } } r = 0; // Count the number of processes with events. for (size_t i = 0; i < num_sources; i++) { r += sources[i].events > 0; } // On Windows, when redirecting to sockets, we keep the child handles alive // in the parent process (see `reproc_start`). We do this because Windows // doesn't correctly flush redirected socket handles when a child process // exits. This can lead to data loss where the parent process doesn't // receive all output of the child process. To get around this, we keep an // extra handle open in the parent process which we close correctly when we // detect the child process has exited. Detecting whether a child process // has exited happens via another inherited socket, but here there's no // danger of data loss because no data is received over this socket. bool again = false; for (size_t i = 0; i < num_sources; i++) { if (!(sources[i].events & REPROC_EVENT_EXIT)) { continue; } reproc_t *process = sources[i].process; if (process->child.out == PIPE_INVALID && process->child.err == PIPE_INVALID) { continue; } r = pipe_shutdown(process->child.out); if (r < 0) { goto finish; } r = pipe_shutdown(process->child.err); if (r < 0) { goto finish; } process->child.out = pipe_destroy(process->child.out); process->child.err = pipe_destroy(process->child.err); again = true; } // If we've closed handles, we poll again so we can include any new close // events that occurred because we closed handles. if (again) { r = reproc_poll(sources, num_sources, timeout); if (r < 0) { goto finish; } } } finish: free(pipes); return r; } int reproc_read(reproc_t *process, REPROC_STREAM stream, uint8_t *buffer, size_t size) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(stream == REPROC_STREAM_OUT || stream == REPROC_STREAM_ERR); ASSERT_EINVAL(buffer); pipe_type *pipe = stream == REPROC_STREAM_OUT ? &process->pipe.out : &process->pipe.err; pipe_type child = stream == REPROC_STREAM_OUT ? process->child.out : process->child.err; int r = -1; if (*pipe == PIPE_INVALID) { return REPROC_EPIPE; } // If we've kept extra handles open in the parent, make sure we use // `reproc_poll` which closes the extra handles we keep open when the child // process exits. If we don't, `pipe_read` will block forever because the // extra handles we keep open in the parent would never be closed. if (child != PIPE_INVALID) { int event = stream == REPROC_STREAM_OUT ? REPROC_EVENT_OUT : REPROC_EVENT_ERR; reproc_event_source source = { process, event, 0 }; r = reproc_poll(&source, 1, process->nonblocking ? 0 : REPROC_INFINITE); if (r <= 0) { return r == 0 ? REPROC_EWOULDBLOCK : r; } } r = pipe_read(*pipe, buffer, size); if (r == REPROC_EPIPE) { *pipe = pipe_destroy(*pipe); } return r; } int reproc_write(reproc_t *process, const uint8_t *buffer, size_t size) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); if (buffer == NULL) { // Allow `NULL` buffers but only if `size == 0`. ASSERT_EINVAL(size == 0); return 0; } if (process->pipe.in == PIPE_INVALID) { return REPROC_EPIPE; } int r = pipe_write(process->pipe.in, buffer, size); if (r == REPROC_EPIPE) { process->pipe.in = pipe_destroy(process->pipe.in); } return r; } int reproc_close(reproc_t *process, REPROC_STREAM stream) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); switch (stream) { case REPROC_STREAM_IN: process->pipe.in = pipe_destroy(process->pipe.in); return 0; case REPROC_STREAM_OUT: process->pipe.out = pipe_destroy(process->pipe.out); return 0; case REPROC_STREAM_ERR: process->pipe.err = pipe_destroy(process->pipe.err); return 0; } return REPROC_EINVAL; } int reproc_wait(reproc_t *process, int timeout) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(process->status != STATUS_NOT_STARTED); int r = -1; if (process->status >= 0) { return process->status; } if (timeout == REPROC_DEADLINE) { timeout = expiry(REPROC_INFINITE, process->deadline); // If the deadline has expired, `expiry` returns `REPROC_DEADLINE` which // means we'll only check if the process is still running. if (timeout == REPROC_DEADLINE) { timeout = 0; } } ASSERT(process->pipe.exit != PIPE_INVALID); pipe_event_source source = { .pipe = process->pipe.exit, .interests = PIPE_EVENT_IN }; r = pipe_poll(&source, 1, timeout); if (r <= 0) { return r == 0 ? REPROC_ETIMEDOUT : r; } r = process_wait(process->handle); if (r < 0) { return r; } process->pipe.exit = pipe_destroy(process->pipe.exit); return process->status = r; } int reproc_terminate(reproc_t *process) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(process->status != STATUS_NOT_STARTED); if (process->status >= 0) { return 0; } return process_terminate(process->handle); } int reproc_kill(reproc_t *process) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(process->status != STATUS_NOT_STARTED); if (process->status >= 0) { return 0; } return process_kill(process->handle); } int reproc_stop(reproc_t *process, reproc_stop_actions stop) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(process->status != STATUS_NOT_STARTED); stop = parse_stop_actions(stop); reproc_stop_action actions[] = { stop.first, stop.second, stop.third }; int r = -1; for (size_t i = 0; i < ARRAY_SIZE(actions); i++) { r = REPROC_EINVAL; // NOLINT switch (actions[i].action) { case REPROC_STOP_NOOP: r = 0; continue; case REPROC_STOP_WAIT: r = 0; break; case REPROC_STOP_TERMINATE: r = reproc_terminate(process); break; case REPROC_STOP_KILL: r = reproc_kill(process); break; } // Stop if `reproc_terminate` or `reproc_kill` fail. if (r < 0) { break; } r = reproc_wait(process, actions[i].timeout); if (r != REPROC_ETIMEDOUT) { break; } } return r; } int reproc_pid(reproc_t *process) { ASSERT_EINVAL(process); ASSERT_EINVAL(process->status != STATUS_IN_CHILD); ASSERT_EINVAL(process->status != STATUS_NOT_STARTED); return process_pid(process->handle); } reproc_t *reproc_destroy(reproc_t *process) { ASSERT_RETURN(process, NULL); if (process->status == STATUS_IN_PROGRESS) { reproc_stop(process, process->stop); } process_destroy(process->handle); pipe_destroy(process->pipe.in); pipe_destroy(process->pipe.out); pipe_destroy(process->pipe.err); pipe_destroy(process->pipe.exit); pipe_destroy(process->child.out); pipe_destroy(process->child.err); if (process->status != STATUS_NOT_STARTED) { deinit(); } free(process); return NULL; } const char *reproc_strerror(int error) { return error_string(error); } reproc-14.2.5/reproc/src/run.c000066400000000000000000000022171460055552200161340ustar00rootroot00000000000000#include #include #include "error.h" int reproc_run(const char *const *argv, reproc_options options) { if (!options.redirect.discard && !options.redirect.file && !options.redirect.path) { options.redirect.parent = true; } return reproc_run_ex(argv, options, REPROC_SINK_NULL, REPROC_SINK_NULL); } int reproc_run_ex(const char *const *argv, reproc_options options, // lgtm [cpp/large-parameter] reproc_sink out, reproc_sink err) { reproc_t *process = NULL; int r = REPROC_ENOMEM; // There's no way for `reproc_run_ex` to inform the caller whether we're in // the forked process or the parent process so let's not allow forking when // using `reproc_run_ex`. ASSERT_EINVAL(!options.fork); process = reproc_new(); if (process == NULL) { goto finish; } r = reproc_start(process, argv, options); if (r < 0) { goto finish; } r = reproc_drain(process, out, err); if (r < 0) { goto finish; } r = reproc_stop(process, options.stop); if (r < 0) { goto finish; } finish: reproc_destroy(process); return r; } reproc-14.2.5/reproc/src/strv.c000066400000000000000000000020371460055552200163260ustar00rootroot00000000000000#include "strv.h" #include #include #include #include "error.h" static char *str_dup(const char *s) { ASSERT_RETURN(s, NULL); char *r = malloc(strlen(s) + 1); if (!r) { return NULL; } strcpy(r, s); // NOLINT return r; } char **strv_concat(char *const *a, const char *const *b) { char *const *i = NULL; const char *const *j = NULL; size_t size = 1; size_t c = 0; STRV_FOREACH(i, a) { size++; } STRV_FOREACH(j, b) { size++; } char **r = calloc(size, sizeof(char *)); if (!r) { goto finish; } STRV_FOREACH(i, a) { r[c] = str_dup(*i); if (!r[c]) { goto finish; } c++; } STRV_FOREACH(j, b) { r[c] = str_dup(*j); if (!r[c]) { goto finish; } c++; } r[c++] = NULL; finish: if (c < size) { STRV_FOREACH(i, r) { free(*i); } free(r); return NULL; } return r; } char **strv_free(char **l) { char **s = NULL; STRV_FOREACH(s, l) { free(*s); } free(l); return NULL; } reproc-14.2.5/reproc/src/strv.h000066400000000000000000000002451460055552200163320ustar00rootroot00000000000000#pragma once #define STRV_FOREACH(s, l) for ((s) = (l); (s) && *(s); (s)++) char **strv_concat(char *const *a, const char *const *b); char **strv_free(char **l); reproc-14.2.5/reproc/src/utf.h000066400000000000000000000012271460055552200161330ustar00rootroot00000000000000#pragma once #include // `size` represents the entire size of `string`, including NUL-terminators. We // take the entire size because strings like the environment string passed to // CreateProcessW includes multiple NUL-terminators so we can't always rely on // `strlen` to calculate the string length for us. See the lpEnvironment // documentation of CreateProcessW: // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw // Pass -1 as the size to have `utf16_from_utf8` calculate the size until (and // including) the first NUL terminator. wchar_t *utf16_from_utf8(const char *string, int size); reproc-14.2.5/reproc/src/utf.posix.c000066400000000000000000000000701460055552200172620ustar00rootroot00000000000000#include "utf.h" // `utf16_from_utf8` is Windows-only. reproc-14.2.5/reproc/src/utf.windows.c000066400000000000000000000017751460055552200176270ustar00rootroot00000000000000#include "utf.h" #include #include #include #include "error.h" wchar_t *utf16_from_utf8(const char *string, int size) { ASSERT(string); // Determine wstring size (`MultiByteToWideChar` returns the required size if // its last two arguments are `NULL` and 0). int r = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string, size, NULL, 0); if (r == 0) { return NULL; } // `MultiByteToWideChar` does not return negative values so the cast to // `size_t` is safe. wchar_t *wstring = calloc((size_t) r, sizeof(wchar_t)); if (wstring == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } // Now we pass our allocated string and its size as the last two arguments // instead of `NULL` and 0 which makes `MultiByteToWideChar` actually perform // the conversion. r = MultiByteToWideChar(CP_UTF8, 0, string, size, wstring, r); if (r == 0) { free(wstring); return NULL; } return wstring; } reproc-14.2.5/reproc/test/000077500000000000000000000000001460055552200153525ustar00rootroot00000000000000reproc-14.2.5/reproc/test/argv.c000066400000000000000000000013361460055552200164600ustar00rootroot00000000000000#include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/argv", "\"argument 1\"", "\"argument 2\"", NULL }; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; r = reproc_run_ex(argv, (reproc_options){ 0 }, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); const char *current = output; for (size_t i = 0; i < 3; i++) { size_t size = strlen(argv[i]); ASSERT_GE_SIZE(strlen(current), size); ASSERT_EQ_MEM(current, argv[i], size); current += size; current += *current == '\r'; current += *current == '\n'; } ASSERT_EQ_SIZE(strlen(current), (size_t) 0); reproc_free(output); } reproc-14.2.5/reproc/test/assert.h000066400000000000000000000036031460055552200170260ustar00rootroot00000000000000#pragma once #include #include #include #define ASSERT(expression) ASSERT_MSG(expression, "%s", "") #define ASSERT_OK(r) ASSERT_MSG(r >= 0, "%s", reproc_strerror(r)) #define ASSERT_EQ_MEM(left, right, size) \ ASSERT_MSG(memcmp(left, right, size) == 0, "\"%.*s\" == \"%.*s\"", \ (int) size, left, (int) size, right) #define ASSERT_EQ_STR(left, right) \ ASSERT_MSG(strcmp(left, right) == 0, "%s == %s", left, right) #define ASSERT_GE_SIZE(left, right) \ ASSERT_MSG(left >= right, "%zu >= %zu", left, right) #define ASSERT_EQ_SIZE(left, right) \ ASSERT_MSG(left == right, "%zu == %zu", left, right) #define ASSERT_EQ_INT(left, right) \ ASSERT_MSG(left == right, "%i == %i", left, right) #ifdef _WIN32 #define ABORT() exit(EXIT_FAILURE) #else // Use `abort` so we get a coredump. #define ABORT() abort() #endif #define ASSERT_MSG(expression, format, ...) \ do { \ if (!(expression)) { \ fprintf(stderr, "%s:%u: Assertion '%s' (" format ") failed", __FILE__, \ __LINE__, #expression, __VA_ARGS__); \ \ fflush(stderr); \ \ ABORT(); \ } \ } while (0) reproc-14.2.5/reproc/test/deadline.c000066400000000000000000000003421460055552200172620ustar00rootroot00000000000000#include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/deadline", NULL }; int r = reproc_run(argv, (reproc_options){ .deadline = 100 }); ASSERT(r == REPROC_SIGTERM); } reproc-14.2.5/reproc/test/env.c000066400000000000000000000015361460055552200163130ustar00rootroot00000000000000#include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/env", NULL }; const char *envp[] = { "IP=127.0.0.1", "PORT=8080", NULL }; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; r = reproc_run_ex(argv, (reproc_options){ .env.behavior = REPROC_ENV_EMPTY, .env.extra = envp }, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); const char *current = output; for (size_t i = 0; i < 2; i++) { size_t size = strlen(envp[i]); ASSERT_GE_SIZE(strlen(current), size); ASSERT_EQ_MEM(current, envp[i], size); current += size; current += *current == '\r'; current += *current == '\n'; } ASSERT_EQ_SIZE(strlen(current), (size_t) 0); reproc_free(output); } reproc-14.2.5/reproc/test/fork.c000066400000000000000000000013671460055552200164660ustar00rootroot00000000000000#include #include #include #include #include "assert.h" int main(void) { reproc_t *process = reproc_new(); const char *MESSAGE = "reproc stands for REdirected PROCess!"; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; r = reproc_start(process, NULL, (reproc_options){ .fork = true }); if (r == 0) { printf("%s", MESSAGE); fclose(stdout); // `_exit` doesn't flush stdout. _exit(EXIT_SUCCESS); } ASSERT_OK(r); r = reproc_drain(process, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); ASSERT_EQ_STR(output, MESSAGE); r = reproc_wait(process, REPROC_INFINITE); ASSERT_OK(r); reproc_destroy(process); reproc_free(output); } reproc-14.2.5/reproc/test/io.c000066400000000000000000000027461460055552200161360ustar00rootroot00000000000000#include #include #include "assert.h" #define MESSAGE "reproc stands for REdirected PROCess" static void io() { int r = -1; reproc_t *process = reproc_new(); ASSERT(process); const char *argv[] = { RESOURCE_DIRECTORY "/io", NULL }; r = reproc_start(process, argv, (reproc_options){ .redirect.err.type = REPROC_REDIRECT_STDOUT }); ASSERT_OK(r); r = reproc_write(process, (uint8_t *) MESSAGE, strlen(MESSAGE)); ASSERT_OK(r); ASSERT_EQ_INT(r, (int) strlen(MESSAGE)); r = reproc_close(process, REPROC_STREAM_IN); ASSERT_OK(r); char *out = NULL; r = reproc_drain(process, reproc_sink_string(&out), REPROC_SINK_NULL); ASSERT_OK(r); ASSERT(out != NULL); ASSERT_EQ_STR(out, MESSAGE MESSAGE); r = reproc_wait(process, REPROC_INFINITE); ASSERT_OK(r); reproc_destroy(process); reproc_free(out); } static void timeout(void) { int r = -1; reproc_t *process = reproc_new(); ASSERT(process); const char *argv[] = { RESOURCE_DIRECTORY "/io", NULL }; r = reproc_start(process, argv, (reproc_options){ 0 }); ASSERT_OK(r); reproc_event_source source = { process, REPROC_EVENT_OUT | REPROC_EVENT_ERR, 0 }; r = reproc_poll(&source, 1, 200); ASSERT(r == 0); r = reproc_close(process, REPROC_STREAM_IN); ASSERT_OK(r); r = reproc_poll(&source, 1, 200); ASSERT_OK(r); reproc_destroy(process); } int main(void) { io(); timeout(); } reproc-14.2.5/reproc/test/overflow.c000066400000000000000000000005321460055552200173610ustar00rootroot00000000000000#include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/overflow", NULL }; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; r = reproc_run_ex(argv, (reproc_options){ 0 }, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); reproc_free(output); } reproc-14.2.5/reproc/test/path.c000066400000000000000000000013331460055552200164520ustar00rootroot00000000000000#include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/path", NULL }; int r = -1; r = reproc_run(argv, (reproc_options){ .redirect.path = "path.txt" }); ASSERT_OK(r); FILE *file = fopen("path.txt", "rb"); ASSERT(file != NULL); r = fseek(file, 0, SEEK_END); ASSERT_OK(r); r = (int) ftell(file); ASSERT_OK(r); size_t size = (size_t) r; char *string = malloc(size + 1); ASSERT(string != NULL); rewind(file); r = (int) fread(string, sizeof(char), size, file); ASSERT_EQ_INT(r, (int) size); string[r] = '\0'; r = fclose(file); ASSERT_OK(r); r = remove("path.txt"); ASSERT_OK(r); ASSERT_EQ_STR(string, argv[0]); free(string); } reproc-14.2.5/reproc/test/pid.c000066400000000000000000000014121460055552200162700ustar00rootroot00000000000000#include #include #include #include #include "assert.h" int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/pid", NULL }; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; reproc_t *process = reproc_new(); ASSERT(process); ASSERT(reproc_pid(process) == REPROC_EINVAL); r = reproc_start(process, argv, (reproc_options){ 0 }); ASSERT_OK(r); r = reproc_drain(process, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); ASSERT(reproc_pid(process) == strtol(output, NULL, 10)); r = reproc_wait(process, REPROC_INFINITE); ASSERT_OK(r); ASSERT(reproc_pid(process) == strtol(output, NULL, 10)); reproc_destroy(process); reproc_free(output); } reproc-14.2.5/reproc/test/stop.c000066400000000000000000000011731460055552200165050ustar00rootroot00000000000000#include #include "assert.h" static void stop(REPROC_STOP action, int status) { int r = -1; reproc_t *process = reproc_new(); ASSERT(process); const char *argv[] = { RESOURCE_DIRECTORY "/stop", NULL }; r = reproc_start(process, argv, (reproc_options){ 0 }); ASSERT_OK(r); r = reproc_wait(process, 50); ASSERT(r == REPROC_ETIMEDOUT); reproc_stop_actions stop = { .first = { action, 500 } }; r = reproc_stop(process, stop); ASSERT_EQ_INT(r, status); reproc_destroy(process); } int main(void) { stop(REPROC_STOP_TERMINATE, REPROC_SIGTERM); stop(REPROC_STOP_KILL, REPROC_SIGKILL); } reproc-14.2.5/reproc/test/working-directory.c000066400000000000000000000012511460055552200211770ustar00rootroot00000000000000#include #include "assert.h" static void replace(char *string, char old, char new) { for (size_t i = 0; i < strlen(string); i++) { string[i] = (char) (string[i] == old ? new : string[i]); } } int main(void) { const char *argv[] = { RESOURCE_DIRECTORY "/working-directory", NULL }; char *output = NULL; reproc_sink sink = reproc_sink_string(&output); int r = -1; r = reproc_run_ex(argv, (reproc_options){ .working_directory = RESOURCE_DIRECTORY }, sink, sink); ASSERT_OK(r); ASSERT(output != NULL); replace(output, '\\', '/'); ASSERT_EQ_STR(output, RESOURCE_DIRECTORY); reproc_free(output); }