Add bidirectional redirection to shell command execution function (Ctrl-t R and --exec option)

The "shell command execution feature" previously only supported the
ability to transfer a command's stdout and stderr to a device.

To support bidirectional commands, a feature will be added to connect
input from a device to the command's stdin.

Since some communication commands (sz, rz) require stderr to be kept
local, a feature will also be added to not transfer stderr to a device
if the command string begins with a '?'.

On Linux, you can terminate a running command by pressing Ctrl-t R again
while the command is running (this uses the command /usr/bin/pkill
internally).
This commit is contained in:
yabu76 2026-01-18 15:39:12 +09:00
parent 321494b4e6
commit 445a21b807
4 changed files with 131 additions and 22 deletions

View file

@ -28,6 +28,9 @@
#include <regex.h>
#include <errno.h>
#include "print.h"
#include "misc.h"
static pid_t shell_command_pid = 0;
void delay(long ms)
{
@ -111,7 +114,7 @@ int read_poll(int fd, void *data, size_t len, int timeout)
return 0;
}
ssize_t write_poll(int fd, void *data, size_t len, int timeout)
ssize_t write_poll(int fd, const void *data, size_t len, int timeout)
{
struct pollfd fds;
ssize_t ret = 0;
@ -234,46 +237,72 @@ bool match_patterns(const char *string, const char *patterns)
// specified filedescriptor, and runs command.
int execute_shell_command(int fd, const char *command)
{
#define READ_END 0
#define WRITE_END 1
pid_t pid;
#define READ_END 0
#define WRITE_END 1
int status;
int pipe_fd[2];
int pipefd_c2p[2];
int pipefd_p2c[2];
#if defined(__linux__)
static bool done_once = false;
if (!done_once)
{
atexit(&terminate_shell_command);
done_once = true;
}
#endif
// Create Pipes
if (pipe(pipe_fd) == -1)
if (pipe(pipefd_c2p) == -1 || pipe(pipefd_p2c) == -1)
{
tio_error_print("pipe() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
// Fork a child process
pid = fork();
if (pid == -1)
shell_command_pid = fork();
if (shell_command_pid == -1)
{
// Error occurred
tio_error_print("fork() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
else if (pid == 0)
else if (shell_command_pid == 0)
{
// Child process
close(pipefd_c2p[READ_END]);
close(pipefd_p2c[WRITE_END]);
tio_printf("Executing shell command '%s'", command);
// Redirect stdout and stderr to the file descriptor
close(pipe_fd[READ_END]);
if (dup2(pipe_fd[WRITE_END], STDOUT_FILENO) == -1 || dup2(pipe_fd[WRITE_END], STDERR_FILENO) == -1)
// Redirect stdin and stdout to the parent-pipe
if (dup2(pipefd_c2p[WRITE_END], STDOUT_FILENO) == -1 ||
dup2(pipefd_p2c[READ_END], STDIN_FILENO) == -1)
{
tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
// command prefix '?' excludes stderr from redirection
if (command[0] == '?')
{
command += 1;
}
else
{
if (dup2(pipefd_c2p[WRITE_END], STDERR_FILENO) == -1)
{
tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
}
// Execute the shell command
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// If execlp() returns, it means an error occurred
close(pipe_fd[WRITE_END]);
close(pipefd_c2p[WRITE_END]);
close(pipefd_p2c[READ_END]);
perror("execlp");
tio_error_print("execlp() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
@ -281,23 +310,69 @@ int execute_shell_command(int fd, const char *command)
else
{
// Parent process
fd_set rdfs;
int maxfd;
char buf[BUFSIZ];
int bytes;
// Read pipe and transfer to tty device.
close(pipe_fd[WRITE_END]);
while ( (bytes = read(pipe_fd[READ_END], buf, sizeof(buf))) > 0)
close(pipefd_c2p[WRITE_END]);
close(pipefd_p2c[READ_END]);
while (true)
{
FD_ZERO(&rdfs);
FD_SET(fd, &rdfs);
FD_SET(pipefd_c2p[READ_END], &rdfs);
maxfd = MAX(fd, pipefd_c2p[READ_END]);
/* Block until input becomes available or timeout */
status = select(maxfd + 1, &rdfs, NULL, NULL, NULL);
if (status < 0)
{
tio_warning_printf("select() failed(%s)", strerror(errno));
break;
}
if (FD_ISSET(fd, &rdfs))
{
bytes = read(fd, buf, sizeof(buf));
if (bytes <= 0)
{
tio_warning_printf("Could not read from tty device");
break;
}
rx_total += bytes;
write(pipefd_p2c[WRITE_END], buf, bytes);
}
if (FD_ISSET(pipefd_c2p[READ_END], &rdfs))
{
// Read pipe and transfer to tty device.
bytes = read(pipefd_c2p[READ_END], buf, sizeof(buf));
if (bytes < 0)
{
tio_warning_printf("Could not write to tty device");
}
else if (bytes == 0)
{
// Shell command has finished.
break;
}
if (tty_write(fd, buf, bytes) < 0)
{
tio_warning_printf("Could not write to tty device");
}
}
tty_sync(fd);
close(pipe_fd[READ_END]);
}
}
close(pipefd_p2c[WRITE_END]);
close(pipefd_c2p[READ_END]);
// Wait for the child process to finish
waitpid(pid, &status, 0);
waitpid(shell_command_pid, &status, 0);
shell_command_pid = 0;
if (WIFEXITED(status))
{
@ -313,6 +388,26 @@ int execute_shell_command(int fd, const char *command)
return 0;
}
#if defined(__linux__)
void terminate_shell_command(void)
{
// If previous shell command pid is remain, terminate it.
if (shell_command_pid != 0)
{
#define PKILL_BUFSIZ 80
char pkill_buf[PKILL_BUFSIZ] = {0};
int bytes;
bytes = snprintf(pkill_buf, PKILL_BUFSIZ, "/usr/bin/pkill -P %d", shell_command_pid);
if (bytes > 0 && bytes < PKILL_BUFSIZ)
{
system(pkill_buf);
}
}
}
#endif
void clear_line()
{
printf("\r\033[K");

View file

@ -38,3 +38,7 @@ double get_current_time(void);
bool match_patterns(const char *string, const char *patterns);
int execute_shell_command(int fd, const char *command);
void clear_line();
#if defined(__linux__)
void terminate_shell_command(void);
#endif

View file

@ -175,7 +175,7 @@ char key_hit = 0xff;
const char* device_name = NULL;
GList *device_list = NULL;
static struct termios tio, tio_raw, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
static unsigned long rx_total = 0, tx_total = 0;
unsigned long rx_total = 0, tx_total = 0;
static bool connected = false;
static bool standard_baudrate = true;
static void (*printchar)(char c);
@ -557,6 +557,15 @@ void *tty_stdin_input_thread(void *arg)
tio_printf("Flushed data I/O buffers");
tcflush(device_fd, TCIOFLUSH);
break;
#if defined(__linux__)
case KEY_SHIFT_R:
if (state == STATE_EXEC_SHELL_COMMAND)
{
tio_printf("Terminated shell command");
terminate_shell_command();
}
break;
#endif
default:
break;
}

View file

@ -81,6 +81,7 @@ typedef enum
extern const char *device_name;
extern bool interactive_mode;
extern state_t state;
extern unsigned long rx_total, tx_total;
void stdout_configure(void);
void stdin_configure(void);