/* linker requires -lutil for pty.h */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pty.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <ctype.h>
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
#define FALSE 0
#define TRUE 1
#define PTYIN 1
#define PTYOUT -1
int make_pty( char *cargv[] );
void ttyset( int reset, pid_t source, pid_t target );
int
main( int argc, char *argv[] )
{
FILE *dfp;
int pty_fd;
unsigned char c;
unsigned char last_c;
int dir = 0;
/* runs a command in a pty */
if ( argc < 2 ) {
printf( "Usage: %s <command> [optional paramters]\n", argv[0] );
exit( EXIT_FAILURE );
}
if (( pty_fd = make_pty( &argv[1] )) == -1 ) {
fprintf( stderr, "Failed to create pty for %s\n", argv[1] );
exit( EXIT_FAILURE );
}
if ( !( dfp = fopen( "./debug.log", "w" ))) {
fprintf( stderr, "Failed to open debug log\n" );
exit( EXIT_FAILURE );
}
/* set stdin to ~raw mode and direct signals to child process */
ttyset( FALSE, STDIN_FILENO, pty_fd );
/* direct stdin fd to pty and pty to stdout fd */
while ( TRUE ) {
/* get char('s) from pty */
while ( read( pty_fd, &c, 1 ) > 0 ) {
/* output character */
write( STDOUT_FILENO, &c, 1 );
if ( dir != PTYIN ) {
dir = PTYIN;
fprintf( dfp, "\nPTYIN: " );
}
if ( isprint( c ))
fprintf( dfp, "%c", c );
else
fprintf( dfp, " 0x%02X ", c );
}
if ( errno != EAGAIN && errno != EWOULDBLOCK ) {
/* lost connection to child process */
fprintf( stderr, "Lost connection to child pty: %s", strerror( errno ));
break;
}
/* get char from stdin fd */
if ( read( STDIN_FILENO, &c, 1 ) > 0 ) {
/* look for enter key - break on no input */
if ( c == 0x0A && ( !last_c || c == last_c )) {
printf( "\nDone processing\n" );
break;
}
/* save last key - flag for break */
last_c = c;
/* write character to pty */
write( pty_fd, &c, 1 );
if ( dir != PTYOUT ) {
dir = PTYOUT;
fprintf( dfp, "\nPTYOUT: " );
}
if ( isprint( c ))
fprintf( dfp, "%c", c );
else
fprintf( dfp, " 0x%02X ", c );
}
fflush( dfp );
/* give the system time to play */
usleep( 1000 );
}
fprintf( dfp, "\n");
fclose( dfp );
ttyset( TRUE, STDIN_FILENO, pty_fd );
return EXIT_SUCCESS;
} /* end main */
int
make_pty( char *cargv[] ) /* cargv must be a null terminated */
{
int master_fd = -1;
pid_t slave_fd;
/* create slave pty and master controler */
slave_fd = forkpty( &master_fd, NULL, NULL, NULL );
switch( slave_fd ) {
case -1:
/* error */
fprintf( stderr, "ERROR creating pty" );
return -1;
case 0:
/* execute program in slave pty */
if ( execvp( cargv[0], cargv ) == -1 ) {
fprintf( stderr, "Child failed to execute command: %s\n", cargv[0] );
return -1;
}
break;
default:
/* parent process - nothing to do */
break;
}
return master_fd;
} /* end make_pty */
void
ttyset( int reset, pid_t source, pid_t target )
{
static struct termios old = { 0 };
struct termios new;
static int in_flags = 0;
if ( reset ) {
/* set source back to original setting */
(void) tcsetattr( source, TCSANOW, &old );
(void) fcntl( source, F_SETFL, in_flags );
} else {
/* get source termios structure and set to semi-raw input */
(void) tcgetattr( source, &old );
new = old;
new.c_cc[VMIN] = 1;
new.c_cc[VTIME] = 0;
new.c_lflag &= ~( ICANON | ECHO );
new.c_iflag &= ~( ISTRIP | INPCK );
(void) tcsetattr( source, TCSANOW, &new );
/* set source for non blocking mode */
in_flags = fcntl( source, F_GETFL );
(void) fcntl( source, F_SETFL, in_flags | O_NONBLOCK );
/* set target for non blocking mode - no need to save flags */
(void) fcntl( target, F_SETFL, fcntl( target, F_GETFL ) | O_NONBLOCK );
}
} /* end ttyset */