Add *pty API (#3221)

* Initial function signatur and struct for `RzPty` API

* Change type annotation to borrow

* Implement `rz_subprocess_login_tty`

* Add fork mode enum and move pty struct; restructure ifdef guards

* Add rough implementation of forkpty

  * Add `rz_sys` versions for `forkpty` family of functions

* Add forkpty code; Make changes according to review

* Implement forkpty properly and add unit test

  * Add `test_interactive_pty`

* Add documentation

* Remove const in function argument

  * Apparently, MacOS as non-const arguments for `openpty` and `forkpty`

* Add more tests for subprocess forkin using forkpty

  * `test_interactive_pipe_pty`: Create pipes between the slave's PTY
    and master and use them
  * `test_interactive_custom_pty`: Use a custom PTY for the slave

* Add PTY functions exist checks for unit tests

* Move terminal param code inside the forkpty branch

* Add comment about why we need to check `n_bytes = 0` in `subprocess_wait`

* Remove forkpty with a specified `RzPTY`

  * Also, no piping done in forkpty

* Add checks to disallow using `forkpty` with piping

* Make changes according to review

  * No need to check proc file descriptors for -1 in `forkpty` mode, since
    they *will* be -1
  * No need to check `n_bytes == 0` in for `std{out,err}_pty`

* Extract pipe initialization code in `init_pipes` function

* Allow using custom PTY for `rz_subprocess_forkpty`

* Remove const qualifiers for termios and winsize arguments

  * This makes the build pass on MacOS, which does accepts non-const
    arguments

* Change `pid_t` to int so that it compiles on Windows

* Add empty implementation for PTY API for Windows

  * Update function signature for `rz_subprocess_start_opt` for Windows
  * Re-add `#define` for `pid_t` in `librz/socket/run.c`

* Move functions to remove duplicate definitions

* Allow specifying piping options even in `forkpty` mode

    * Only expose the master fd for the streams which have pipe create
      as their piping option

* Add comment about not using `master_fd` directly

* Fix types in definition for Windows

* Free the allocated `RzPTY` object

* Add const qualifiers for `RzPTY` argument

* Assign `fork_mode` and `pty` in all instances of subprocess opt start

    * for good measure

* Compare master fd with -1, not 0

* Remove stray file

* Add second comment about the terminal attribute

* Overhaul the whole PTY implementation

    * Now the caller can either use a pipe or PTY to send and receive
      data from any of the streams
    * Extra option `make_raw` to use the slave PTY in raw mode
    * Add documentation for all the new functions

* Fix UAF bug

* Add error handling for `tcgetattr`

* Move the term param setting code

* Add documentation for the subprocess API and sys API

* Format according to clang-format
This commit is contained in:
Dhruv Maroo 2022-12-29 18:08:39 +05:30 committed by GitHub
parent 276306b587
commit d20f68d7ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 552 additions and 68 deletions

View file

@ -16,7 +16,9 @@ typedef enum rz_subprocess_pipe_create_t {
RZ_SUBPROCESS_PIPE_NONE,
///< A new pipe should be created. It can be used for stdin, stdout and stderr.
RZ_SUBPROCESS_PIPE_CREATE,
///< Re-use the same pipe as stdout. It can be used for stderr only.
///< A new PTY should be created. It can be used for stdin, stdout and stderr.
RZ_SUBPROCESS_PIPE_PTY,
///< Re-use the same pipe/PTY as stdout. It can be used for stderr only.
RZ_SUBPROCESS_PIPE_STDOUT,
} RzSubprocessPipeCreate;
@ -33,6 +35,11 @@ typedef enum rz_process_wait_reason_t {
RZ_SUBPROCESS_BYTESREAD,
} RzSubprocessWaitReason;
typedef enum rz_subprocess_fork_mode_t {
RZ_SUBPROCESS_FORK,
RZ_SUBPROCESS_FORKPTY,
} RzSubprocessForkMode;
/**
* Provide results from running a sub-process, like output, return value, etc.
*/
@ -51,6 +58,12 @@ typedef struct rz_process_output_t {
bool timeout;
} RzSubprocessOutput;
typedef struct rz_pty_t {
int master_fd;
int slave_fd;
char *name;
} RzPty;
/**
* Specify how the new subprocess should be created.
*/
@ -73,6 +86,14 @@ typedef struct rz_subprocess_opt_t {
RzSubprocessPipeCreate stdout_pipe;
///< Specify how to deal with subprocess stderr
RzSubprocessPipeCreate stderr_pipe;
/* Both the following fields only matter when using PTY in the pipe options */
///< Provide the PTY to use (if NULL, then a new one will be created, if required)
///< No need to close the PTY once it has been used, you can free it directly
RzPty *pty;
///< Use raw mode for the created PTY (disable echo, control characters, etc.)
///< If you don't know what to put here, put true here.
bool make_raw;
} RzSubprocessOpt;
typedef struct rz_subprocess_t RzSubprocess;
@ -82,7 +103,7 @@ RZ_API void rz_subprocess_fini(void);
RZ_API RzSubprocess *rz_subprocess_start(
const char *file, const char *args[], size_t args_size,
const char *envvars[], const char *envvals[], size_t env_size);
RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt);
RZ_API RZ_OWN RzSubprocess *rz_subprocess_start_opt(RZ_NONNULL const RzSubprocessOpt *opt);
RZ_API void rz_subprocess_free(RzSubprocess *proc);
RZ_API RzSubprocessWaitReason rz_subprocess_wait(RzSubprocess *proc, ut64 timeout_ms);
RZ_API void rz_subprocess_kill(RzSubprocess *proc);
@ -91,8 +112,13 @@ RZ_API ut8 *rz_subprocess_out(RzSubprocess *proc, int *length);
RZ_API ut8 *rz_subprocess_err(RzSubprocess *proc, int *length);
RZ_API ssize_t rz_subprocess_stdin_write(RzSubprocess *proc, const ut8 *buf, size_t buf_size);
RZ_API RzStrBuf *rz_subprocess_stdout_read(RzSubprocess *proc, size_t n, ut64 timeout_ms);
RZ_API RzStrBuf *rz_subprocess_stdout_readline(RzSubprocess *proc, ut64 timeout_ms);
RZ_API RZ_BORROW RzStrBuf *rz_subprocess_stdout_readline(RzSubprocess *proc, ut64 timeout_ms);
RZ_API RzSubprocessOutput *rz_subprocess_drain(RzSubprocess *proc);
RZ_API void rz_subprocess_output_free(RzSubprocessOutput *out);
#endif
RZ_API RZ_OWN RzPty *rz_subprocess_openpty(RZ_BORROW RZ_NULLABLE char *slave_name, RZ_NULLABLE void /* const struct termios */ *term_params, RZ_NULLABLE void /* const struct winsize */ *win_params);
RZ_API void rz_subprocess_close_pty(RZ_BORROW RZ_NONNULL const RzPty *pty);
RZ_API bool rz_subprocess_login_tty(RZ_BORROW RZ_NONNULL const RzPty *pty);
RZ_API void rz_subprocess_pty_free(RZ_OWN RzPty *pty);
#endif // RZ_UTIL_SUBPROCESS_H

