Newer
Older
Import / web / www.xiaofrog.com / shell / webtty.c
/***********************************************************************
  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);
    }
}