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 <regex.h>
#include <errno.h> #include <errno.h>
#include "print.h" #include "print.h"
#include "misc.h"
static pid_t shell_command_pid = 0;
void delay(long ms) void delay(long ms)
{ {
@ -111,7 +114,7 @@ int read_poll(int fd, void *data, size_t len, int timeout)
return 0; 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; struct pollfd fds;
ssize_t ret = 0; ssize_t ret = 0;
@ -234,46 +237,72 @@ bool match_patterns(const char *string, const char *patterns)
// specified filedescriptor, and runs command. // specified filedescriptor, and runs command.
int execute_shell_command(int fd, const char *command) int execute_shell_command(int fd, const char *command)
{ {
#define READ_END 0 #define READ_END 0
#define WRITE_END 1 #define WRITE_END 1
pid_t pid;
int status; 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 // Create Pipes
if (pipe(pipe_fd) == -1) if (pipe(pipefd_c2p) == -1 || pipe(pipefd_p2c) == -1)
{ {
tio_error_print("pipe() failed (%s)", strerror(errno)); tio_error_print("pipe() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// Fork a child process // Fork a child process
pid = fork(); shell_command_pid = fork();
if (pid == -1) if (shell_command_pid == -1)
{ {
// Error occurred // Error occurred
tio_error_print("fork() failed (%s)", strerror(errno)); tio_error_print("fork() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
else if (pid == 0) else if (shell_command_pid == 0)
{ {
// Child process // Child process
close(pipefd_c2p[READ_END]);
close(pipefd_p2c[WRITE_END]);
tio_printf("Executing shell command '%s'", command); tio_printf("Executing shell command '%s'", command);
// Redirect stdout and stderr to the file descriptor // Redirect stdin and stdout to the parent-pipe
close(pipe_fd[READ_END]); if (dup2(pipefd_c2p[WRITE_END], STDOUT_FILENO) == -1 ||
if (dup2(pipe_fd[WRITE_END], STDOUT_FILENO) == -1 || dup2(pipe_fd[WRITE_END], STDERR_FILENO) == -1) dup2(pipefd_p2c[READ_END], STDIN_FILENO) == -1)
{ {
tio_error_print("dup2() failed (%s)", strerror(errno)); tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); 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 // Execute the shell command
execl("/bin/sh", "sh", "-c", command, (char *)NULL); execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// If execlp() returns, it means an error occurred // 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"); perror("execlp");
tio_error_print("execlp() failed (%s)", strerror(errno)); tio_error_print("execlp() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -281,23 +310,69 @@ int execute_shell_command(int fd, const char *command)
else else
{ {
// Parent process // Parent process
fd_set rdfs;
int maxfd;
char buf[BUFSIZ]; char buf[BUFSIZ];
int bytes; int bytes;
// Read pipe and transfer to tty device. close(pipefd_c2p[WRITE_END]);
close(pipe_fd[WRITE_END]); close(pipefd_p2c[READ_END]);
while ( (bytes = read(pipe_fd[READ_END], buf, sizeof(buf))) > 0)
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) if (tty_write(fd, buf, bytes) < 0)
{ {
tio_warning_printf("Could not write to tty device"); tio_warning_printf("Could not write to tty device");
} }
}
tty_sync(fd); 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 // Wait for the child process to finish
waitpid(pid, &status, 0); waitpid(shell_command_pid, &status, 0);
shell_command_pid = 0;
if (WIFEXITED(status)) if (WIFEXITED(status))
{ {
@ -313,6 +388,26 @@ int execute_shell_command(int fd, const char *command)
return 0; 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() void clear_line()
{ {
printf("\r\033[K"); printf("\r\033[K");

View file

@ -38,3 +38,7 @@ double get_current_time(void);
bool match_patterns(const char *string, const char *patterns); bool match_patterns(const char *string, const char *patterns);
int execute_shell_command(int fd, const char *command); int execute_shell_command(int fd, const char *command);
void clear_line(); 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; const char* device_name = NULL;
GList *device_list = NULL; GList *device_list = NULL;
static struct termios tio, tio_raw, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; 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 connected = false;
static bool standard_baudrate = true; static bool standard_baudrate = true;
static void (*printchar)(char c); static void (*printchar)(char c);
@ -557,6 +557,15 @@ void *tty_stdin_input_thread(void *arg)
tio_printf("Flushed data I/O buffers"); tio_printf("Flushed data I/O buffers");
tcflush(device_fd, TCIOFLUSH); tcflush(device_fd, TCIOFLUSH);
break; 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: default:
break; break;
} }

View file

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