Meson config to use reproc as external library

This commit is contained in:
Francesco Abbate 2021-05-19 17:06:23 +02:00
parent 9b1b1a3820
commit a8e1e040ca
8 changed files with 11 additions and 3966 deletions

View File

@ -17,6 +17,15 @@ if not lua_dep.found()
endif endif
sdl_dep = dependency('sdl2', method: 'config-tool') 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 = [] lite_cargs = []
if get_option('portable') if get_option('portable')

View File

@ -8,11 +8,10 @@
#include <lua.h> #include <lua.h>
#include <lualib.h> #include <lualib.h>
#include <lauxlib.h> #include <lauxlib.h>
#include <reproc.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include "reproc.h"
typedef struct { typedef struct {
reproc_t * process; reproc_t * process;
lua_State* L; lua_State* L;

View File

@ -1,8 +1,3 @@
platform = 'posix'
if host_machine.system() == 'windows'
platform = 'windows'
endif
lite_sources = [ lite_sources = [
'api/api.c', 'api/api.c',
'api/cp_replace.c', 'api/cp_replace.c',
@ -11,8 +6,6 @@ lite_sources = [
'api/regex.c', 'api/regex.c',
'api/system.c', 'api/system.c',
'api/process.c', 'api/process.c',
'reproc.c',
'reproc.' + platform + '.c',
'renderer.c', 'renderer.c',
'renwindow.c', 'renwindow.c',
'fontdesc.c', 'fontdesc.c',
@ -27,7 +20,7 @@ endif
executable('lite', executable('lite',
lite_sources + lite_rc, lite_sources + lite_rc,
include_directories: [lite_include, font_renderer_include], include_directories: [lite_include, font_renderer_include],
dependencies: [lua_dep, sdl_dep, pcre2_dep, libm, libdl, libx11], dependencies: lite_deps,
c_args: lite_cargs, c_args: lite_cargs,
link_with: libfontrenderer, link_with: libfontrenderer,
link_args: lite_link_args, link_args: lite_link_args,

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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, &timespec);
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.

File diff suppressed because it is too large Load Diff

View File

@ -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);