Implement PTY support
This commit is contained in:
parent
91ae02a63d
commit
aee382337c
6
.github/workflows/sanitizer.yml
vendored
6
.github/workflows/sanitizer.yml
vendored
@ -56,6 +56,12 @@ jobs:
|
||||
|
||||
sanitizers-macos:
|
||||
runs-on: macos-14
|
||||
env:
|
||||
# The sanitizer interferes with malloc memory preallocation causing a
|
||||
# harmless warning, which interferes with tests that assert program
|
||||
# output. This env var gets rid of the warning.
|
||||
# See https://stackoverflow.com/a/70209891
|
||||
MallocNanoZone: 0
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
|
||||
@ -92,7 +92,16 @@ Data types
|
||||
* search for the exact file name before trying variants with
|
||||
* extensions like '.exe' or '.cmd'.
|
||||
*/
|
||||
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7)
|
||||
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7),
|
||||
/*
|
||||
* Start subprocess with a pseudo terminal. To use this flag, set
|
||||
* stdio[0] to UV_CREATE_PIPE | UV_READABLE_PIPE and
|
||||
* stdio[1] to UV_CREATE_PIPE | UV_WRITABLE_PIPE and
|
||||
* stdio[2] to UV_IGNORE. The first pipe will be the PTY in (write here),
|
||||
* while the second pipe will be the PTY out (read here). The child will have
|
||||
* all standard FDs (0, 1 and 2) connected to the PTY as it should.
|
||||
*/
|
||||
UV_PROCESS_PTY = (1 << 8)
|
||||
};
|
||||
|
||||
.. c:type:: uv_stdio_container_t
|
||||
@ -264,7 +273,8 @@ API
|
||||
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.
|
||||
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.
|
||||
@ -272,6 +282,8 @@ API
|
||||
.. 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)
|
||||
|
||||
Sends the specified signal to the given process handle. Check the documentation
|
||||
@ -288,4 +300,12 @@ API
|
||||
|
||||
.. versionadded:: 1.19.0
|
||||
|
||||
.. c:function:: int uv_pty_resize(uv_process_t* process, unsigned short cols, unsigned short rows)
|
||||
|
||||
For a PTY enabled process, change the size of the terminal. Returns 0 on
|
||||
success, `UV_EINVAL` when the `process` is not PTY enabled, `UV_ENOTSUP`
|
||||
when the OS does not support PTYs.
|
||||
|
||||
.. versionadded:: 1.19.0
|
||||
|
||||
.. seealso:: The :c:type:`uv_handle_t` API functions also apply.
|
||||
|
||||
20
include/uv.h
20
include/uv.h
@ -1100,6 +1100,12 @@ typedef struct uv_process_options_s {
|
||||
*/
|
||||
int stdio_count;
|
||||
uv_stdio_container_t* stdio;
|
||||
/*
|
||||
* When starting the child with a PTY, these set the initial width and
|
||||
* height.
|
||||
*/
|
||||
unsigned int pty_cols;
|
||||
unsigned int pty_rows;
|
||||
/*
|
||||
* 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
|
||||
@ -1162,7 +1168,16 @@ enum uv_process_flags {
|
||||
* search for the exact file name before trying variants with
|
||||
* extensions like '.exe' or '.cmd'.
|
||||
*/
|
||||
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7)
|
||||
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7),
|
||||
/*
|
||||
* Start subprocess with a pseudo terminal. To use this flag, set
|
||||
* stdio[0] to UV_CREATE_PIPE | UV_READABLE_PIPE and
|
||||
* stdio[1] to UV_CREATE_PIPE | UV_WRITABLE_PIPE and
|
||||
* stdio[2] to UV_IGNORE. The first pipe will be the PTY in (write here),
|
||||
* while the second pipe will be the PTY out (read here). The child will have
|
||||
* all standard FDs (0, 1 and 2) connected to the PTY as it should.
|
||||
*/
|
||||
UV_PROCESS_PTY = (1 << 8)
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1178,6 +1193,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_pty_resize(uv_process_t* process,
|
||||
unsigned short cols,
|
||||
unsigned short rows);
|
||||
UV_EXTERN int uv_process_kill(uv_process_t*, int signum);
|
||||
UV_EXTERN int uv_kill(int pid, int signum);
|
||||
UV_EXTERN uv_pid_t uv_process_get_pid(const uv_process_t*);
|
||||
|
||||
@ -357,6 +357,7 @@ typedef struct {
|
||||
#define UV_PROCESS_PRIVATE_FIELDS \
|
||||
struct uv__queue queue; \
|
||||
int status; \
|
||||
int pty_fd;
|
||||
|
||||
#define UV_FS_PRIVATE_FIELDS \
|
||||
const char *new_path; \
|
||||
|
||||
@ -610,6 +610,7 @@ typedef struct {
|
||||
int exit_signal; \
|
||||
HANDLE wait_handle; \
|
||||
HANDLE process_handle; \
|
||||
HANDLE pty_handle; \
|
||||
volatile char exit_cb_pending;
|
||||
|
||||
#define UV_FS_PRIVATE_FIELDS \
|
||||
|
||||
@ -31,15 +31,17 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <termios.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
# include <spawn.h>
|
||||
# include <paths.h>
|
||||
# include <sys/kauth.h>
|
||||
# include <sys/types.h>
|
||||
# include <sys/sysctl.h>
|
||||
# include <dlfcn.h>
|
||||
# include <crt_externs.h>
|
||||
@ -287,7 +289,11 @@ static void uv__write_errno(int error_fd) {
|
||||
static void uv__process_child_init(const uv_process_options_t* options,
|
||||
int stdio_count,
|
||||
int (*pipes)[2],
|
||||
int error_fd) {
|
||||
int error_fd,
|
||||
int also_close_fd) {
|
||||
if (also_close_fd >= 0)
|
||||
uv__close(also_close_fd);
|
||||
|
||||
sigset_t signewset;
|
||||
int close_fd;
|
||||
int use_fd;
|
||||
@ -314,9 +320,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
|
||||
uv__write_errno(error_fd);
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_DETACHED)
|
||||
setsid();
|
||||
|
||||
/* First duplicate low numbered fds, since it's not safe to duplicate them,
|
||||
* they could get replaced. Example: swapping stdout and stderr; without
|
||||
* this fd 2 (stderr) would be duplicated into fd 1, thus making both
|
||||
@ -379,6 +382,20 @@ static void uv__process_child_init(const uv_process_options_t* options,
|
||||
uv__close(close_fd);
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
// Put ourself into a new session and process group, making us session
|
||||
// and process group leader.
|
||||
if (setsid() < 0) {
|
||||
uv__write_errno(error_fd);
|
||||
}
|
||||
|
||||
// Make our dear terminal the controlling terminal.
|
||||
if (ioctl(STDIN_FILENO, TIOCSCTTY) < 0)
|
||||
uv__write_errno(error_fd);
|
||||
}
|
||||
else if (options->flags & UV_PROCESS_DETACHED)
|
||||
setsid();
|
||||
|
||||
if (options->cwd != NULL && chdir(options->cwd))
|
||||
uv__write_errno(error_fd);
|
||||
|
||||
@ -820,7 +837,8 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
|
||||
int stdio_count,
|
||||
int (*pipes)[2],
|
||||
int error_fd,
|
||||
pid_t* pid) {
|
||||
pid_t* pid,
|
||||
int close_fd) {
|
||||
sigset_t signewset;
|
||||
sigset_t sigoldset;
|
||||
|
||||
@ -842,7 +860,7 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
|
||||
|
||||
if (*pid == 0) {
|
||||
/* Fork succeeded, in the child process */
|
||||
uv__process_child_init(options, stdio_count, pipes, error_fd);
|
||||
uv__process_child_init(options, stdio_count, pipes, error_fd, close_fd);
|
||||
abort();
|
||||
}
|
||||
|
||||
@ -862,7 +880,8 @@ static int uv__spawn_and_init_child(
|
||||
const uv_process_options_t* options,
|
||||
int stdio_count,
|
||||
int (*pipes)[2],
|
||||
pid_t* pid) {
|
||||
pid_t* pid,
|
||||
int close_fd) {
|
||||
int signal_pipe[2] = { -1, -1 };
|
||||
int status;
|
||||
int err;
|
||||
@ -870,33 +889,34 @@ static int uv__spawn_and_init_child(
|
||||
ssize_t r;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn);
|
||||
if (!(options->flags & UV_PROCESS_PTY)) {
|
||||
uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn);
|
||||
|
||||
/* Special child process spawn case for macOS Big Sur (11.0) onwards
|
||||
*
|
||||
* Big Sur introduced a significant performance degradation on a call to
|
||||
* fork/exec when the process has many pages mmaped in with MAP_JIT, like, say
|
||||
* a javascript interpreter. Electron-based applications, for example,
|
||||
* are impacted; though the magnitude of the impact depends on how much the
|
||||
* app relies on subprocesses.
|
||||
*
|
||||
* On macOS, though, posix_spawn is implemented in a way that does not
|
||||
* exhibit the problem. This block implements the forking and preparation
|
||||
* logic with posix_spawn and its related primitives. It also takes advantage of
|
||||
* the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to
|
||||
* leak descriptors to the child process. */
|
||||
err = uv__spawn_and_init_child_posix_spawn(options,
|
||||
stdio_count,
|
||||
pipes,
|
||||
pid,
|
||||
&posix_spawn_fncs);
|
||||
|
||||
/* The posix_spawn flow will return UV_ENOSYS if any of the posix_spawn_x_np
|
||||
* non-standard functions is both _needed_ and _undefined_. In those cases,
|
||||
* default back to the fork/execve strategy. For all other errors, just fail. */
|
||||
if (err != UV_ENOSYS)
|
||||
return err;
|
||||
/* Special child process spawn case for macOS Big Sur (11.0) onwards
|
||||
*
|
||||
* Big Sur introduced a significant performance degradation on a call to
|
||||
* fork/exec when the process has many pages mmaped in with MAP_JIT, like, say
|
||||
* a javascript interpreter. Electron-based applications, for example,
|
||||
* are impacted; though the magnitude of the impact depends on how much the
|
||||
* app relies on subprocesses.
|
||||
*
|
||||
* On macOS, though, posix_spawn is implemented in a way that does not
|
||||
* exhibit the problem. This block implements the forking and preparation
|
||||
* logic with posix_spawn and its related primitives. It also takes advantage of
|
||||
* the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to
|
||||
* leak descriptors to the child process. */
|
||||
err = uv__spawn_and_init_child_posix_spawn(options,
|
||||
stdio_count,
|
||||
pipes,
|
||||
pid,
|
||||
&posix_spawn_fncs);
|
||||
|
||||
/* The posix_spawn flow will return UV_ENOSYS if any of the posix_spawn_x_np
|
||||
* non-standard functions is both _needed_ and _undefined_. In those cases,
|
||||
* default back to the fork/execve strategy. For all other errors, just fail. */
|
||||
if (err != UV_ENOSYS)
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This pipe is used by the parent to wait until
|
||||
@ -926,7 +946,7 @@ static int uv__spawn_and_init_child(
|
||||
/* Acquire write lock to prevent opening new fds in worker threads */
|
||||
uv_rwlock_wrlock(&loop->cloexec_lock);
|
||||
|
||||
err = uv__spawn_and_init_child_fork(options, stdio_count, pipes, signal_pipe[1], pid);
|
||||
err = uv__spawn_and_init_child_fork(options, stdio_count, pipes, signal_pipe[1], pid, close_fd);
|
||||
|
||||
/* Release lock in parent process */
|
||||
uv_rwlock_wrunlock(&loop->cloexec_lock);
|
||||
@ -963,6 +983,74 @@ static int uv__spawn_and_init_child(
|
||||
}
|
||||
#endif /* ISN'T TARGET_OS_TV || TARGET_OS_WATCH */
|
||||
|
||||
int uv__pty_resize_fd(int pty_fd,
|
||||
unsigned short cols,
|
||||
unsigned short rows) {
|
||||
struct winsize winp;
|
||||
winp.ws_col = cols;
|
||||
winp.ws_row = rows;
|
||||
winp.ws_xpixel = 0;
|
||||
winp.ws_ypixel = 0;
|
||||
if (ioctl(pty_fd, TIOCSWINSZ, &winp) < 0)
|
||||
return UV__ERR(errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
if (*fd_pty < 0)
|
||||
return UV__ERR(errno);
|
||||
|
||||
if (grantpt(*fd_pty) < 0) {
|
||||
my_errno = UV__ERR(errno);
|
||||
close(*fd_pty);
|
||||
return my_errno;
|
||||
}
|
||||
|
||||
if (unlockpt(*fd_pty) < 0) {
|
||||
my_errno = UV__ERR(errno);
|
||||
close(*fd_pty);
|
||||
return my_errno;
|
||||
}
|
||||
|
||||
int path_tty_size = 40;
|
||||
char *path_tty = uv__malloc(path_tty_size * sizeof(char));
|
||||
// Apple and linux both have ptsname_r.
|
||||
// Use TIOCGPTPEER. (see man ioctl_tty) Where is that available?
|
||||
// There is no ptsname_r on OpenBSD.
|
||||
while ((ret = ptsname_r(*fd_pty, path_tty, path_tty_size)) == ERANGE) {
|
||||
path_tty_size *= 2;
|
||||
path_tty = uv__realloc(path_tty, path_tty_size * sizeof(char));
|
||||
}
|
||||
if (ret != 0) {
|
||||
my_errno = UV__ERR(errno);
|
||||
uv__free(path_tty);
|
||||
close(*fd_pty);
|
||||
return my_errno;
|
||||
}
|
||||
|
||||
*fd_tty = open(path_tty, O_RDWR | O_NOCTTY);
|
||||
if (*fd_tty < 0) {
|
||||
my_errno = UV__ERR(errno);
|
||||
uv__free(path_tty);
|
||||
close(*fd_pty);
|
||||
return my_errno;
|
||||
}
|
||||
|
||||
uv__free(path_tty);
|
||||
|
||||
if ((my_errno = uv__pty_resize_fd(*fd_pty, cols, rows)) != 0) {
|
||||
close(*fd_pty);
|
||||
close(*fd_tty);
|
||||
return my_errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uv_spawn(uv_loop_t* loop,
|
||||
uv_process_t* process,
|
||||
const uv_process_options_t* options) {
|
||||
@ -977,6 +1065,19 @@ int uv_spawn(uv_loop_t* loop,
|
||||
int err;
|
||||
int exec_errorno;
|
||||
int i;
|
||||
int fd_tty;
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
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 |
|
||||
options->pty_cols == 0)
|
||||
return UV_EINVAL;
|
||||
}
|
||||
|
||||
assert(options->file != NULL);
|
||||
assert(!(options->flags & ~(UV_PROCESS_DETACHED |
|
||||
@ -986,11 +1087,13 @@ int uv_spawn(uv_loop_t* loop,
|
||||
UV_PROCESS_WINDOWS_HIDE |
|
||||
UV_PROCESS_WINDOWS_HIDE_CONSOLE |
|
||||
UV_PROCESS_WINDOWS_HIDE_GUI |
|
||||
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
|
||||
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
|
||||
UV_PROCESS_PTY)));
|
||||
|
||||
uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS);
|
||||
uv__queue_init(&process->queue);
|
||||
process->status = 0;
|
||||
process->pty_fd = -1;
|
||||
|
||||
stdio_count = options->stdio_count;
|
||||
if (stdio_count < 3)
|
||||
@ -1009,18 +1112,33 @@ int uv_spawn(uv_loop_t* loop,
|
||||
pipes[i][1] = -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < options->stdio_count; i++) {
|
||||
for (i = (options->flags & UV_PROCESS_PTY) ? 3 : 0; i < options->stdio_count; i++) {
|
||||
err = uv__process_init_stdio(options->stdio + i, pipes[i]);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
if ((err = uv__spawn_make_pty(&process->pty_fd, &fd_tty, options->pty_cols, options->pty_rows)) != 0)
|
||||
goto error;
|
||||
|
||||
pipes[0][1] = fd_tty;
|
||||
pipes[1][1] = fd_tty;
|
||||
pipes[2][1] = fd_tty;
|
||||
pipes[0][0] = process->pty_fd;
|
||||
if ((pipes[1][0] = dup(process->pty_fd)) < 0) {
|
||||
err = UV__ERR(errno);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef UV_USE_SIGCHLD
|
||||
uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
|
||||
#endif
|
||||
|
||||
/* Spawn the child */
|
||||
exec_errorno = uv__spawn_and_init_child(loop, options, stdio_count, pipes, &pid);
|
||||
exec_errorno = uv__spawn_and_init_child(loop, options, stdio_count, pipes, &pid, process->pty_fd);
|
||||
|
||||
#if 0
|
||||
/* This runs into a nodejs issue (it expects initialized streams, even if the
|
||||
@ -1056,7 +1174,19 @@ int uv_spawn(uv_loop_t* loop,
|
||||
uv__handle_start(process);
|
||||
}
|
||||
|
||||
for (i = 0; i < options->stdio_count; i++) {
|
||||
// We could special case this (do it as part of the below for loop) if it's ok for the pipe to be non-blocking.
|
||||
// TODO: Validate this.
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
err = uv__close(fd_tty);
|
||||
|
||||
if ((err = uv_pipe_open((uv_pipe_t *)(options->stdio[0].data.stream), pipes[0][0])) != 0)
|
||||
printf("uv_pipe_open 0 ret: %i\n", err);
|
||||
|
||||
if ((err = uv_pipe_open((uv_pipe_t *)(options->stdio[1].data.stream), pipes[1][0])) != 0)
|
||||
printf("uv_pipe_open 1 ret: %i\n", err);
|
||||
}
|
||||
|
||||
for (i = (options->flags & UV_PROCESS_PTY) ? 3 : 0; i < options->stdio_count; i++) {
|
||||
err = uv__process_open_stream(options->stdio + i, pipes[i]);
|
||||
if (err == 0)
|
||||
continue;
|
||||
@ -1122,3 +1252,11 @@ void uv__process_close(uv_process_t* handle) {
|
||||
uv_signal_stop(&handle->loop->child_watcher);
|
||||
#endif
|
||||
}
|
||||
|
||||
int uv_pty_resize(uv_process_t* process,
|
||||
unsigned short cols,
|
||||
unsigned short rows) {
|
||||
if (process->pty_fd == -1)
|
||||
return UV_EINVAL;
|
||||
return uv__pty_resize_fd(process->pty_fd, cols, rows);
|
||||
}
|
||||
|
||||
@ -192,12 +192,20 @@ int uv__stdio_create(uv_loop_t* loop,
|
||||
/* Prepopulate the buffer with INVALID_HANDLE_VALUE handles so we can clean
|
||||
* up on failure. */
|
||||
CHILD_STDIO_COUNT(buffer) = count;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
CHILD_STDIO_CRT_FLAGS(buffer, i) = 0;
|
||||
memset(CHILD_STDIO_HANDLE(buffer, i), 0xFF, sizeof(HANDLE));
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
/* In PTY mode the STDIO handles are connected via a separate mechanism. Skip
|
||||
* them here. */
|
||||
if (options->flags & UV_PROCESS_PTY)
|
||||
i = 3;
|
||||
else
|
||||
i = 0;
|
||||
|
||||
for (; i < count; i++) {
|
||||
uv_stdio_container_t fdopt;
|
||||
if (i < options->stdio_count) {
|
||||
fdopt = options->stdio[i];
|
||||
|
||||
@ -38,6 +38,17 @@
|
||||
|
||||
#define SIGKILL 9
|
||||
|
||||
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
|
||||
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
|
||||
#endif
|
||||
|
||||
typedef VOID* HPCON;
|
||||
typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);
|
||||
typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
|
||||
typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
|
||||
typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
|
||||
typedef HRESULT (__stdcall *PFNRELEASEPSEUDOCONSOLE)(HPCON hpc);
|
||||
|
||||
typedef struct env_var {
|
||||
const WCHAR* const wide;
|
||||
@ -135,6 +146,7 @@ static void uv__process_init(uv_loop_t* loop, uv_process_t* handle) {
|
||||
handle->wait_handle = INVALID_HANDLE_VALUE;
|
||||
handle->process_handle = INVALID_HANDLE_VALUE;
|
||||
handle->exit_cb_pending = 0;
|
||||
handle->pty_handle = NULL;
|
||||
|
||||
UV_REQ_INIT(&handle->exit_req, UV_PROCESS_EXIT);
|
||||
handle->exit_req.data = handle;
|
||||
@ -814,6 +826,35 @@ static void CALLBACK exit_wait_callback(void* data, BOOLEAN didTimeout) {
|
||||
POST_COMPLETION_FOR_REQ(loop, &process->exit_req);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uv_work_t req;
|
||||
HANDLE pty_handle;
|
||||
} uv__pty_close_pty_work;
|
||||
|
||||
void pty_close_pty_cb(uv_work_t *req) {
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
// Error loading kernel32.dll: (error code %i)
|
||||
if (!hLibrary)
|
||||
return;
|
||||
// return uv_translate_sys_error(GetLastError());
|
||||
|
||||
PFNCLOSEPSEUDOCONSOLE pfnClose = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,"ClosePseudoConsole");
|
||||
if (!pfnClose)
|
||||
return;
|
||||
// int err = GetLastError();
|
||||
// if (err == ERROR_PROC_NOT_FOUND)
|
||||
// return UV_ENOTSUP;
|
||||
// else
|
||||
// return uv_translate_sys_error(err);
|
||||
|
||||
uv__pty_close_pty_work *close_req = (uv__pty_close_pty_work *)req->data;
|
||||
pfnClose(close_req->pty_handle);
|
||||
FreeLibrary(hLibrary);
|
||||
}
|
||||
|
||||
void pty_close_pty_cleanup_cb(uv_work_t *req, int status) {
|
||||
uv__free(req);
|
||||
}
|
||||
|
||||
/* Called on main thread after a child process has exited. */
|
||||
void uv__process_proc_exit(uv_loop_t* loop, uv_process_t* handle) {
|
||||
@ -847,13 +888,19 @@ void uv__process_proc_exit(uv_loop_t* loop, uv_process_t* handle) {
|
||||
exit_code = uv_translate_sys_error(GetLastError());
|
||||
}
|
||||
|
||||
if (handle->pty_handle != NULL) {
|
||||
uv__pty_close_pty_work *close_pty_work = (uv__pty_close_pty_work*)uv__malloc(sizeof(uv__pty_close_pty_work));
|
||||
close_pty_work->req.data = (void*)close_pty_work;
|
||||
close_pty_work->pty_handle = handle->pty_handle;
|
||||
uv_queue_work(loop, &(close_pty_work->req), &pty_close_pty_cb, &pty_close_pty_cleanup_cb);
|
||||
}
|
||||
|
||||
/* Fire the exit callback. */
|
||||
if (handle->exit_cb) {
|
||||
handle->exit_cb(handle, exit_code, handle->exit_signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void uv__process_close(uv_loop_t* loop, uv_process_t* handle) {
|
||||
uv__handle_closing(handle);
|
||||
|
||||
@ -874,7 +921,6 @@ void uv__process_close(uv_loop_t* loop, uv_process_t* handle) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void uv__process_endgame(uv_loop_t* loop, uv_process_t* handle) {
|
||||
assert(!handle->exit_cb_pending);
|
||||
assert(handle->flags & UV_HANDLE_CLOSING);
|
||||
@ -886,17 +932,187 @@ void uv__process_endgame(uv_loop_t* loop, uv_process_t* handle) {
|
||||
uv__handle_close(handle);
|
||||
}
|
||||
|
||||
int uv_pty_resize(uv_process_t* process,
|
||||
unsigned short cols,
|
||||
unsigned short rows) {
|
||||
if (process->pty_handle == NULL)
|
||||
return UV_EINVAL;
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
// Error loading kernel32.dll: (error code %i)
|
||||
if (!hLibrary)
|
||||
return uv_translate_sys_error(GetLastError());
|
||||
|
||||
PFNRESIZEPSEUDOCONSOLE pfnResize = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,"ResizePseudoConsole");
|
||||
if (!pfnResize) {
|
||||
int err = GetLastError();
|
||||
if (err == ERROR_PROC_NOT_FOUND)
|
||||
return UV_ENOTSUP;
|
||||
else
|
||||
return uv_translate_sys_error(err);
|
||||
}
|
||||
|
||||
COORD size = {cols, rows};
|
||||
HRESULT hr = pfnResize(process->pty_handle, size);
|
||||
// Failed to resize PTY device: (error code %i)
|
||||
if (FAILED(hr))
|
||||
return uv_translate_sys_error(GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uv_work_t req;
|
||||
HANDLE forward_handle;
|
||||
ssize_t nread;
|
||||
char *buf;
|
||||
} uv__pty_in_forward_data;
|
||||
|
||||
typedef struct {
|
||||
HANDLE pty_in_write;
|
||||
uv_process_t *process;
|
||||
} uv__pty_stream_forward_data;
|
||||
|
||||
static void pty_stdin_forward_alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
|
||||
buf->base = (char*)uv__malloc(suggested_size);
|
||||
buf->len = suggested_size;
|
||||
}
|
||||
|
||||
/* Forwarding PTY STDIN.
|
||||
* Step 2: We're on a separate thread now. Retrieve the data to be written and
|
||||
* write to the sync pipe.
|
||||
*/
|
||||
void pty_stdin_forward_write_cb(uv_work_t *req) {
|
||||
int err = 0;
|
||||
uv__pty_in_forward_data *forward_data = (uv__pty_in_forward_data *)req->data;
|
||||
DWORD nwritten;
|
||||
if (!WriteFile(forward_data->forward_handle, forward_data->buf, forward_data->nread, &nwritten, NULL)) {
|
||||
err = GetLastError();
|
||||
// Can't do much about the error sadly.
|
||||
}
|
||||
}
|
||||
|
||||
void pty_stdin_forward_after_write_cb(uv_work_t *req, int status) {
|
||||
uv__pty_in_forward_data *forward_data = (uv__pty_in_forward_data *)req->data;
|
||||
uv__free(forward_data);
|
||||
}
|
||||
|
||||
/* Forwarding PTY STDIN.
|
||||
* Step 1: Read the async pipe and call uv_queue_work to run the rest on a
|
||||
* separate thread.
|
||||
*/
|
||||
void pty_stdin_forward_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
|
||||
uv__pty_stream_forward_data *stream_forward_data = (uv__pty_stream_forward_data*)stream->data;
|
||||
if (stream_forward_data->process->flags & UV_HANDLE_CLOSED || nread == UV_EOF) {
|
||||
uv_read_stop(stream);
|
||||
uv_close((uv_handle_t*)stream, NULL);
|
||||
CloseHandle(stream_forward_data->pty_in_write);
|
||||
uv__free(stream_forward_data);
|
||||
}
|
||||
else if (nread > 0) {
|
||||
uv__pty_in_forward_data *forward_data = (uv__pty_in_forward_data*)uv__malloc(sizeof(uv__pty_in_forward_data));
|
||||
forward_data->req.data = (void*)forward_data;
|
||||
forward_data->nread = nread;
|
||||
|
||||
forward_data->buf = (char*)uv__malloc(sizeof(char) * nread);
|
||||
memcpy(forward_data->buf, buf->base, nread);
|
||||
|
||||
forward_data->forward_handle = stream_forward_data->pty_in_write;
|
||||
|
||||
uv_queue_work(stream->loop, &(forward_data->req), &pty_stdin_forward_write_cb, &pty_stdin_forward_after_write_cb);
|
||||
}
|
||||
|
||||
if (buf->base != NULL) {
|
||||
uv__free(buf->base);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uv_work_t req;
|
||||
DWORD nread;
|
||||
char *buf;
|
||||
HANDLE pty_out_read;
|
||||
uv_pipe_t out_write;
|
||||
} uv__pty_out_data;
|
||||
const int out_read_buf_len = 512;
|
||||
|
||||
void pty_stdout_read_cb(uv_work_t *req) {
|
||||
int err = 0;
|
||||
uv__pty_out_data *out_data = (uv__pty_out_data *)req->data;
|
||||
out_data->nread = 0;
|
||||
|
||||
if (!ReadFile(out_data->pty_out_read, out_data->buf, out_read_buf_len, &(out_data->nread), NULL)) {
|
||||
err = GetLastError();
|
||||
if (err != ERROR_BROKEN_PIPE) {
|
||||
// Can't do much about the error sadly.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pty_stdout_after_write_cb(uv_write_t *req, int status) {
|
||||
uv__free(req->data);
|
||||
uv__free(req);
|
||||
}
|
||||
|
||||
void pty_free_out_data(uv_handle_t *handle) {
|
||||
uv__free(handle->data);
|
||||
}
|
||||
|
||||
void pty_stdout_after_read_cb(uv_work_t *req, int status) {
|
||||
int err = 0;
|
||||
uv__pty_out_data *out_data = (uv__pty_out_data *)req->data;
|
||||
if (out_data->nread > 0) {
|
||||
uv_buf_t *buf = (uv_buf_t*)uv__malloc(sizeof(uv_buf_t));
|
||||
buf->base = (char*)uv__malloc(sizeof(char) * out_data->nread);
|
||||
buf->len = out_data->nread;
|
||||
memcpy(buf->base, out_data->buf, out_data->nread);
|
||||
uv_write_t *write_req = (uv_write_t*)uv__malloc(sizeof(uv_write_t));
|
||||
write_req->data = (void*)buf;
|
||||
|
||||
uv_write(write_req, (uv_stream_t*)&(out_data->out_write), buf, 1, pty_stdout_after_write_cb);
|
||||
|
||||
uv_queue_work(req->loop, &(out_data->req), &pty_stdout_read_cb, &pty_stdout_after_read_cb);
|
||||
}
|
||||
else if (out_data->nread == 0) {
|
||||
CloseHandle(out_data->pty_out_read);
|
||||
out_data->out_write.data = (void*)out_data;
|
||||
uv__free(out_data->buf);
|
||||
uv_close((uv_handle_t*)&(out_data->out_write), &pty_free_out_data);
|
||||
}
|
||||
else {
|
||||
// Can't do much about the error sadly.
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/microsoft/terminal/blob/c4fbb58f69b0a5cc86c245505311d0a0b3cc1399/src/winconpty/winconpty.cpp#L532-L559
|
||||
typedef struct {
|
||||
HANDLE hSignal;
|
||||
HANDLE hPtyReference;
|
||||
HANDLE hConPtyProcess;
|
||||
} uv__pseudo_console;
|
||||
HRESULT uv__release_pseudo_console(HPCON hPC) {
|
||||
uv__pseudo_console *pPty = (uv__pseudo_console*)hPC;
|
||||
if (pPty == NULL)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (pPty->hPtyReference != INVALID_HANDLE_VALUE && pPty->hPtyReference != NULL) {
|
||||
CloseHandle(pPty->hPtyReference);
|
||||
pPty->hPtyReference = NULL;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
int uv_spawn(uv_loop_t* loop,
|
||||
uv_process_t* process,
|
||||
const uv_process_options_t* options) {
|
||||
int i;
|
||||
int err = 0;
|
||||
|
||||
WCHAR* path = NULL, *alloc_path = NULL;
|
||||
BOOL result;
|
||||
WCHAR* application_path = NULL, *application = NULL, *arguments = NULL,
|
||||
*env = NULL, *cwd = NULL;
|
||||
STARTUPINFOW startup;
|
||||
STARTUPINFOEXW startupex;
|
||||
ZeroMemory(&startupex, sizeof(startupex));
|
||||
PROCESS_INFORMATION info;
|
||||
DWORD process_flags, cwd_len;
|
||||
BYTE* child_stdio_buffer;
|
||||
@ -914,6 +1130,21 @@ int uv_spawn(uv_loop_t* loop,
|
||||
return UV_EINVAL;
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
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->flags & (UV_PROCESS_WINDOWS_HIDE |
|
||||
UV_PROCESS_WINDOWS_HIDE_CONSOLE))
|
||||
return UV_EINVAL;
|
||||
if (options->pty_rows == 0 ||
|
||||
options->pty_cols == 0)
|
||||
return UV_EINVAL;
|
||||
}
|
||||
|
||||
assert(options->file != NULL);
|
||||
assert(!(options->flags & ~(UV_PROCESS_DETACHED |
|
||||
UV_PROCESS_SETGID |
|
||||
@ -922,7 +1153,8 @@ int uv_spawn(uv_loop_t* loop,
|
||||
UV_PROCESS_WINDOWS_HIDE |
|
||||
UV_PROCESS_WINDOWS_HIDE_CONSOLE |
|
||||
UV_PROCESS_WINDOWS_HIDE_GUI |
|
||||
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
|
||||
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
|
||||
UV_PROCESS_PTY)));
|
||||
|
||||
err = uv__utf8_to_utf16_alloc(options->file, &application);
|
||||
if (err)
|
||||
@ -1002,10 +1234,6 @@ int uv_spawn(uv_loop_t* loop,
|
||||
}
|
||||
}
|
||||
|
||||
err = uv__stdio_create(loop, options, &child_stdio_buffer);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
application_path = search_path(application,
|
||||
cwd,
|
||||
path,
|
||||
@ -1016,18 +1244,123 @@ int uv_spawn(uv_loop_t* loop,
|
||||
goto done;
|
||||
}
|
||||
|
||||
startup.cb = sizeof(startup);
|
||||
startup.lpReserved = NULL;
|
||||
startup.lpDesktop = NULL;
|
||||
startup.lpTitle = NULL;
|
||||
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
startupex.StartupInfo.cb = sizeof(startupex);
|
||||
startupex.StartupInfo.lpReserved = NULL;
|
||||
startupex.StartupInfo.lpDesktop = NULL;
|
||||
startupex.StartupInfo.lpTitle = NULL;
|
||||
startupex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
||||
|
||||
startup.cbReserved2 = uv__stdio_size(child_stdio_buffer);
|
||||
startup.lpReserved2 = (BYTE*) child_stdio_buffer;
|
||||
err = uv__stdio_create(loop, options, &child_stdio_buffer);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
startup.hStdInput = uv__stdio_handle(child_stdio_buffer, 0);
|
||||
startup.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1);
|
||||
startup.hStdError = uv__stdio_handle(child_stdio_buffer, 2);
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
if (!hLibrary) {
|
||||
// Error loading kernel32.dll: (error code %i)
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
PFNCREATEPSEUDOCONSOLE pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,"CreatePseudoConsole");
|
||||
if (!pfnCreate) {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_PROC_NOT_FOUND) {
|
||||
err = UV_ENOTSUP;
|
||||
goto done_uv;
|
||||
}
|
||||
else {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forwarding PTY STDIN.
|
||||
* We read an asynchronous pipe and in a separate thread write to a
|
||||
* synchronous pipe. We need to do this, because older versions of ConPTY
|
||||
* do not support async pipes.
|
||||
*/
|
||||
uv_pipe_t* in_write_pipe = (uv_pipe_t*) options->stdio[0].data.stream;
|
||||
HANDLE in_read = INVALID_HANDLE_VALUE;
|
||||
err = uv__create_stdio_pipe_pair(loop,
|
||||
in_write_pipe,
|
||||
&in_read,
|
||||
options->stdio[0].flags);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
int in_read_fd = uv_open_osfhandle(in_read);
|
||||
uv_pipe_t* in_read_pipe = (uv_pipe_t*)uv__malloc(sizeof(uv_pipe_t));
|
||||
uv_pipe_init(loop, in_read_pipe, 0);
|
||||
uv_pipe_open(in_read_pipe, in_read_fd);
|
||||
|
||||
HANDLE pty_in_read = INVALID_HANDLE_VALUE;
|
||||
HANDLE pty_in_write = INVALID_HANDLE_VALUE;
|
||||
if (!CreatePipe(&pty_in_read, &pty_in_write, NULL, 0)) {
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
uv__pty_stream_forward_data* stream_forward_data = (void*)uv__malloc(sizeof(uv__pty_stream_forward_data));
|
||||
stream_forward_data->pty_in_write = pty_in_write;
|
||||
stream_forward_data->process = process;
|
||||
in_read_pipe->data = (void*)stream_forward_data;
|
||||
|
||||
uv_read_start((uv_stream_t*)in_read_pipe, pty_stdin_forward_alloc_cb, pty_stdin_forward_read_cb);
|
||||
|
||||
/* Forwarding PTY STDOUT
|
||||
*/
|
||||
uv__pty_out_data *out_data = (uv__pty_out_data*)uv__malloc(sizeof(uv__pty_out_data));
|
||||
out_data->req.data = (void*)out_data;
|
||||
out_data->buf = (char*)uv__malloc(sizeof(char) * out_read_buf_len);
|
||||
|
||||
uv_pipe_t* out_read_pipe = (uv_pipe_t*) options->stdio[1].data.stream;
|
||||
HANDLE out_write = INVALID_HANDLE_VALUE;
|
||||
err = uv__create_stdio_pipe_pair(loop,
|
||||
out_read_pipe,
|
||||
&out_write,
|
||||
options->stdio[1].flags);
|
||||
if (err)
|
||||
goto done;
|
||||
int out_write_fd = uv_open_osfhandle(out_write);
|
||||
uv_pipe_init(loop, &(out_data->out_write), 0);
|
||||
uv_pipe_open(&(out_data->out_write), out_write_fd);
|
||||
|
||||
HANDLE pty_out_write = INVALID_HANDLE_VALUE;
|
||||
out_data->pty_out_read = INVALID_HANDLE_VALUE;
|
||||
if (!CreatePipe(&(out_data->pty_out_read), &pty_out_write, NULL, 0)) {
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
uv_queue_work(loop, &(out_data->req), &pty_stdout_read_cb, &pty_stdout_after_read_cb);
|
||||
|
||||
/* Now setup the PTY object
|
||||
*/
|
||||
COORD size = {options->pty_cols, options->pty_rows};
|
||||
|
||||
HRESULT hr = pfnCreate(size, pty_in_read, pty_out_write, 0, &process->pty_handle);
|
||||
if (FAILED(hr)) {
|
||||
// Failed to create PTY device: (error code %i)
|
||||
err = hr;
|
||||
goto done;
|
||||
}
|
||||
|
||||
FreeLibrary(hLibrary);
|
||||
CloseHandle(pty_in_read);
|
||||
CloseHandle(pty_out_write);
|
||||
|
||||
startupex.StartupInfo.hStdInput = NULL;
|
||||
startupex.StartupInfo.hStdOutput = NULL;
|
||||
startupex.StartupInfo.hStdError = NULL;
|
||||
}
|
||||
else {
|
||||
startupex.StartupInfo.hStdInput = uv__stdio_handle(child_stdio_buffer, 0);
|
||||
startupex.StartupInfo.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1);
|
||||
startupex.StartupInfo.hStdError = uv__stdio_handle(child_stdio_buffer, 2);
|
||||
}
|
||||
|
||||
startupex.StartupInfo.cbReserved2 = uv__stdio_size(child_stdio_buffer);
|
||||
startupex.StartupInfo.lpReserved2 = (BYTE*) child_stdio_buffer;
|
||||
|
||||
process_flags = CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
@ -1044,9 +1377,9 @@ int uv_spawn(uv_loop_t* loop,
|
||||
if ((options->flags & UV_PROCESS_WINDOWS_HIDE_GUI) ||
|
||||
(options->flags & UV_PROCESS_WINDOWS_HIDE)) {
|
||||
/* Use SW_HIDE to avoid any potential process window. */
|
||||
startup.wShowWindow = SW_HIDE;
|
||||
startupex.StartupInfo.wShowWindow = SW_HIDE;
|
||||
} else {
|
||||
startup.wShowWindow = SW_SHOWDEFAULT;
|
||||
startupex.StartupInfo.wShowWindow = SW_SHOWDEFAULT;
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_DETACHED) {
|
||||
@ -1064,15 +1397,41 @@ int uv_spawn(uv_loop_t* loop,
|
||||
process_flags |= CREATE_SUSPENDED;
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
process_flags |= EXTENDED_STARTUPINFO_PRESENT;
|
||||
size_t attr_list_size;
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &attr_list_size);
|
||||
startupex.lpAttributeList = uv__malloc(attr_list_size);
|
||||
|
||||
if (!InitializeProcThreadAttributeList(startupex.lpAttributeList, 1, 0,
|
||||
&attr_list_size)) {
|
||||
// Failed to init proc thread attribute list. (error code %i)
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!UpdateProcThreadAttribute(startupex.lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
process->pty_handle,
|
||||
sizeof(process->pty_handle),
|
||||
NULL,
|
||||
NULL)) {
|
||||
// Failed to update proc thread attribute list. (error code %i)
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CreateProcessW(application_path,
|
||||
arguments,
|
||||
NULL,
|
||||
NULL,
|
||||
1,
|
||||
TRUE,
|
||||
process_flags,
|
||||
env,
|
||||
cwd,
|
||||
&startup,
|
||||
&startupex.StartupInfo,
|
||||
&info)) {
|
||||
/* CreateProcessW failed. */
|
||||
err = GetLastError();
|
||||
@ -1109,6 +1468,31 @@ int uv_spawn(uv_loop_t* loop,
|
||||
}
|
||||
}
|
||||
|
||||
if (options->flags & UV_PROCESS_PTY) {
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
if (!hLibrary) {
|
||||
// Error loading kernel32.dll: (error code %i)
|
||||
err = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
PFNRELEASEPSEUDOCONSOLE pfnRelease = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,"ReleasePseudoConsole");
|
||||
if (!pfnRelease) {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_PROC_NOT_FOUND) {
|
||||
err = 0;
|
||||
uv__release_pseudo_console(process->pty_handle);
|
||||
}
|
||||
else {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else {
|
||||
pfnRelease(process->pty_handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Spawn succeeded. Beyond this point, failure is reported asynchronously. */
|
||||
|
||||
process->process_handle = info.hProcess;
|
||||
@ -1154,6 +1538,7 @@ int uv_spawn(uv_loop_t* loop,
|
||||
uv__free(cwd);
|
||||
uv__free(env);
|
||||
uv__free(alloc_path);
|
||||
uv__free(startupex.lpAttributeList);
|
||||
|
||||
if (child_stdio_buffer != NULL) {
|
||||
/* Clean up child stdio handles. */
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
# include <io.h>
|
||||
# define read _read
|
||||
#else
|
||||
@ -247,6 +248,49 @@ static int maybe_run_test(int argc, char **argv) {
|
||||
spawn_stdin_stdout();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(argv[1], "spawn_helper10") == 0) {
|
||||
char buffer[256];
|
||||
int cols, rows;
|
||||
uv_tty_t tty;
|
||||
notify_parent_process();
|
||||
uv_tty_init(uv_default_loop(), &tty, 1, 0);
|
||||
|
||||
ASSERT_PTR_EQ(buffer, fgets(buffer, sizeof(buffer) - 1, stdin));
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
|
||||
ASSERT_OK(uv_tty_get_winsize(&tty, &cols, &rows));
|
||||
#ifdef _WIN32
|
||||
printf("Is a TTY: %s\nWinSize: %ix%i\nRead: %s\n", _isatty(_fileno(stdin)) ? "true" : "false", cols, rows, buffer);
|
||||
#else
|
||||
printf("Is a TTY: %s\nWinSize: %ix%i\nRead: %s\n", isatty(STDIN_FILENO) ? "true" : "false", cols, rows, buffer);
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(argv[1], "spawn_helper11") == 0) {
|
||||
char inbuffer[256];
|
||||
char outbuffer[256];
|
||||
notify_parent_process();
|
||||
|
||||
ASSERT_PTR_EQ(inbuffer, fgets(inbuffer, sizeof(inbuffer) - 1, stdin));
|
||||
inbuffer[sizeof(inbuffer) - 1] = '\0';
|
||||
|
||||
#ifdef _WIN32
|
||||
snprintf(outbuffer, sizeof(outbuffer), "Is a TTY: %s\nRead: %s\n", _isatty(_fileno(stdin)) ? "true" : "false", inbuffer);
|
||||
DWORD bytes;
|
||||
WriteFile((HANDLE) _get_osfhandle(3), outbuffer, sizeof(outbuffer) - 1, &bytes, NULL);
|
||||
#else
|
||||
snprintf(outbuffer, sizeof(outbuffer), "Is a TTY: %s\nRead: %s\n", isatty(STDIN_FILENO) ? "true" : "false", inbuffer);
|
||||
ssize_t r;
|
||||
do
|
||||
r = write(3, outbuffer, sizeof(outbuffer) - 1);
|
||||
while (r == -1 && errno == EINTR);
|
||||
fsync(3);
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
if (strcmp(argv[1], "spawn_helper_setuid_setgid") == 0) {
|
||||
|
||||
@ -346,6 +346,9 @@ TEST_DECLARE (spawn_quoted_path)
|
||||
TEST_DECLARE (spawn_tcp_server)
|
||||
TEST_DECLARE (spawn_exercise_sigchld_issue)
|
||||
TEST_DECLARE (spawn_relative_path)
|
||||
TEST_DECLARE (spawn_pty_setup_succeeds)
|
||||
TEST_DECLARE (spawn_pty_stdio_greater_than_3)
|
||||
TEST_DECLARE (spawn_pty_resize)
|
||||
TEST_DECLARE (fs_poll)
|
||||
TEST_DECLARE (fs_poll_getpath)
|
||||
TEST_DECLARE (fs_poll_close_request)
|
||||
@ -1031,6 +1034,9 @@ TASK_LIST_START
|
||||
TEST_ENTRY (spawn_tcp_server)
|
||||
TEST_ENTRY (spawn_exercise_sigchld_issue)
|
||||
TEST_ENTRY (spawn_relative_path)
|
||||
TEST_ENTRY (spawn_pty_setup_succeeds)
|
||||
TEST_ENTRY (spawn_pty_stdio_greater_than_3)
|
||||
TEST_ENTRY (spawn_pty_resize)
|
||||
TEST_ENTRY (fs_poll)
|
||||
TEST_ENTRY (fs_poll_getpath)
|
||||
TEST_ENTRY (fs_poll_close_request)
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <synchapi.h>
|
||||
# include <shellapi.h>
|
||||
# include <wchar.h>
|
||||
typedef BOOL (WINAPI *sCompareObjectHandles)(_In_ HANDLE, _In_ HANDLE);
|
||||
@ -52,6 +53,7 @@ static char* args[5];
|
||||
static int no_term_signal;
|
||||
static int timer_counter;
|
||||
static uv_tcp_t tcp_server;
|
||||
uv_pipe_t pty_in;
|
||||
|
||||
#define OUTPUT_SIZE 1024
|
||||
static char output[OUTPUT_SIZE];
|
||||
@ -59,20 +61,30 @@ static int output_used;
|
||||
|
||||
|
||||
static void close_cb(uv_handle_t* handle) {
|
||||
printf("close_cb\n");
|
||||
close_cb_called++;
|
||||
}
|
||||
|
||||
static void exit_cb(uv_process_t* process,
|
||||
int64_t exit_status,
|
||||
int term_signal) {
|
||||
printf("exit_cb\n");
|
||||
exit_cb_called++;
|
||||
ASSERT_EQ(1, exit_status);
|
||||
ASSERT_OK(term_signal);
|
||||
uv_close((uv_handle_t*) process, close_cb);
|
||||
}
|
||||
|
||||
static void pty_exit_cb(uv_process_t* process,
|
||||
int64_t exit_status,
|
||||
int term_signal) {
|
||||
exit_cb_called++;
|
||||
ASSERT_EQ(1, exit_status);
|
||||
ASSERT_OK(term_signal);
|
||||
uv_close((uv_handle_t*) process, close_cb);
|
||||
#ifdef _WIN32
|
||||
uv_close((uv_handle_t*)&pty_in, close_cb);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void fail_cb(uv_process_t* process,
|
||||
int64_t exit_status,
|
||||
@ -86,7 +98,6 @@ static void kill_cb(uv_process_t* process,
|
||||
int term_signal) {
|
||||
int err;
|
||||
|
||||
printf("exit_cb\n");
|
||||
exit_cb_called++;
|
||||
#ifdef _WIN32
|
||||
ASSERT_EQ(1, exit_status);
|
||||
@ -117,7 +128,6 @@ static void kill_cb(uv_process_t* process,
|
||||
static void detach_failure_cb(uv_process_t* process,
|
||||
int64_t exit_status,
|
||||
int term_signal) {
|
||||
printf("detach_cb\n");
|
||||
exit_cb_called++;
|
||||
}
|
||||
|
||||
@ -138,18 +148,36 @@ static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) {
|
||||
}
|
||||
}
|
||||
|
||||
static void pty_on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) {
|
||||
if (nread > 0) {
|
||||
output_used += nread;
|
||||
} else if (nread < 0) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
ASSERT_EQ(nread, UV_EOF);
|
||||
#elif defined(__ANDROID__)
|
||||
ASSERT(nread == UV_EIO || nread == UV_EOF);
|
||||
#else
|
||||
ASSERT_EQ(nread, UV_EIO);
|
||||
#endif
|
||||
uv_close((uv_handle_t*) tcp, close_cb);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_read_once(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) {
|
||||
uv_read_stop(tcp);
|
||||
on_read(tcp, nread, buf);
|
||||
}
|
||||
|
||||
|
||||
static void write_cb(uv_write_t* req, int status) {
|
||||
ASSERT_OK(status);
|
||||
uv_close((uv_handle_t*) req->handle, close_cb);
|
||||
}
|
||||
|
||||
static void pty_write_cb(uv_write_t* req, int status) {
|
||||
ASSERT_OK(status);
|
||||
#ifndef _WIN32
|
||||
uv_close((uv_handle_t*) req->handle, close_cb);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void write_null_cb(uv_write_t* req, int status) {
|
||||
ASSERT_OK(status);
|
||||
@ -183,6 +211,35 @@ static void timer_counter_cb(uv_timer_t* handle) {
|
||||
++timer_counter;
|
||||
}
|
||||
|
||||
void filter_vt(char *t, char with_nl_and_sp) {
|
||||
int wi = 0, len = strlen(t), csi = 0, osc = 0;
|
||||
for (int ri = 0; ri < len; ri++) {
|
||||
if (with_nl_and_sp && t[ri] == 13 && t[ri+1] == 10) {
|
||||
ri++;
|
||||
}
|
||||
else if (with_nl_and_sp && t[ri] == ' ') {
|
||||
// skip
|
||||
}
|
||||
else if (csi && 64 <= t[ri] && t[ri] <= 126) {
|
||||
csi--;
|
||||
}
|
||||
else if (osc && t[ri] == 7) {
|
||||
osc--;
|
||||
}
|
||||
else if (t[ri] == 27 && t[ri+1] == '[') { // ESC [ == CSI
|
||||
ri++;
|
||||
csi++;
|
||||
}
|
||||
else if (t[ri] == 27 && t[ri+1] == ']') { // ESC ] == OSC
|
||||
ri++;
|
||||
osc++;
|
||||
}
|
||||
else if (!csi && !osc) {
|
||||
t[wi++] = t[ri];
|
||||
}
|
||||
}
|
||||
t[wi] = 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(spawn_fails) {
|
||||
int r;
|
||||
@ -2111,3 +2168,214 @@ TEST_IMPL(spawn_relative_path) {
|
||||
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(spawn_pty_setup_succeeds) {
|
||||
int r;
|
||||
uv_pipe_t pty_out;
|
||||
uv_write_t write_req;
|
||||
uv_buf_t buf;
|
||||
uv_stdio_container_t stdio[3];
|
||||
#ifdef _WIN32
|
||||
char buffer[] = "hello from parent\r\n";
|
||||
#else
|
||||
char buffer[] = "hello from parent\n";
|
||||
#endif
|
||||
|
||||
init_process_options("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;
|
||||
|
||||
r = uv_spawn(uv_default_loop(), &process, &options);
|
||||
ASSERT_OK(r);
|
||||
|
||||
// We don't want to write the trailing \0
|
||||
buf = uv_buf_init(buffer, sizeof(buffer) - 1);
|
||||
|
||||
r = uv_write(&write_req, (uv_stream_t*) &pty_in, &buf, 1, pty_write_cb);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_read_start((uv_stream_t*) &pty_out, on_alloc, pty_on_read);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
ASSERT_OK(r);
|
||||
|
||||
ASSERT_EQ(1, exit_cb_called);
|
||||
ASSERT_EQ(3, close_cb_called); /* Once for process twice for the pipes. */
|
||||
|
||||
/* We see the "hello from parent" string twice. The first is the terminal echo,
|
||||
* the second is the reply from the child.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
/* We're trimming VT sequences that ConPTY sprinkles the output with. Also
|
||||
* we're allowing trailing and leading stuff, because ConPTY sometimes
|
||||
* adds '\r\n's at the start followed by a 'ESC[H' which moves the cursor
|
||||
* back to the start, effectively a no-op.
|
||||
*/
|
||||
filter_vt(output, 0);
|
||||
printf("Output: %s\n", output);
|
||||
ASSERT_PTR_NE(strstr(output, "hello from parent\r\nIs a TTY: true\r\nWinSize: 72x24\r\nRead: hello from parent"), NULL);
|
||||
#else
|
||||
printf("Output: %s\n", output);
|
||||
ASSERT_OK(strcmp("hello from parent\r\nIs a TTY: true\r\nWinSize: 72x24\r\nRead: hello from parent\r\n\r\n", output));
|
||||
#endif
|
||||
|
||||
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(spawn_pty_stdio_greater_than_3) {
|
||||
int r;
|
||||
uv_pipe_t pty_out, pipe4;
|
||||
uv_write_t write_req;
|
||||
uv_buf_t buf;
|
||||
uv_stdio_container_t stdio[4];
|
||||
#ifdef _WIN32
|
||||
char buffer[] = "hello from parent\r\n";
|
||||
#else
|
||||
char buffer[] = "hello from parent\n";
|
||||
#endif
|
||||
|
||||
init_process_options("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;
|
||||
|
||||
r = uv_spawn(uv_default_loop(), &process, &options);
|
||||
ASSERT_OK(r);
|
||||
|
||||
// We don't want to write the trailing \0
|
||||
buf = uv_buf_init(buffer, sizeof(buffer) - 1);
|
||||
|
||||
r = uv_write(&write_req, (uv_stream_t*) &pty_in, &buf, 1, pty_write_cb);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_read_start((uv_stream_t*) &pipe4, on_alloc, on_read);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
ASSERT_OK(r);
|
||||
|
||||
ASSERT_EQ(1, exit_cb_called);
|
||||
ASSERT_EQ(3, close_cb_called); /* Once for process twice for the pipes. */
|
||||
|
||||
printf("Output: %s\n", output);
|
||||
#ifdef _WIN32
|
||||
ASSERT_OK(strcmp("Is a TTY: true\nRead: hello from parent\r\n\n", output));
|
||||
#else
|
||||
ASSERT_OK(strcmp("Is a TTY: true\nRead: hello from parent\n\n", output));
|
||||
#endif
|
||||
|
||||
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(spawn_pty_resize) {
|
||||
int r;
|
||||
uv_pipe_t pty_out;
|
||||
uv_write_t write_req;
|
||||
uv_buf_t buf;
|
||||
uv_stdio_container_t stdio[3];
|
||||
#ifdef _WIN32
|
||||
char buffer[] = "hello from parent\r\n";
|
||||
#else
|
||||
char buffer[] = "hello from parent\n";
|
||||
#endif
|
||||
|
||||
init_process_options("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;
|
||||
|
||||
r = uv_spawn(uv_default_loop(), &process, &options);
|
||||
ASSERT_OK(r);
|
||||
|
||||
// Workaround for https://github.com/microsoft/terminal/pull/10449.
|
||||
// On older (Windows 10) ConPTY versions, the resize can be lost if it
|
||||
// happens shortly after process startup.
|
||||
#ifdef _WIN32
|
||||
Sleep(250);
|
||||
#endif
|
||||
|
||||
r = uv_pty_resize(&process, 10, 15);
|
||||
ASSERT_OK(r);
|
||||
|
||||
// We don't want to write the trailing \0
|
||||
buf = uv_buf_init(buffer, sizeof(buffer) - 1);
|
||||
|
||||
r = uv_write(&write_req, (uv_stream_t*) &pty_in, &buf, 1, pty_write_cb);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_read_start((uv_stream_t*) &pty_out, on_alloc, pty_on_read);
|
||||
ASSERT_OK(r);
|
||||
|
||||
r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
ASSERT_OK(r);
|
||||
|
||||
ASSERT_EQ(1, exit_cb_called);
|
||||
ASSERT_EQ(3, close_cb_called); /* Once for process twice for the pipes. */
|
||||
|
||||
/* We see the "hello from parent" string twice. The first is the terminal echo,
|
||||
* the second is the reply from the child.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
/* We're trimming VT sequences that ConPTY sprinkles the output with. Also
|
||||
* we're allowing trailing and leading stuff, because ConPTY sometimes
|
||||
* adds '\r\n's at the start followed by a 'ESC[H' which moves the cursor
|
||||
* back to the start, effectively a no-op.
|
||||
*/
|
||||
|
||||
/* In addition older ConPTY versions (Windows 10) emit newlines when the
|
||||
* terminal width is reached and put multiple spaces at the end of lines.
|
||||
* so we'll filter out spaces and newlines as well. This abomination can be
|
||||
* removed again, once Windows 10 support is dropped.
|
||||
*/
|
||||
filter_vt(output, 1);
|
||||
printf("Output: %s\n", output);
|
||||
ASSERT_PTR_NE(strstr(output, "hellofromparentIsaTTY:trueWinSize:10x15Read:hellofromparent"), NULL);
|
||||
// ASSERT_PTR_NE(strstr(output, "hello from parent\r\nIs a TTY: true\r\nWinSize: 10x15\r\nRead: hello from parent"), NULL);
|
||||
#else
|
||||
printf("Output: %s\n", output);
|
||||
ASSERT_OK(strcmp("hello from parent\r\nIs a TTY: true\r\nWinSize: 10x15\r\nRead: hello from parent\r\n\r\n", output));
|
||||
#endif
|
||||
|
||||
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user