View file

@ -42,6 +42,12 @@ RZ_API int rz_sys_fork(void);
RZ_API int rz_sys_kill(int pid, int sig);
// nocleanup = false => exit(); true => _exit()
RZ_API void rz_sys_exit(int status, bool nocleanup);
/* openpty family of functions */
RZ_API /* pid_t */ int rz_sys_forkpty(int *amaster, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp);
RZ_API int rz_sys_openpty(int *amaster, int *aslave, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp);
RZ_API int rz_sys_login_tty(int fd);
RZ_API bool rz_is_heap(void *p);
RZ_API bool rz_sys_stop(void);
RZ_API char *rz_sys_pid_to_path(int pid);

View file

@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2020 Florian Märkl <info@florianmaerkl.de>
// SPDX-FileCopyrightText: 2021 ret2libc <sirmy15@gmail.com>
// SPDX-FileCopyrightText: 2022 Dhruv Maroo <dhruvmaru007@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_cons.h>
@ -246,7 +247,13 @@ error:
return ret;
}
RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt) {
/**
* \brief Start a subprocess, using the options provided in \p opt
*
* \param opt RzSubprocessOpt struct
* \return RzSubprocess* The newly created subprocess
*/
RZ_API RZ_OWN RzSubprocess *rz_subprocess_start_opt(RZ_NONNULL const RzSubprocessOpt *opt) {
RzSubprocess *proc = NULL;
const HANDLE curr_stdin_handle = (HANDLE)_get_osfhandle(fileno(stdin));
const HANDLE curr_stdout_handle = (HANDLE)_get_osfhandle(fileno(stdout));
@ -700,6 +707,27 @@ RZ_API void rz_subprocess_free(RzSubprocess *proc) {
CloseHandle(proc->proc);
free(proc);
}
/**
* \brief Unimplemented on Windows
*
* \return RzPty* NULL pointer until it has been implemented
*/
RZ_API RZ_OWN RzPty *rz_subprocess_openpty(RZ_BORROW RZ_NULLABLE char *slave_name, RZ_NULLABLE void /* const struct termios */ *term_params, RZ_NULLABLE void /* const struct winsize */ *win_params) {
RZ_LOG_ERROR("openpty: Not implemented for Windows!");
return NULL;
}
/**
* \brief Unimplemented on Windows
*
* \return bool false
*/
RZ_API bool rz_subprocess_login_tty(RZ_BORROW RZ_NONNULL const RzPty *pty) {
RZ_LOG_ERROR("login_tty: Not implemented for Windows!");
return false;
}
#else // __WINDOWS__
#include <errno.h>
@ -714,6 +742,8 @@ struct rz_subprocess_t {
int ret;
RzStrBuf out;
RzStrBuf err;
int master_fd; ///< Needed to check whether PTY
int slave_fd;
};
static RzPVector subprocs;
@ -890,10 +920,109 @@ static void destroy_child_env(char **child_env) {
free(child_env);
}
RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt) {
static bool init_pipes(RzSubprocess *proc, const RzSubprocessOpt *opt, int stdin_pipe[], int stdout_pipe[], int stderr_pipe[], RzPty *new_pty) {
RzPty *pty = opt->pty;
/* If we need to use a PTY, we should create one right now */
if (!pty && (opt->stdin_pipe == RZ_SUBPROCESS_PIPE_PTY || opt->stdout_pipe == RZ_SUBPROCESS_PIPE_PTY || opt->stderr_pipe == RZ_SUBPROCESS_PIPE_PTY)) {
pty = rz_subprocess_openpty(NULL, NULL, NULL);
if (!pty) {
return false;
}
new_pty = pty;
}
if (pty) {
proc->master_fd = pty->master_fd;
proc->slave_fd = pty->slave_fd;
}
switch (opt->stdin_pipe) {
case RZ_SUBPROCESS_PIPE_CREATE:
if (rz_sys_pipe(stdin_pipe, true) == -1) {
perror("pipe");
return false;
}
proc->stdin_fd = stdin_pipe[1];
break;
case RZ_SUBPROCESS_PIPE_PTY:
stdin_pipe[0] = pty->slave_fd;
stdin_pipe[1] = pty->master_fd;
proc->stdin_fd = stdin_pipe[1];
break;
case RZ_SUBPROCESS_PIPE_STDOUT:
RZ_LOG_ERROR("Invalid pipe option 'RZ_SUBPROCESS_PIPE_STDOUT' for stdin. Ignoring...\n");
break;
case RZ_SUBPROCESS_PIPE_NONE:
break;
}
switch (opt->stdout_pipe) {
case RZ_SUBPROCESS_PIPE_CREATE:
if (rz_sys_pipe(stdout_pipe, true) == -1) {
perror("pipe");
return false;
}
if (fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
perror("fcntl");
return false;
}
proc->stdout_fd = stdout_pipe[0];
break;
case RZ_SUBPROCESS_PIPE_PTY:
stdout_pipe[0] = pty->master_fd;
stdout_pipe[1] = pty->slave_fd;
proc->stdout_fd = stdout_pipe[0];
break;
case RZ_SUBPROCESS_PIPE_STDOUT:
RZ_LOG_ERROR("Invalid pipe option 'RZ_SUBPROCESS_PIPE_STDOUT' for stdout. Ignoring...\n");
break;
case RZ_SUBPROCESS_PIPE_NONE:
break;
}
switch (opt->stderr_pipe) {
case RZ_SUBPROCESS_PIPE_CREATE:
if (rz_sys_pipe(stderr_pipe, true) == -1) {
perror("pipe");
return false;
}
if (fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
perror("fcntl");
return false;
}
proc->stderr_fd = stderr_pipe[0];
break;
case RZ_SUBPROCESS_PIPE_PTY:
stdout_pipe[0] = pty->master_fd;
stdout_pipe[1] = pty->slave_fd;
proc->stdout_fd = stdout_pipe[0];
break;
case RZ_SUBPROCESS_PIPE_STDOUT:
stderr_pipe[0] = stdout_pipe[0];
stderr_pipe[1] = stdout_pipe[1];
proc->stderr_fd = proc->stdout_fd;
break;
case RZ_SUBPROCESS_PIPE_NONE:
break;
}
return true;
}
/**
* \brief Start a subprocess, using the options provided in \p opt
*
* \param opt RzSubprocessOpt struct
* \return RzSubprocess* The newly created subprocess
*/
RZ_API RZ_OWN RzSubprocess *rz_subprocess_start_opt(RZ_NONNULL const RzSubprocessOpt *opt) {
rz_return_val_if_fail(opt, NULL);
RzSubprocess *proc = NULL;
char **child_env = NULL;
char **argv = calloc(opt->args_size + 2, sizeof(char *));
RzPty *new_pty = NULL;
if (!argv) {
return NULL;
}
@ -912,6 +1041,8 @@ RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt) {
proc->stdin_fd = -1;
proc->stdout_fd = -1;
proc->stderr_fd = -1;
proc->master_fd = -1;
proc->slave_fd = -1;
rz_strbuf_init(&proc->out);
rz_strbuf_init(&proc->err);
@ -927,72 +1058,54 @@ RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt) {
int stdin_pipe[2] = { -1, -1 };
int stdout_pipe[2] = { -1, -1 };
int stderr_pipe[2] = { -1, -1 };
if (opt->stdin_pipe == RZ_SUBPROCESS_PIPE_CREATE) {
if (rz_sys_pipe(stdin_pipe, true) == -1) {
perror("pipe");
goto error;
}
proc->stdin_fd = stdin_pipe[1];
}
if (opt->stdout_pipe == RZ_SUBPROCESS_PIPE_CREATE) {
if (rz_sys_pipe(stdout_pipe, true) == -1) {
perror("pipe");
goto error;
}
if (fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
perror("fcntl");
goto error;
}
proc->stdout_fd = stdout_pipe[0];
}
if (opt->stderr_pipe == RZ_SUBPROCESS_PIPE_CREATE) {
if (rz_sys_pipe(stderr_pipe, true) == -1) {
perror("pipe");
goto error;
}
if (fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
perror("fcntl");
goto error;
}
proc->stderr_fd = stderr_pipe[0];
} else if (opt->stderr_pipe == RZ_SUBPROCESS_PIPE_STDOUT) {
stderr_pipe[0] = stdout_pipe[0];
stderr_pipe[1] = stdout_pipe[1];
proc->stderr_fd = proc->stdout_fd;
if (!init_pipes(proc, opt, stdin_pipe, stdout_pipe, stderr_pipe, new_pty)) {
goto error;
}
// Let's create the environment for the child in the parent, with malloc,
// because we can't use functions that lock after fork
child_env = create_child_env(opt->envvars, opt->envvals, opt->env_size);
proc->pid = rz_sys_fork();
if (proc->pid == -1) {
// fail
perror("fork");
goto error;
} else if (proc->pid == 0) {
// child
if (stderr_pipe[1] != -1) {
while ((dup2(stderr_pipe[1], STDERR_FILENO) == -1) && (errno == EINTR)) {
}
if (proc->stderr_fd != proc->stdout_fd) {
if (proc->stderr_fd != proc->stdout_fd && proc->stderr_fd != proc->master_fd) {
rz_sys_pipe_close(stderr_pipe[1]);
rz_sys_pipe_close(stderr_pipe[0]);
}
}
if (stdout_pipe[1] != -1) {
while ((dup2(stdout_pipe[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {
}
rz_sys_pipe_close(stdout_pipe[1]);
rz_sys_pipe_close(stdout_pipe[0]);
if (proc->stdout_fd != proc->master_fd) {
rz_sys_pipe_close(stdout_pipe[1]);
rz_sys_pipe_close(stdout_pipe[0]);
}
}
if (stdin_pipe[0] != -1) {
while ((dup2(stdin_pipe[0], STDIN_FILENO) == -1) && (errno == EINTR)) {
}
rz_sys_pipe_close(stdin_pipe[0]);
rz_sys_pipe_close(stdin_pipe[1]);
if (proc->stdin_fd != proc->master_fd) {
rz_sys_pipe_close(stdin_pipe[0]);
rz_sys_pipe_close(stdin_pipe[1]);
}
}
if (proc->master_fd != -1 && close(proc->master_fd)) {
perror("close");
}
if (proc->slave_fd != -1 && close(proc->slave_fd)) {
perror("close");
}
// Use the previously created environment
@ -1005,18 +1118,45 @@ RZ_API RzSubprocess *rz_subprocess_start_opt(RzSubprocessOpt *opt) {
destroy_child_env(child_env);
free(argv);
if (stdin_pipe[0] != -1) {
if (!opt->make_raw || proc->slave_fd == -1) {
goto no_term_change;
}
#if HAVE_FORKPTY && HAVE_OPENPTY && HAVE_LOGIN_TTY
struct termios term_params;
/* Needed to avoid reading back the writes again from the TTY */
if (tcgetattr(proc->slave_fd, &term_params) != 0) {
perror("tcgetattr");
goto no_term_change;
}
cfmakeraw(&term_params);
/* This avoids ECHO, so we don't read back whatever we wrote */
if (tcsetattr(proc->slave_fd, TCSANOW, &term_params) != 0) {
perror("tcsetattr");
}
#endif
no_term_change:
if (proc->slave_fd != -1 && close(proc->slave_fd) == -1) {
perror("close");
}
if (new_pty) {
/* Free the RzPTY if we created it */
rz_subprocess_pty_free(new_pty);
}
if (stdin_pipe[0] != -1 && stdin_pipe[0] != proc->slave_fd) {
rz_sys_pipe_close(stdin_pipe[0]);
}
if (stdout_pipe[1] != -1) {
if (stdout_pipe[1] != -1 && stdout_pipe[1] != proc->slave_fd) {
rz_sys_pipe_close(stdout_pipe[1]);
}
if (stderr_pipe[1] != -1 && proc->stderr_fd != proc->stdout_fd) {
if (stderr_pipe[1] != -1 && proc->stderr_fd != proc->stdout_fd && stderr_pipe[1] != proc->slave_fd) {
rz_sys_pipe_close(stderr_pipe[1]);
}
rz_pvector_push(&subprocs, proc);
subprocess_unlock();
return proc;
@ -1028,41 +1168,54 @@ error:
if (proc && proc->killpipe[1] == -1) {
rz_sys_pipe_close(proc->killpipe[1]);
}
free(proc);
if (stderr_pipe[0] != -1 && stderr_pipe[0] != stdout_pipe[0]) {
if (stderr_pipe[0] != -1 && stderr_pipe[0] != stdout_pipe[0] && stderr_pipe[0] != proc->master_fd) {
rz_sys_pipe_close(stderr_pipe[0]);
}
if (stderr_pipe[1] != -1 && stderr_pipe[1] != stdout_pipe[1]) {
if (stderr_pipe[1] != -1 && stderr_pipe[1] != stdout_pipe[1] && stderr_pipe[0] != proc->slave_fd) {
rz_sys_pipe_close(stderr_pipe[1]);
}
if (stdout_pipe[0] != -1) {
if (stdout_pipe[0] != -1 && stdout_pipe[0] != proc->master_fd) {
rz_sys_pipe_close(stdout_pipe[0]);
}
if (stdout_pipe[1] != -1) {
if (stdout_pipe[1] != -1 && stdout_pipe[1] != proc->slave_fd) {
rz_sys_pipe_close(stdout_pipe[1]);
}
if (stdin_pipe[0] != -1) {
if (stdin_pipe[0] != -1 && stdin_pipe[0] != proc->slave_fd) {
rz_sys_pipe_close(stdin_pipe[0]);
}
if (stdin_pipe[1] != -1) {
if (stdin_pipe[1] != -1 && stdin_pipe[1] != proc->master_fd) {
rz_sys_pipe_close(stdin_pipe[1]);
}
if (proc->master_fd != -1) {
close(proc->master_fd);
}
if (proc->slave_fd != -1) {
close(proc->slave_fd);
}
free(proc);
if (new_pty) {
/* Free the RzPTY if we created it */
RZ_FREE(new_pty);
}
destroy_child_env(child_env);
subprocess_unlock();
return NULL;
}
static size_t read_to_strbuf(RzStrBuf *sb, int fd, bool *fd_eof, size_t n_bytes) {
static size_t read_to_strbuf(RzStrBuf *sb, int fd, bool *fd_eof, size_t n_bytes, bool is_pty) {
char buf[BUFFER_SIZE];
size_t to_read = sizeof(buf);
if (n_bytes && to_read > n_bytes) {
to_read = n_bytes;
}
ssize_t sz = read(fd, buf, to_read);
if (sz < 0) {
perror("read");
} else if (sz == 0) {
if (sz == 0 || (is_pty && sz == -1 && errno == EIO)) {
/* In case of PTY, EIO (input/output error) denotes EOF, hence the manual checking */
*fd_eof = true;
} else if (sz < 0) {
perror("read");
} else {
rz_strbuf_append_n(sb, buf, (int)sz);
}
@ -1096,6 +1249,11 @@ static RzSubprocessWaitReason subprocess_wait(RzSubprocess *proc, ut64 timeout_m
bool child_dead = false;
bool timedout = true;
bool bytes_enabled = n_bytes != 0;
/* Check if stdout and stderr are connected to a PTY */
bool stdout_pty = proc->stdout_fd != -1 && proc->stdout_fd == proc->master_fd;
bool stderr_pty = proc->stderr_fd != -1 && proc->stderr_fd == proc->master_fd;
while ((!bytes_enabled || n_bytes) && ((stdout_enabled && !stdout_eof) || (stderr_enabled && !stderr_eof) || !child_dead)) {
fd_set rfds;
FD_ZERO(&rfds);
@ -1143,14 +1301,14 @@ static RzSubprocessWaitReason subprocess_wait(RzSubprocess *proc, ut64 timeout_m
timedout = true;
if (stdout_enabled && FD_ISSET(proc->stdout_fd, &rfds)) {
timedout = false;
size_t r = read_to_strbuf(&proc->out, proc->stdout_fd, &stdout_eof, n_bytes);
size_t r = read_to_strbuf(&proc->out, proc->stdout_fd, &stdout_eof, n_bytes, stdout_pty);
if (r > 0 && n_bytes) {
n_bytes -= r;
}
}
if (stderr_enabled && FD_ISSET(proc->stderr_fd, &rfds)) {
timedout = false;
size_t r = read_to_strbuf(&proc->err, proc->stderr_fd, &stderr_eof, n_bytes);
size_t r = read_to_strbuf(&proc->err, proc->stderr_fd, &stderr_eof, n_bytes, stderr_pty);
if (r > 0 && n_bytes) {
n_bytes -= r;
}
@ -1183,7 +1341,9 @@ static RzSubprocessWaitReason subprocess_wait(RzSubprocess *proc, ut64 timeout_m
* \param timeout_ms Wait for at most this amount of millisecond
*/
RZ_API RzSubprocessWaitReason rz_subprocess_wait(RzSubprocess *proc, ut64 timeout_ms) {
if (proc->stdin_fd != -1) {
/* Should not close proc->stdin_fd if the fork mode was PTY
(because it might point to the master fd, which needs to stay open to get the std{out,err}) */
if (proc->stdin_fd != -1 && proc->stdin_fd != proc->master_fd) {
// Close subprocess stdin to tell it that no more input will come from us
rz_sys_pipe_close(proc->stdin_fd);
proc->stdin_fd = -1;
@ -1238,7 +1398,7 @@ RZ_API RzStrBuf *rz_subprocess_stdout_read(RzSubprocess *proc, size_t n, ut64 ti
* \param proc Subprocess to communicate with
* \param timeout_ms Wait for at most this amount of millisecond to read subprocess' stdout
*/
RZ_API RzStrBuf *rz_subprocess_stdout_readline(RzSubprocess *proc, ut64 timeout_ms) {
RZ_API RZ_BORROW RzStrBuf *rz_subprocess_stdout_readline(RzSubprocess *proc, ut64 timeout_ms) {
rz_strbuf_fini(&proc->out);
rz_strbuf_init(&proc->out);
if (proc->stdout_fd != -1) {
@ -1281,17 +1441,98 @@ RZ_API void rz_subprocess_free(RzSubprocess *proc) {
rz_strbuf_fini(&proc->err);
rz_sys_pipe_close(proc->killpipe[0]);
rz_sys_pipe_close(proc->killpipe[1]);
if (proc->stdin_fd != -1) {
if (proc->master_fd != -1) {
rz_sys_pipe_close(proc->master_fd);
}
if (proc->stdin_fd != -1 && proc->stdin_fd != proc->master_fd) {
rz_sys_pipe_close(proc->stdin_fd);
}
if (proc->stdout_fd != -1) {
if (proc->stdout_fd != -1 && proc->stdout_fd != proc->master_fd) {
rz_sys_pipe_close(proc->stdout_fd);
}
if (proc->stderr_fd != -1 && proc->stderr_fd != proc->stdout_fd) {
if (proc->stderr_fd != -1 && proc->stderr_fd != proc->stdout_fd && proc->stderr_fd != proc->master_fd) {
rz_sys_pipe_close(proc->stderr_fd);
}
free(proc);
}
/**
* \brief Call openpty(3) with the provided arguments
*
* \param slave_name The name of the slave PTY is stored in this
* This is marked as RZ_BORROW, so it's ownership no longer stays with the caller
* and is now owned by the returned RzPty struct
*
* \param term_params Terminal attributes (struct termios) for the forked process
* \param win_params Window attributes (struct winsize) for the forked process
*
* \return RzPty*
*/
RZ_API RZ_OWN RzPty *rz_subprocess_openpty(RZ_BORROW RZ_NULLABLE char *slave_name, RZ_NULLABLE void /* const struct termios */ *term_params, RZ_NULLABLE void /* const struct winsize */ *win_params) {
RzPty *pty = RZ_NEW0(RzPty);
int ret = rz_sys_openpty(&pty->master_fd, &pty->slave_fd, slave_name, NULL, NULL);
if (ret == -1) {
perror("openpty");
RZ_FREE(pty);
return NULL;
}
return pty;
}
/**
* \brief Call login_tty(3) on the provided \p pty
*
* \param pty RzPty struct
* \return bool true if login_tty succeeded, false otherwise
*/
RZ_API bool rz_subprocess_login_tty(RZ_BORROW RZ_NONNULL const RzPty *pty) {
rz_return_val_if_fail(pty, false);
int ret = rz_sys_login_tty(pty->slave_fd);
if (ret == -1) {
perror("login_tty");
return false;
}
return true;
}
/**
* \brief Closes the file descriptors associated with \p pty
*
* \param pty RzPty struct
* \return void
*
* No need to call this after you've used the \p pty in `rz_subprocess_start_opt`,
* since the file descriptors would already have been correctly closed
*/
RZ_API void rz_subprocess_close_pty(RZ_BORROW RZ_NONNULL const RzPty *pty) {
if (close(pty->master_fd) == -1) {
perror("close");
}
if (close(pty->slave_fd) == -1) {
perror("close");
}
}
/**
* \brief Free the \p pty
*
* \param pty RzPty struct
* \return void
*/
RZ_API void rz_subprocess_pty_free(RZ_OWN RzPty *pty) {
if (!pty) {
return;
}
RZ_FREE(pty->name);
free(pty);
}
#endif
RZ_API int rz_subprocess_ret(RzSubprocess *proc) {
@ -1355,6 +1596,8 @@ RZ_API RzSubprocess *rz_subprocess_start(
.stdin_pipe = RZ_SUBPROCESS_PIPE_CREATE,
.stdout_pipe = RZ_SUBPROCESS_PIPE_CREATE,
.stderr_pipe = RZ_SUBPROCESS_PIPE_CREATE,
.pty = NULL,
.make_raw = /* does not matter */ false
};
return rz_subprocess_start_opt(&opt);
}

View file

@ -74,6 +74,18 @@ extern char **environ;
#endif
#endif
/* For "openpty" family of funtcions */
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
#if defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <util.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#else
#include <pty.h>
#include <utmp.h>
#endif
#endif
RZ_LIB_VERSION(rz_util);
#ifdef __x86_64__
@ -1666,6 +1678,9 @@ RZ_API int rz_sys_fork(void) {
parent_lock_enter();
#endif
pid_t child = fork();
if (child == -1) {
perror("fork");
}
#if __UNIX__ && HAVE_PIPE && !HAVE_PIPE2
if (child == 0) {
is_child = true;
@ -1681,6 +1696,72 @@ RZ_API int rz_sys_fork(void) {
}
#endif
/**
* \brief Wrapper for forkpty(3)
*
* \param amaster The master end of the PTY is stored here
* \param name The name of the slave end of the PTY is stored here
* \param termp (const struct termios) The terminal attributes
* \param winp (const struct winsize) The window size attributes
*
* \return int (pid_t) PID of the forked process
*/
RZ_API /* pid_t */ int rz_sys_forkpty(int *amaster, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
pid_t ret = forkpty(amaster, name, termp, winp);
if (ret == -1) {
perror("forkpty");
}
return ret;
#else
RZ_LOG_ERROR("forkpty() not found\n");
return -1;
#endif
}
/**
* \brief Wrapper for openpty(3)
*
* \param amaster The master end of the PTY is stored here
* \param aslave The slave end of the PTY is stored here
* \param name The name of the slave end of the PTY is stored here
* \param termp (const struct termios) The terminal attributes
* \param winp (const struct winsize) The window size attributes
*
* \return int Return code
*/
RZ_API int rz_sys_openpty(int *amaster, int *aslave, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
int ret = openpty(amaster, aslave, name, termp, winp);
if (ret == -1) {
perror("openpty");
}
return ret;
#else
RZ_LOG_ERROR("openpty() not found\n");
return -1;
#endif
}
/**
* \brief Wrapper for login_tty(3)
*
* \param fd File descriptor for the slave end of the PTY; To be made the controlling terminal
* \return int Return code
*/
RZ_API int rz_sys_login_tty(int fd) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
int ret = login_tty(fd);
if (ret == -1) {
perror("login_tty");
}
return ret;
#else
RZ_LOG_ERROR("login_tty() not found\n");
return -1;
#endif
}
RZ_API int rz_sys_truncate_fd(int fd, ut64 length) {
#ifdef _MSC_VER
return _chsize_s(fd, length);

View file

@ -263,6 +263,131 @@ bool test_interactive(void) {
mu_end;
}
#if HAVE_FORKPTY && HAVE_OPENPTY && HAVE_LOGIN_TTY
bool test_interactive_pty(void) {
rz_subprocess_init();
const char *exe_path = get_auxiliary_path("subprocess-interactive");
RzSubprocessOpt opt = { 0 };
opt.file = exe_path;
/* Try all PTY */
opt.stdin_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.stdout_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.stderr_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.make_raw = true;
RzSubprocess *sp = rz_subprocess_start_opt(&opt);
mu_assert_notnull(sp, "the subprocess should be created");
rz_subprocess_stdin_write(sp, (const ut8 *)"3\n", strlen("3\n"));
rz_subprocess_stdin_write(sp, (const ut8 *)"5\n", strlen("5\n"));
RzStrBuf *sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
int c = atoi(rz_strbuf_get(sb));
char buf[100];
snprintf(buf, sizeof(buf), "%d\n", 3 + 5 + c);
rz_subprocess_stdin_write(sp, (const ut8 *)buf, strlen(buf));
rz_subprocess_wait(sp, UT_TIMEOUT);
RzSubprocessOutput *spo = rz_subprocess_drain(sp);
mu_assert_streq(remove_cr(spo->out), "Right\n", "A Good message should be returned");
mu_assert_eq(spo->ret, 0, "subprocess exited in the right way");
rz_subprocess_output_free(spo);
rz_subprocess_free(sp);
rz_subprocess_fini();
mu_end;
}
bool test_interactive_custom_pty(void) {
rz_subprocess_init();
const char *exe_path = get_auxiliary_path("subprocess-interactive");
RzSubprocessOpt opt = { 0 };
opt.file = exe_path;
opt.stdin_pipe = RZ_SUBPROCESS_PIPE_CREATE;
opt.stdout_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.stderr_pipe = RZ_SUBPROCESS_PIPE_STDOUT;
opt.make_raw = true;
opt.pty = rz_subprocess_openpty(NULL, NULL, NULL);
RzSubprocess *sp = rz_subprocess_start_opt(&opt);
mu_assert_notnull(sp, "the subprocess should be created");
rz_subprocess_stdin_write(sp, (const ut8 *)"3\n", strlen("3\n"));
rz_subprocess_stdin_write(sp, (const ut8 *)"5\n", strlen("5\n"));
RzStrBuf *sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
int c = atoi(rz_strbuf_get(sb));
char buf[100];
snprintf(buf, sizeof(buf), "%d\n", 3 + 5 + c);
rz_subprocess_stdin_write(sp, (const ut8 *)buf, strlen(buf));
rz_subprocess_wait(sp, UT_TIMEOUT);
RzSubprocessOutput *spo = rz_subprocess_drain(sp);
mu_assert_streq(remove_cr(spo->out), "Right\n", "A Good message should be returned");
mu_assert_eq(spo->ret, 0, "subprocess exited in the right way");
rz_subprocess_output_free(spo);
rz_subprocess_free(sp);
rz_subprocess_fini();
rz_subprocess_pty_free(opt.pty);
mu_end;
}
bool test_interactive_not_raw_pty(void) {
rz_subprocess_init();
const char *exe_path = get_auxiliary_path("subprocess-interactive");
RzSubprocessOpt opt = { 0 };
opt.file = exe_path;
opt.stdin_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.stdout_pipe = RZ_SUBPROCESS_PIPE_PTY;
opt.stderr_pipe = RZ_SUBPROCESS_PIPE_PTY;
/* Not raw, so all input will be echoe-ed */
opt.make_raw = false;
RzSubprocess *sp = rz_subprocess_start_opt(&opt);
mu_assert_notnull(sp, "the subprocess should be created");
rz_subprocess_stdin_write(sp, (const ut8 *)"3\n", strlen("3\n"));
rz_subprocess_stdin_write(sp, (const ut8 *)"5\n", strlen("5\n"));
/* CRLF is returned, can be changed by using custom PTY with custom term params
Not necessary for testing here though */
RzStrBuf *sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
mu_assert_streq(remove_cr(rz_strbuf_get(sb)), "3\r\n", "No 3");
sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
mu_assert_streq(remove_cr(rz_strbuf_get(sb)), "5\r\n", "No 5");
sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
int c = atoi(rz_strbuf_get(sb));
char buf[100];
snprintf(buf, sizeof(buf), "%d\n", 3 + 5 + c);
rz_subprocess_stdin_write(sp, (const ut8 *)buf, strlen(buf));
sb = rz_subprocess_stdout_readline(sp, UT_TIMEOUT);
mu_assert_eq(atoi(rz_strbuf_get(sb)), 3 + 5 + c, "No 3 + 5 + c");
rz_subprocess_wait(sp, UT_TIMEOUT);
RzSubprocessOutput *spo = rz_subprocess_drain(sp);
mu_assert_streq(remove_cr(spo->out), "Right\r\n", "A Good message should be returned");
mu_assert_eq(spo->ret, 0, "subprocess exited in the right way");
rz_subprocess_output_free(spo);
rz_subprocess_free(sp);
rz_subprocess_fini();
mu_end;
}
#else
bool test_interactive_pty(void) {
mu_end;
}
bool test_interactive_custom_pty(void) {
mu_end;
}
bool test_interactive_not_raw_pty(void) {
mu_end;
}
#endif // PTY functions
bool all_tests() {
mu_run_test(test_noargs_noinput_outerr);
mu_run_test(test_args);
@ -275,7 +400,10 @@ bool all_tests() {
mu_run_test(test_stderronly);
mu_run_test(test_stdoutstderr);
mu_run_test(test_interactive);
mu_run_test(test_interactive_pty);
mu_run_test(test_interactive_custom_pty);
mu_run_test(test_interactive_not_raw_pty);
return tests_passed != tests_run;
}
mu_main(all_tests)
mu_main(all_tests)