Meson config to use reproc as external library
This commit is contained in:
parent
9b1b1a3820
commit
a8e1e040ca
|
@ -17,6 +17,15 @@ if not lua_dep.found()
|
|||
endif
|
||||
|
||||
sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||
reproc_dep = dependency('reproc')
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl, libx11]
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
# Note that we need to explicitly add the windows socket DLL because
|
||||
# the pkg-config file from reproc does not include it.
|
||||
lite_deps += meson.get_compiler('cpp').find_library('ws2_32', required: true)
|
||||
endif
|
||||
|
||||
lite_cargs = []
|
||||
if get_option('portable')
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <reproc.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "reproc.h"
|
||||
|
||||
typedef struct {
|
||||
reproc_t * process;
|
||||
lua_State* L;
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
platform = 'posix'
|
||||
if host_machine.system() == 'windows'
|
||||
platform = 'windows'
|
||||
endif
|
||||
|
||||
lite_sources = [
|
||||
'api/api.c',
|
||||
'api/cp_replace.c',
|
||||
|
@ -11,8 +6,6 @@ lite_sources = [
|
|||
'api/regex.c',
|
||||
'api/system.c',
|
||||
'api/process.c',
|
||||
'reproc.c',
|
||||
'reproc.' + platform + '.c',
|
||||
'renderer.c',
|
||||
'renwindow.c',
|
||||
'fontdesc.c',
|
||||
|
@ -27,7 +20,7 @@ endif
|
|||
executable('lite',
|
||||
lite_sources + lite_rc,
|
||||
include_directories: [lite_include, font_renderer_include],
|
||||
dependencies: [lua_dep, sdl_dep, pcre2_dep, libm, libdl, libx11],
|
||||
dependencies: lite_deps,
|
||||
c_args: lite_cargs,
|
||||
link_with: libfontrenderer,
|
||||
link_args: lite_link_args,
|
||||
|
|
1246
src/reproc.c
1246
src/reproc.c
File diff suppressed because it is too large
Load Diff
666
src/reproc.h
666
src/reproc.h
|
@ -1,666 +0,0 @@
|
|||
/**
|
||||
* @copyright Daan De Meyer
|
||||
* @license MIT
|
||||
* @link https://github.com/DaanDeMeyer/reproc Source code.
|
||||
* @note Amalgamation by Jefferson Gonzalez
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//
|
||||
// export.h
|
||||
//
|
||||
#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.h
|
||||
//
|
||||
#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
|
||||
|
||||
//
|
||||
// drain.h
|
||||
//
|
||||
#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
|
||||
|
||||
//
|
||||
// run.h
|
||||
//
|
||||
#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
|
|
@ -1,797 +0,0 @@
|
|||
/**
|
||||
* @copyright Daan De Meyer
|
||||
* @license MIT
|
||||
* @link https://github.com/DaanDeMeyer/reproc Source code.
|
||||
* @note Amalgamation by Jefferson Gonzalez
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "reproc_private.h"
|
||||
|
||||
//
|
||||
// clocl.posix.c
|
||||
//
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// error.posix.c
|
||||
//
|
||||
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];
|
||||
|
||||
int r = strerror_r(abs(error), string, ARRAY_SIZE(string));
|
||||
if (r != 0) {
|
||||
return "Failed to retrieve error string";
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
//
|
||||
// handle.posix.c
|
||||
//
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// init.posix.c
|
||||
//
|
||||
int init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void deinit(void) {}
|
||||
|
||||
//
|
||||
// pipe.posix.c
|
||||
//
|
||||
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);
|
||||
}
|
||||
|
||||
//
|
||||
// process.posix.c
|
||||
//
|
||||
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 = PATH_MAX;
|
||||
|
||||
// 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 += PATH_MAX;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// redirect.posix.c
|
||||
//
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// utf.posix.c
|
||||
//
|
||||
|
||||
// `utf16_from_utf8` is Windows-only.
|
1001
src/reproc.windows.c
1001
src/reproc.windows.c
File diff suppressed because it is too large
Load Diff
|
@ -1,246 +0,0 @@
|
|||
/**
|
||||
* @copyright Daan De Meyer
|
||||
* @license MIT
|
||||
* @link https://github.com/DaanDeMeyer/reproc Source code.
|
||||
* @note Amalgamation by Jefferson Gonzalez
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "reproc.h"
|
||||
|
||||
//
|
||||
// macro.h
|
||||
//
|
||||
#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
|
||||
|
||||
//
|
||||
// clock.h
|
||||
//
|
||||
int64_t now(void);
|
||||
|
||||
//
|
||||
// error.h
|
||||
//
|
||||
#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);
|
||||
|
||||
//
|
||||
// handle.h
|
||||
//
|
||||
#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);
|
||||
|
||||
//
|
||||
// init.h
|
||||
//
|
||||
int init(void);
|
||||
|
||||
void deinit(void);
|
||||
|
||||
//
|
||||
// options.h
|
||||
//
|
||||
reproc_stop_actions parse_stop_actions(reproc_stop_actions stop);
|
||||
|
||||
int parse_options(reproc_options *options, const char *const *argv);
|
||||
|
||||
//
|
||||
// pipe.h
|
||||
//
|
||||
#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);
|
||||
|
||||
//
|
||||
// process.h
|
||||
//
|
||||
#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);
|
||||
|
||||
//
|
||||
// redirect.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);
|
||||
|
||||
//
|
||||
// strv.h
|
||||
//
|
||||
#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);
|
||||
|
||||
//
|
||||
// utf.h
|
||||
//
|
||||
|
||||
// `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);
|
Loading…
Reference in New Issue