/***********************************************************************
webtty.c - using fifos to control applications running in a pseudo
terminal.
This file is part of the webtty package. Latest version can be found
at http://testape.com/webtty_sample.html. The webtty package was
written by Martin Steen Nielsen.
webtty is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
***********************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
//#include <util.h>
#include <pty.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <syslog.h>
#define MAX_CHUNK 1000
/***********************************************************************
wait
***********************************************************************/
static int waitms(int ms)
{
struct timespec requested = { 0, ms*1e6 };
struct timespec remaining;
nanosleep(&requested, &remaining);
return ms;
}
/***********************************************************************
signal handler ( from GNU C Library documentation)
***********************************************************************/
static volatile sig_atomic_t fatal_error_in_progress = 0;
static int terminal, pid_terminal, pid_buffer, pid_fifo_out, pid_fifo_in;
static char *name_buffer, *name_fifo_in, *name_fifo_out, *name_pid, *process_id;
static void fatal_error_signal(int sig)
{
/* Since this handler is established for more than one kind of signal,
it might still get invoked recursively by delivery of some other kind
of signal. Use a static variable to keep track of that. */
if (fatal_error_in_progress)
raise(sig);
fatal_error_in_progress = 1;
sleep(5);
if (pid_terminal)
kill(pid_terminal,sig);
if (pid_buffer)
kill(pid_buffer,sig);
if (pid_fifo_out)
kill(pid_fifo_out,sig);
if (pid_fifo_in)
kill(pid_fifo_in,sig);
unlink(name_buffer);
unlink(name_fifo_in);
unlink(name_fifo_out);
unlink(name_pid);
/* cleanup memory */
free(name_buffer);
free(name_fifo_in);
free(name_fifo_out);
free(name_pid);
closelog();
free(process_id);
signal(sig, SIG_DFL);
raise(sig);
}
/***********************************************************************
exit error function
***********************************************************************/
static void exit_error(char *error_text)
{
fprintf(stderr, "failed - %s\n",error_text);
_exit(1);
}
/***********************************************************************
wait for input function ( from GNU C library documentation )
***********************************************************************/
static int input_timeout (int filedes, unsigned int milli_seconds)
{
char buf;
fd_set set;
struct timeval timeout;
int res,r;
/* Initialize the file descriptor set. */
FD_ZERO (&set);
FD_SET (filedes, &set);
/* Initialize the timeout data structure. */
timeout.tv_sec = 0;
timeout.tv_usec = 1000 * milli_seconds;
/* select returns 0 if timeout, 1 if input available, -1 if error. */
return select (FD_SETSIZE, &set, NULL, NULL, &timeout);
}
/***********************************************************************
handle terminal
***********************************************************************/
static int handle_terminal (int *terminal, char **argv)
{
int pid = forkpty (terminal, 0, 0, 0);
if ( -1 == pid )
/* forkpty failed */
exit_error("could not fork or more pseudo terminals available");
/* are we parent ? */
if ( 0 != pid )
/* yes - return with child pid */
return pid;
/* remove cr/lf translation from terminal */
struct termios settings;
tcgetattr (STDOUT_FILENO, &settings);
settings.c_oflag &= ~OPOST;
settings.c_oflag &= ~ONLCR;
tcsetattr (STDOUT_FILENO, TCSANOW, &settings);
/* stdin/stdout/stderr is connected to fd. Launch application */
execvp(argv[0],&argv[0]);
/* application terminated - die and return */
close(*terminal);
_exit (1);
}
/***********************************************************************
handle output buffer - copies from terminal to file buffer
***********************************************************************/
static int handle_output_buffer (int terminal_out, char *buffer)
{
int pid, terminal_buffer = open(buffer,O_WRONLY|O_CREAT,0777);
if ( -1 == terminal_buffer )
/* failed */
exit_error("creating buffer <id>_out failed");
pid = fork();
if ( -1 == pid )
/* failed */
exit_error("fork failed");
/* are we parent ? */
if ( 0 != pid )
/* yes - return with child pid */
return pid;
/* copy from pseudo terminal to write buffer.
Exit when ternminal dies or when killed by parent */
while (-1 != input_timeout(terminal_out,0))
{
char buf;
if ( 1 == input_timeout(terminal_out,20) )
{
int res = read(terminal_out,&buf,1);
/* Reading from pseudo terminal in loop until closed */
if ( -1 == res)
break;
if ( 0 ==res )
waitms(20);
else
/* keep writing to pseudo terminal */
write(terminal_buffer,&buf,1);
}
}
/* close file */
close(terminal_buffer);
/* terminate process */
_exit(1);
}
/***********************************************************************
handle output fifo
***********************************************************************/
static int handle_output_fifo (char *name_buffer, char *name_fifo_out)
{
int data_count, time_count, sres, fifo_out, admin_out, pid, buffer;
buffer = open(name_buffer,O_RDONLY);
if ( -1 == buffer )
/* failed */
exit_error("open buffer <id>_out failed");
pid = fork();
if ( -1 == pid )
/* failed */
exit_error("fork failed");
/* are we parent ? */
if ( 0 != pid )
/* yes - return with child pid */
return pid;
/* no - We're the output handling child. */
while(1)
{
char *outbuf;
int time=0;
do
{
/* start with pause, to allow close operation to recover, before
reopening */
time += waitms(20);
/* Open in non - blocking will return -1 if not connected */
fifo_out = open(name_fifo_out,O_WRONLY|O_NONBLOCK);
/* Have we been here for 10 sec ? */
if (time>10000)
{
close(buffer);
_exit(1);
}
/* Keep going until client is connected */
} while( -1 == fifo_out);
/* zero counters */
data_count = 0; time_count = 0;
outbuf = (char*)malloc(MAX_CHUNK);
while(1)
{
if (1 != read(buffer,&outbuf[data_count],1))
{
/* Yes - simply make 50 ms pause */
/* consider feeding browser 0's to keep connection */
time_count += waitms(20);
}
else
{
if (data_count==0)
time_count=0;
data_count++;
}
/* give the browser manageable chunks */
if (data_count>=MAX_CHUNK)
{
break;
}
/* avoid timeout in browser, if no data */
if (time_count>10000)
{
break;
}
/* Transmit data after 200 ms */
if (data_count && time_count)
{
break;
}
}
if (data_count != 0 )
{
write(fifo_out,outbuf,data_count);
}
free(outbuf);
close (fifo_out);
}
close(buffer);
_exit(1);
}
/***********************************************************************
handle input fifo
***********************************************************************/
static int handle_input_fifo (char *name_fifo_in, int terminal)
{
int pid = fork();
if ( -1 == pid )
/* failed */
exit_error("fork failed");
/* are we parent ? */
if ( 0 != pid )
/* yes - return with child pid */
return pid;
/* no - we're child. loop on open and read pipe. Exit when killed
by parent only or when pipe disappears */
while(1)
{
char buf;
/* Open input pipe */
int fifo_in = open(name_fifo_in,O_RDONLY,0);
/* error ? */
if (fifo_in == -1)
/* Yes - die */
exit_error("input pipe not found");
/* No - Reading from pipe in loop until closed */
while (1 == read(fifo_in,&buf,1))
/* keep writing to pseudo terminal */
write(terminal,&buf,1);
/* close this end of pipe */
close(fifo_in);
}
_exit(1);
}
/***********************************************************************
exit help function
***********************************************************************/
static void exit_help(void)
{
printf("Usage:\n");
printf(" webtty <id> <application> [parameters]\n\n");
printf("Example:\n");
printf(" webtty key1234 bash\n");
printf(" webtty key1234 traceroute testape.com\n");
exit(1);
}
/***********************************************************************
process arguments and control process and pipe connetions
***********************************************************************/
int main(int argc, char **argv)
{
/* Is there no args or is '--help' '-?' or '-h' present ? */
if ((1 == argc) || strstr(argv[1],"--help-?-h"))
/* Yes - display the help */
exit_help();
else
{
FILE *f;
int dying_status, dying_pid;
/* No - run the program */
name_buffer = (char *)malloc(5+strlen(argv[1]));
name_fifo_in = (char *)malloc(5+strlen(argv[1]));
name_fifo_out = (char *)malloc(5+strlen(argv[1]));
name_pid = (char *)malloc(5+strlen(argv[1]));
process_id = (char *)malloc(9+1+strlen(argv[1]));
sprintf(process_id, "webtty[%s]",argv[1]);
openlog(process_id,LOG_PERROR,0);
/* launch application */
pid_terminal = handle_terminal(&terminal, &argv[2]);
/* handover to main process id to php */
strcpy(name_pid, argv[1]);
strcat(name_pid, "_pid");
f = fopen(name_pid,"w");
fprintf(f,"%d\n",pid_terminal);
fclose(f);
/* launch buffer handling */
strcpy(name_buffer,argv[1]);
strcat(name_buffer,"_buf");
pid_buffer = handle_output_buffer(terminal,name_buffer);
/* launch fifo output handling */
strcpy(name_fifo_out, argv[1]);
strcat(name_fifo_out, "_out");
mkfifo(name_fifo_out,0777);
pid_fifo_out = handle_output_fifo(name_buffer, name_fifo_out);
/* launch fifo input handling */
strcpy(name_fifo_in, argv[1]);
strcat(name_fifo_in, "_in");
mkfifo(name_fifo_in,0777);
pid_fifo_in = handle_input_fifo(name_fifo_in,terminal);
/* establish signal handler */
if (signal (SIGINT, fatal_error_signal) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, fatal_error_signal) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, fatal_error_signal) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
/* wait for any children to die */
int dying_id = waitpid (-1, 0, 0);
if (pid_fifo_in == dying_id)
pid_fifo_in=0;
if (pid_fifo_out == dying_id)
pid_fifo_out=0;
if (pid_buffer == dying_id)
pid_buffer=0;
if (pid_terminal == dying_id)
pid_terminal=0;
/* cleanup and kill rest of children in signal handler */
raise (SIGTERM);
/* wait for all to die, before returning */
wait (0);
}
}