ABI compat: Don't change the size of uv_process_options_t

Do so by introducing a `uv_spawn2` and `uv_process_options2_t`.
This commit is contained in:
Patrick Böker 2025-11-04 23:17:43 +01:00
parent d066cc1698
commit 74f6de3848
9 changed files with 244 additions and 73 deletions

View File

@ -167,8 +167,8 @@ API
Create a pair of connected pipe handles.
Data may be written to `fds[1]` and read from `fds[0]`.
The resulting handles can be passed to `uv_pipe_open`, used with `uv_spawn`,
or for any other purpose.
The resulting handles can be passed to `uv_pipe_open`, used with
`uv_spawn` / `uv_spawn2` or for any other purpose.
Valid values for `flags` are:

View File

@ -17,6 +17,8 @@ Data types
.. c:type:: uv_process_options_t
Deprecated, use `uv_spawn2` with `uv_process_options2_t` instead.
Options for spawning the process (passed to :c:func:`uv_spawn`.
::
@ -34,15 +36,37 @@ Data types
uv_gid_t gid;
} uv_process_options_t;
.. c:type:: uv_process_options2_t
Options for spawning the process (passed to :c:func:`uv_spawn2`.
::
typedef struct uv_process_options2_s {
int version;
uv_exit_cb exit_cb;
const char* file;
char** args;
char** env;
const char* cwd;
unsigned int flags;
int stdio_count;
uv_stdio_container_t* stdio;
unsigned int pty_cols;
unsigned int pty_rows;
uv_uid_t uid;
uv_gid_t gid;
} uv_process_options2_t;
.. c:type:: void (*uv_exit_cb)(uv_process_t*, int64_t exit_status, int term_signal)
Type definition for callback passed in :c:type:`uv_process_options_t` which
Type definition for callback passed in :c:type:`uv_process_options2_t` which
will indicate the exit status and the signal that caused the process to
terminate, if any.
.. c:enum:: uv_process_flags
Flags to be set on the flags field of :c:type:`uv_process_options_t`.
Flags to be set on the flags field of :c:type:`uv_process_options2_t`.
::
@ -88,7 +112,7 @@ Data types
UV_PROCESS_WINDOWS_HIDE_GUI = (1 << 6),
/*
* On Windows, if the path to the program to execute, specified in
* uv_process_options_t's file field, has a directory component,
* uv_process_options2_t's file field, has a directory component,
* search for the exact file name before trying variants with
* extensions like '.exe' or '.cmd'.
*/
@ -186,36 +210,40 @@ Public members
.. note::
The :c:type:`uv_handle_t` members also apply.
.. c:member:: uv_exit_cb uv_process_options_t.exit_cb
.. c:member:: uv_exit_cb uv_process_options2_t.version
Version of the struct. Simply set it to UV_PROCESS_OPTIONS_VERSION.
.. c:member:: uv_exit_cb uv_process_options2_t.exit_cb
Callback called after the process exits.
.. c:member:: const char* uv_process_options_t.file
.. c:member:: const char* uv_process_options2_t.file
Path pointing to the program to be executed.
.. c:member:: char** uv_process_options_t.args
.. c:member:: char** uv_process_options2_t.args
Command line arguments. args[0] should be the path to the program. On
Windows this uses `CreateProcess` which concatenates the arguments into a
string this can cause some strange errors. See the
``UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS`` flag on :c:enum:`uv_process_flags`.
.. c:member:: char** uv_process_options_t.env
.. c:member:: char** uv_process_options2_t.env
Environment for the new process. If NULL the parents environment is used.
.. c:member:: const char* uv_process_options_t.cwd
.. c:member:: const char* uv_process_options2_t.cwd
Current working directory for the subprocess.
.. c:member:: unsigned int uv_process_options_t.flags
.. c:member:: unsigned int uv_process_options2_t.flags
Various flags that control how :c:func:`uv_spawn` behaves. See
:c:enum:`uv_process_flags`.
.. c:member:: int uv_process_options_t.stdio_count
.. c:member:: uv_stdio_container_t* uv_process_options_t.stdio
.. c:member:: int uv_process_options2_t.stdio_count
.. c:member:: uv_stdio_container_t* uv_process_options2_t.stdio
The `stdio` field points to an array of :c:type:`uv_stdio_container_t`
structs that describe the file descriptors that will be made available to
@ -226,8 +254,14 @@ Public members
On Windows file descriptors greater than 2 are available to the child process only if
the child processes uses the MSVCRT runtime.
.. c:member:: uv_uid_t uv_process_options_t.uid
.. c:member:: uv_gid_t uv_process_options_t.gid
.. c:member:: uv_gid_t uv_process_options2_t.pty_cols
.. c:member:: uv_uid_t uv_process_options2_t.pty_rows
When installing a PTY via the `UV_PROCESS_PTY` flag, set the initial
pseudo terminal dimensions via these fields.
.. c:member:: uv_uid_t uv_process_options2_t.uid
.. c:member:: uv_gid_t uv_process_options2_t.gid
Libuv can change the child process' user/group id. This happens only when
the appropriate bits are set in the flags fields.
@ -265,6 +299,27 @@ API
.. c:function:: int uv_spawn(uv_loop_t* loop, uv_process_t* handle, const uv_process_options_t* options)
Deprecated. Use `uv_spawn2`. `uv_spawn2` behaves identical, but
takes a `uv_processs_options2_t` instead.
Initializes the process handle and starts the process. If the process is
successfully spawned, this function will return 0. Otherwise, the
negative error code corresponding to the reason it couldn't spawn is
returned.
Possible reasons for failing to spawn would include (but not be limited to)
the file to execute not existing, not having permissions to use the setuid or
setgid specified, or not having enough memory to allocate for the new
process.
.. versionchanged:: 1.24.0 Added `UV_PROCESS_WINDOWS_HIDE_CONSOLE` and
`UV_PROCESS_WINDOWS_HIDE_GUI` flags.
.. versionchanged:: 1.48.0 Added the
`UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME` flag.
.. c:function:: int uv_spawn2(uv_loop_t* loop, uv_process_t* handle, const uv_process_options2_t* options)
Initializes the process handle and starts the process. If the process is
successfully spawned, this function will return 0. Otherwise, the
negative error code corresponding to the reason it couldn't spawn is
@ -276,13 +331,10 @@ API
process. If the OS does not support PTYs and `UV_PROCESS_PTY` is passed
the error will be `UV_ENOTSUP`.
.. versionchanged:: 1.24.0 Added `UV_PROCESS_WINDOWS_HIDE_CONSOLE` and
`UV_PROCESS_WINDOWS_HIDE_GUI` flags.
.. versionadded:: 1.52.0 Identical to `uv_spawn` but takes a
`uv_process_options2_t` instead. With `uv_spawn2` it's
now possible to use a PTY via the `UV_PROCESS_PTY` flag.
.. versionchanged:: 1.48.0 Added the
`UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME` flag.
.. versionchanged:: 1.51.0 Added the `UV_PROCESS_PTY` flag.
.. c:function:: int uv_process_kill(uv_process_t* handle, int signum)
@ -306,6 +358,6 @@ API
success, `UV_EINVAL` when the `process` is not PTY enabled, `UV_ENOTSUP`
when the OS does not support PTYs.
.. versionadded:: 1.19.0
.. versionadded:: 1.52.0
.. seealso:: The :c:type:`uv_handle_t` API functions also apply.

View File

@ -1065,6 +1065,59 @@ typedef struct uv_stdio_container_s {
} uv_stdio_container_t;
typedef struct uv_process_options_s {
uv_exit_cb exit_cb; /* Called after the process exits. */
const char* file; /* Path to program to execute. */
/*
* Command line arguments. args[0] should be the path to the program. On
* Windows this uses CreateProcess which concatenates the arguments into a
* string this can cause some strange errors. See the note at
* windows_verbatim_arguments.
*/
char** args;
/*
* This will be set as the environ variable in the subprocess. If this is
* NULL then the parents environ will be used.
*/
char** env;
/*
* If non-null this represents a directory the subprocess should execute
* in. Stands for current working directory.
*/
const char* cwd;
/*
* Various flags that control how uv_spawn() behaves. See the definition of
* `enum uv_process_flags` below.
*/
unsigned int flags;
/*
* The `stdio` field points to an array of uv_stdio_container_t structs that
* describe the file descriptors that will be made available to the child
* process. The convention is that stdio[0] points to stdin, fd 1 is used for
* stdout, and fd 2 is stderr.
*
* Note that on windows file descriptors greater than 2 are available to the
* child process only if the child processes uses the MSVCRT runtime.
*/
int stdio_count;
uv_stdio_container_t* stdio;
/*
* Libuv can change the child process' user/group id. This happens only when
* the appropriate bits are set in the flags fields. This is not supported on
* windows; uv_spawn() will fail and set the error to UV_ENOTSUP.
*/
uv_uid_t uid;
uv_gid_t gid;
} uv_process_options_t;
#define UV_PROCESS_OPTIONS_VERSION_V0 0 /* Same as original uv_process_options_t */
#define UV_PROCESS_OPTIONS_VERSION_V1 1 /* With pty fields */
#define UV_PROCESS_OPTIONS_VERSION UV_PROCESS_OPTIONS_VERSION_V1
typedef struct uv_process_options2_s {
/* Struct version. Simply set it to UV_PROCESS_OPTIONS_VERSION. */
int version;
uv_exit_cb exit_cb; /* Called after the process exits. */
const char* file; /* Path to program to execute. */
/*
@ -1113,7 +1166,7 @@ typedef struct uv_process_options_s {
*/
uv_uid_t uid;
uv_gid_t gid;
} uv_process_options_t;
} uv_process_options2_t;
/*
* These are the flags that can be used for the uv_process_options.flags field.
@ -1193,6 +1246,9 @@ struct uv_process_s {
UV_EXTERN int uv_spawn(uv_loop_t* loop,
uv_process_t* handle,
const uv_process_options_t* options);
UV_EXTERN int uv_spawn2(uv_loop_t* loop,
uv_process_t* handle,
const uv_process_options2_t* options);
UV_EXTERN int uv_pty_resize(uv_process_t* process,
unsigned short cols,
unsigned short rows);

View File

@ -293,7 +293,7 @@ static void uv__write_errno(int error_fd) {
}
static void uv__process_child_init(const uv_process_options_t* options,
static void uv__process_child_init(const uv_process_options2_t* options,
int stdio_count,
int (*pipes)[2],
int error_fd,
@ -494,7 +494,7 @@ static void uv__spawn_init_posix_spawn(void) {
static int uv__spawn_set_posix_spawn_attrs(
posix_spawnattr_t* attrs,
const uv__posix_spawn_fncs_t* posix_spawn_fncs,
const uv_process_options_t* options) {
const uv_process_options2_t* options) {
int err;
unsigned int flags;
sigset_t signal_set;
@ -566,7 +566,7 @@ error:
static int uv__spawn_set_posix_spawn_file_actions(
posix_spawn_file_actions_t* actions,
const uv__posix_spawn_fncs_t* posix_spawn_fncs,
const uv_process_options_t* options,
const uv_process_options2_t* options,
int stdio_count,
int (*pipes)[2]) {
int fd;
@ -700,7 +700,7 @@ char* uv__spawn_find_path_in_env(char** env) {
}
static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options,
static int uv__spawn_resolve_and_spawn(const uv_process_options2_t* options,
posix_spawnattr_t* attrs,
posix_spawn_file_actions_t* actions,
pid_t* pid) {
@ -801,7 +801,7 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options,
static int uv__spawn_and_init_child_posix_spawn(
const uv_process_options_t* options,
const uv_process_options2_t* options,
int stdio_count,
int (*pipes)[2],
pid_t* pid,
@ -841,7 +841,7 @@ error:
}
#endif
static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
static int uv__spawn_and_init_child_fork(const uv_process_options2_t* options,
int stdio_count,
int (*pipes)[2],
int error_fd,
@ -885,7 +885,7 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
static int uv__spawn_and_init_child(
uv_loop_t* loop,
const uv_process_options_t* options,
const uv_process_options2_t* options,
int stdio_count,
int (*pipes)[2],
pid_t* pid,
@ -1022,7 +1022,6 @@ int uv__spawn_make_pty(int* fd_pty, int* fd_tty, int cols, int rows) {
}
#else
int uv__spawn_make_pty(int* fd_pty, int* fd_tty, int cols, int rows) {
int ret;
int my_errno;
*fd_pty = posix_openpt(O_RDWR);
@ -1064,6 +1063,27 @@ int uv__spawn_make_pty(int* fd_pty, int* fd_tty, int cols, int rows) {
int uv_spawn(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options_t* options) {
uv_process_options2_t options2;
options2.version = UV_PROCESS_OPTIONS_VERSION_V0;
options2.exit_cb = options->exit_cb;
options2.file = options->file;
options2.args = options->args;
options2.env = options->env;
options2.cwd = options->cwd;
options2.flags = options->flags;
options2.stdio_count = options->stdio_count;
options2.stdio = options->stdio;
options2.pty_cols = 0;
options2.pty_rows = 0;
options2.uid = options->uid;
options2.gid = options->gid;
return uv_spawn2(loop, process, &options2);
}
int uv_spawn2(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options2_t* options) {
#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)
/* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */
return UV_ENOSYS;
@ -1078,13 +1098,15 @@ int uv_spawn(uv_loop_t* loop,
int fd_tty;
if (options->flags & UV_PROCESS_PTY) {
if (options->version < UV_PROCESS_OPTIONS_VERSION_V1)
return UV_EINVAL;
if (options->stdio[0].flags != (UV_CREATE_PIPE | UV_READABLE_PIPE) ||
options->stdio[0].data.stream->type != UV_NAMED_PIPE ||
options->stdio[1].flags != (UV_CREATE_PIPE | UV_WRITABLE_PIPE) ||
options->stdio[1].data.stream->type != UV_NAMED_PIPE ||
options->stdio[2].flags != UV_IGNORE)
return UV_EINVAL;
if (options->pty_rows == 0 |
if (options->pty_rows == 0 ||
options->pty_cols == 0)
return UV_EINVAL;
}

View File

@ -276,7 +276,7 @@ int uv__random_winrandom(void* buf, size_t buflen);
* Process stdio handles.
*/
int uv__stdio_create(uv_loop_t* loop,
const uv_process_options_t* options,
const uv_process_options2_t* options,
BYTE** buffer_ptr);
void uv__stdio_destroy(BYTE* buffer);
void uv__stdio_noinherit(BYTE* buffer);

View File

@ -167,7 +167,7 @@ int uv__create_nul_handle(HANDLE* handle_ptr,
int uv__stdio_create(uv_loop_t* loop,
const uv_process_options_t* options,
const uv_process_options2_t* options,
BYTE** buffer_ptr) {
BYTE* buffer;
int count, i;

View File

@ -1104,6 +1104,27 @@ HRESULT uv__release_pseudo_console(HPCON hPC) {
int uv_spawn(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options_t* options) {
uv_process_options2_t options2;
options2.version = UV_PROCESS_OPTIONS_VERSION_V0;
options2.exit_cb = options->exit_cb;
options2.file = options->file;
options2.args = options->args;
options2.env = options->env;
options2.cwd = options->cwd;
options2.flags = options->flags;
options2.stdio_count = options->stdio_count;
options2.stdio = options->stdio;
options2.pty_cols = 0;
options2.pty_rows = 0;
options2.uid = options->uid;
options2.gid = options->gid;
return uv_spawn2(loop, process, &options2);
}
int uv_spawn2(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options2_t* options) {
int i;
int err = 0;
@ -1131,6 +1152,8 @@ int uv_spawn(uv_loop_t* loop,
}
if (options->flags & UV_PROCESS_PTY) {
if (options->version < UV_PROCESS_OPTIONS_VERSION_V1)
return UV_EINVAL;
if (options->stdio[0].flags != (UV_CREATE_PIPE | UV_READABLE_PIPE) ||
options->stdio[0].data.stream->type != UV_NAMED_PIPE ||
options->stdio[1].flags != (UV_CREATE_PIPE | UV_WRITABLE_PIPE) ||

View File

@ -268,7 +268,7 @@ static int maybe_run_test(int argc, char **argv) {
return 1;
}
if (strcmp(argv[1], "spawn_helper11") == 0) {
char inbuffer[256];
char inbuffer[200];
char outbuffer[256];
notify_parent_process();

View File

@ -47,6 +47,7 @@ static int exit_cb_called;
static uv_process_t process;
static uv_timer_t timer;
static uv_process_options_t options;
static uv_process_options2_t options2;
static char exepath[1024];
static size_t exepath_size = 1024;
static char* args[5];
@ -205,6 +206,23 @@ static void init_process_options(char* test, uv_exit_cb exit_cb) {
options.flags = 0;
}
static void init_process_options2(char* test, uv_exit_cb exit_cb) {
/* Note spawn_helper1 defined in test/run-tests.c */
int r = uv_exepath(exepath, &exepath_size);
ASSERT_OK(r);
exepath[exepath_size] = '\0';
args[0] = exepath;
args[1] = test;
args[2] = NULL;
args[3] = NULL;
args[4] = NULL;
options2.version = UV_PROCESS_OPTIONS_VERSION;
options2.file = exepath;
options2.args = args;
options2.exit_cb = exit_cb;
options2.flags = 0;
}
static void timer_cb(uv_timer_t* handle) {
uv_process_kill(&process, SIGTERM);
@ -2186,23 +2204,23 @@ TEST_IMPL(spawn_pty_setup_succeeds) {
char buffer[] = "hello from parent\n";
#endif
init_process_options("spawn_helper10", pty_exit_cb);
init_process_options2("spawn_helper10", pty_exit_cb);
uv_pipe_init(uv_default_loop(), &pty_in, 0);
uv_pipe_init(uv_default_loop(), &pty_out, 0);
options.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options.stdio[2].flags = UV_IGNORE;
options.stdio_count = 3;
options.pty_cols = 72;
options.pty_rows = 24;
options2.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options2.stdio = stdio;
options2.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options2.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options2.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options2.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options2.stdio[2].flags = UV_IGNORE;
options2.stdio_count = 3;
options2.pty_cols = 72;
options2.pty_rows = 24;
r = uv_spawn(uv_default_loop(), &process, &options);
r = uv_spawn2(uv_default_loop(), &process, &options2);
ASSERT_OK(r);
// We don't want to write the trailing \0
@ -2253,26 +2271,26 @@ TEST_IMPL(spawn_pty_stdio_greater_than_3) {
char buffer[] = "hello from parent\n";
#endif
init_process_options("spawn_helper11", pty_exit_cb);
init_process_options2("spawn_helper11", pty_exit_cb);
uv_pipe_init(uv_default_loop(), &pty_in, 0);
uv_pipe_init(uv_default_loop(), &pty_out, 0);
uv_pipe_init(uv_default_loop(), &pipe4, 0);
options.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options.stdio[2].flags = UV_IGNORE;
options.stdio[3].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options.stdio[3].data.stream = (uv_stream_t*) &pipe4;
options.stdio_count = 4;
options.pty_cols = 72;
options.pty_rows = 24;
options2.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options2.stdio = stdio;
options2.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options2.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options2.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options2.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options2.stdio[2].flags = UV_IGNORE;
options2.stdio[3].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options2.stdio[3].data.stream = (uv_stream_t*) &pipe4;
options2.stdio_count = 4;
options2.pty_cols = 72;
options2.pty_rows = 24;
r = uv_spawn(uv_default_loop(), &process, &options);
r = uv_spawn2(uv_default_loop(), &process, &options2);
ASSERT_OK(r);
// We don't want to write the trailing \0
@ -2313,23 +2331,23 @@ TEST_IMPL(spawn_pty_resize) {
char buffer[] = "hello from parent\n";
#endif
init_process_options("spawn_helper10", pty_exit_cb);
init_process_options2("spawn_helper10", pty_exit_cb);
uv_pipe_init(uv_default_loop(), &pty_in, 0);
uv_pipe_init(uv_default_loop(), &pty_out, 0);
options.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options.stdio[2].flags = UV_IGNORE;
options.stdio_count = 3;
options.pty_cols = 72;
options.pty_rows = 24;
options2.flags |= UV_PROCESS_PTY | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
options2.stdio = stdio;
options2.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options2.stdio[0].data.stream = (uv_stream_t*) &pty_in;
options2.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
options2.stdio[1].data.stream = (uv_stream_t*) &pty_out;
options2.stdio[2].flags = UV_IGNORE;
options2.stdio_count = 3;
options2.pty_cols = 72;
options2.pty_rows = 24;
r = uv_spawn(uv_default_loop(), &process, &options);
r = uv_spawn2(uv_default_loop(), &process, &options2);
ASSERT_OK(r);
// Workaround for https://github.com/microsoft/terminal/pull/10449.