mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
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:
parent
321494b4e6
commit
445a21b807
4 changed files with 131 additions and 22 deletions
133
src/misc.c
133
src/misc.c
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
11
src/tty.c
11
src/tty.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue