/****************************************************************************
**
*A  pty.c                       XGAP source                      Frank Celler
**
*H  @(#)$Id: pty.c,v 1.8 1994/06/11 13:52:21 fceller Exp $
**
*Y  Copyright 1992-1994,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
**
*H  $Log: pty.c,v $
*H  Revision 1.8  1994/06/11  13:52:21  fceller
*H  added 'SYS_HAS_SYS_SIGNAL' (why is SIGKILL not in <signal.h>?)
*H
*H  Revision 1.7  1994/06/11  12:42:28  fceller
*H  added 'SYS_HAS_GETPTY'
*H
*H  Revision 1.6  1994/06/06  08:57:24  fceller
*H  added database
*H
*H  Revision 1.5  1994/06/03  10:50:09  fceller
*H  fixed exec problem (again)
*H
*H  Revision 1.4  1993/10/21  17:09:51  fceller
*H  fixed includes
*H
*H  Revision 1.3  93/10/21  17:03:07  17:03:07  fceller ( Frank Celler)
*H  added first (incomplete) hp support
*H  
*H  Revision 1.2  1993/10/18  11:04:47  fceller
*H  added fast updated,  fixed timing problem
*H
*H  Revision 1.1  1993/08/12  13:49:47  fceller
*H  fixed opening of slave
*H
*H  Revision 1.0  1993/04/05  11:42:18  fceller
*H  Initial revision
*/
#include    <stdio.h>
#include    <stdlib.h>
#include    <fcntl.h>
#ifdef      SYS_HAS_TERMIO
#   include     <termio.h>
#else
#   include     <sgtty.h>
#endif
#ifdef      SYS_HAS_UNISTD
#   include	<unistd.h>
#endif
#ifdef      SYS_HAS_LIBC
#   include     <libc.h>
#endif
#ifdef      SYS_HAS_SIGNAL
#   include     <signal.h>
#endif
#ifdef	    SYS_HAS_SYS_SIGNAL
#   include    <sys/signal.h>
#endif

#include    <sys/param.h>
#include    <sys/types.h>
#include    <sys/time.h>
#include    <sys/wait.h>
#include    <sys/resource.h>
#include    <sys/ioctl.h>
#include    <sys/errno.h>

#ifdef      SYS_HAS_GETPTY
#   include     <sys/stat.h>
#endif

#include    <X11/Intrinsic.h>

#include    "utils.h"
#include    "pty.h"


/****************************************************************************
**
*V  GapPID  . . . . . . . . . . . . . . . . . . . . . . . . gap subprocess id
*/
static int      GapPID = -1;


/****************************************************************************
**
*V  FromGap . . . . . . . . . . . . . . . . . . . . . . for messages from gap
*V  ToGap . . . . . . . . . . . . . . . . . . . . . . . . for messages to gap
*/
int             FromGap;
int             ToGap;


/****************************************************************************
**

*F  ReadGap( <line>, <len> )  . . . . . . . . . . . . . . . . read gap output
*/
#ifdef DEBUG_ON
int READ_GAP ( file, where, line, len )
    char  * file;
    int     where;
    char  * line;
    int     len;
{
    int     n;
    int     old;

    if ( Debug )
    {
	fprintf( stderr, "%04d:%s:ReadGap( buf, %d ) = ", where, file, len );
	fflush( stderr );
    }
    if ( len < 0 )
    {
        len = read( FromGap, line, -len );
	if ( Debug )
	{
	    if ( len == -1 )
		fprintf( stderr, "-1: no input\n" );
	    else
	    {
		fprintf( stderr, "%d: '", len );
		fwrite( line, 1, len, stderr );
		fprintf( stderr, "'\n" );
	    }
	    fflush( stderr );
	}
	return len;
    }
    else
    {
        old = len;
        while ( 0 < len )
        {
            while ( ( n = read( FromGap, line, len ) ) < 0 )
		;
            line = line + n;
            len  = len - n;
        }
	if ( Debug )
	{
	    fprintf( stderr, "%d: '", old );
	    fwrite( line, 1, old, stderr );
	    fprintf( stderr, "'\n" );
	    fflush( stderr );
	}
        return old;
    }
}
#else
int ReadGap ( line, len )
    char  * line;
    int     len;
{
    int     n;
    int     old;

    if ( len < 0 )
        return read( FromGap, line, -len );
    else
    {
        old = len;
        while ( 0 < len )
        {
            while ( ( n = read( FromGap, line, len ) ) < 0 )
		;
            line = line + n;
            len  = len - n;
        }
        return old;
    }
}
#endif


/****************************************************************************
**
*F  WriteGap( <line>, <len> ) . . . . . . . . . . . . . . . . write gap input
*/
extern int errno;

#ifdef DEBUG_ON
    void WRITE_GAP ( file, where, line, len )
        char  * file;
        int     where;
        char  * line;
        int     len;
#else
    void WriteGap ( line, len )
        char  * line;
        int     len;
#endif
{
    int     res;

#   ifdef DEBUG_ON
        if ( Debug )
	{
	    fprintf( stderr, "%04d:%s:WriteGap( %d ) = '", where, file, len );
	    fwrite( line, 1, len, stderr );
	    fprintf( stderr, "'\n" );
	    fflush( stderr );
	}
#   endif
    while ( 0 < len )
    {
        res = write( ToGap, line, len );
        if ( res < 0 )
        {
	    if ( errno == EAGAIN )
		continue;
            perror( "WriteGap" );
            KillGap();
            exit(1);
        }
        len  -= res;
        line += res;
    }
}


/****************************************************************************
**
*F  KillGap() . . . . . . . . . . . . . . . . . . . . .  kill the running gap
*/
void KillGap ()
{
    if ( GapPID != -1 )
    {
	close(ToGap);
        kill( GapPID, SIGKILL );
    }
}


/****************************************************************************
**
*F  InterruptGap()  . . . . . . . . . . . . . . . .  interupt the running gap
*/
void InterruptGap ()
{
    if ( GapPID != -1 )
        kill( GapPID, SIGINT );
}


/****************************************************************************
**
*F  GetMasterPty( <fid> ) . . . . . . . . .  open a master pty (from "xterm")
*/
static char  * ptydev;
static char  * ttydev;

#ifndef SYS_PTYDEV
#  ifdef hpux
#    define SYS_PTYDEV          "/dev/ptym/ptyxx"
#  else
#    define SYS_PTYDEV          "/dev/ptyxx"
#  endif
#endif

#ifndef SYS_TTYDEV
#  ifdef hpux
#    define SYS_TTYDEV          "/dev/pty/ttyxx"
#  else
#    define SYS_TTYDEV          "/dev/ttyxx"
#  endif
#endif

#ifndef SYS_PTYCHAR1
#  ifdef hpux
#    define SYS_PTYCHAR1        "zyxwvutsrqp"
#  else
#    define SYS_PTYCHAR1        "pqrstuvwxyz"
#  endif
#endif

#ifndef SYS_PTYCHAR2
#  ifdef hpux
#    define SYS_PTYCHAR2        "fedcba9876543210"
#  else
#    define SYS_PTYCHAR2        "0123456789abcdef"
#  endif
#endif


int GetMasterPty ( pty )
    int   * pty;
{
#   ifdef att
        if ( (*pty = open( "/dev/ptmx", O_RDWR )) < 0 )
            return 1;
        return 0;

#   else
#   ifdef SYS_HAS_GET_PSEUDOTTY
        return (*pty = getpseudotty( &ttydev, &ptydev )) >= 0 ? 0 : 1;

#   else
#   ifdef SYS_HAS_GETPTY
    char  * line;

	line = _getpty(pty, O_RDWR|O_NDELAY, 0600, 0) ;
        if (0 == line)
            return -1;
	strcpy( ttydev, line );
	return 0;

#   else
#   if defined(sgi) || (defined(umips) && defined(SYS_IS_USG))
        struct stat fstat_buf;

        *pty = open( "/dev/ptc", O_RDWR );
        if ( *pty < 0 || (fstat (*pty, &fstat_buf)) < 0 )
            return 1;
        sprintf( ttydev, "/dev/ttyq%d", minor(fstat_buf.st_rdev) );
#       if !defined(sgi)
            sprintf( ptydev, "/dev/ptyq%d", minor(fstat_buf.st_rdev) );
            if ( (*tty = open (ttydev, O_RDWR)) < 0 ) 
            {
                close (*pty);
                return 1;
            }
#       endif
        return 0;

#   else
        static int  devindex = 0;
        static int  letter   = 0;
        static int  slave    = 0;

        while ( SYS_PTYCHAR1[letter] )
        {
            ttydev[strlen(ttydev)-2] = SYS_PTYCHAR1[letter];
            ptydev[strlen(ptydev)-2] = SYS_PTYCHAR1[letter];

            while ( SYS_PTYCHAR2[devindex] )
            {
                ttydev[strlen(ttydev)-1] = SYS_PTYCHAR2[devindex];
                ptydev[strlen(ptydev)-1] = SYS_PTYCHAR2[devindex];
                        
                if ( (*pty = open( ptydev, O_RDWR )) >= 0 )
                    if ( (slave = open( ttydev, O_RDWR, 0 )) >= 0 )
                    {
                        close(slave);
                        (void) devindex++;
                        return 0;
                    }
                devindex++;
            }
            devindex = 0;
            (void) letter++;
        }
        return 1;
#   endif
#   endif
#   endif
#   endif
}


/****************************************************************************
**
*F  StartGapProcess( <name>, <argv> ) . . . start a gap subprocess using ptys
*/
#ifdef SYS_HAS_PID_T
    extern pid_t wait3();
#else
    extern int wait3();
#endif
extern int select();
extern int ioctl();

static void GapStatusHasChanged ()
{
#   ifdef SYS_HAS_UNION_WAIT
        union wait	w;
#   else
        int             w;
#   endif

    /* if the child was stopped return */
    if ( wait3( &w, WNOHANG | WUNTRACED, 0 ) != GapPID || WIFSTOPPED(w) )
	return;
    exit(1);
}

void StartGapProcess ( name, argv )
    char          * name;
    char          * argv[];
{
    char            c[8];    /* buffer for communication        */
    int             master;  /* pipe to GAP                     */
    int             slave;   /* pipe from GAP                   */
    int             n;       /* return value of 'select'        */
    int             i, j;    /* loop variables                  */
    struct timeval  timeout; /* time to wait for aknowledgement */
    struct fd_set   fds;     /* for 'select'                    */
#   ifdef SYS_HAS_TCSETAW
        struct termio   tst; /* old and new terminal state      */
#   else
        struct sgttyb   tst; /* old and new terminal state      */
#   endif

    /* construct the name of the pseudo terminal */
    ttydev = XtMalloc(strlen(SYS_TTYDEV)+1);  strcpy( ttydev, SYS_TTYDEV );
    ptydev = XtMalloc(strlen(SYS_PTYDEV)+1);  strcpy( ptydev, SYS_PTYDEV );

    /* open pseudo terminal for communication with gap */
    if ( GetMasterPty(&master) )
    {
        fputs( "open master failed\n", stderr );
        exit(1);
    }
    if ( (slave  = open( ttydev, O_RDWR, 0 )) < 0 )
    {
        fputs( "open slave failed\n", stderr );
        exit(1);
    }
#   if defined(DEBUG_ON) && !defined(att)
        if ( Debug )
	    fprintf( stderr,
	        "%04d:%s:StartGapProcess: master='%s', slave='%s'\n",
		__LINE__, __FILE__, ptydev ? ptydev : "unknown",
		ttydev ? ttydev : "unkown" );
#   endif
    XtFree(ttydev);
    XtFree(ptydev);
#   ifdef SYS_HAS_TCSETAW
        if ( ioctl( slave, TCGETA, &tst ) == -1 )
	{
	    fputs( "ioctl TCGETA on slave pty failed\n", stderr );
	    exit(1);
	}
        tst.c_cc[VINTR] = 0377;
        tst.c_cc[VQUIT] = 0377;
        tst.c_iflag    &= ~(INLCR|ICRNL);
        tst.c_cc[VMIN]  = 1;
        tst.c_cc[VTIME] = 0;
        tst.c_lflag    &= ~(ECHO|ICANON);
        if ( ioctl( slave, TCSETAW, &tst ) == -1 )
        {
	    fputs( "ioctl TCSETAW on slave pty failed\n", stderr );
	    exit(1);
	}
#   else
        if ( ioctl( slave, TIOCGETP, (char*)&tst ) == -1 )
        {
	    fputs( "ioctl TIOCGETP on slave pty failed\n", stderr );
	    exit(1);
        }
        tst.sg_flags |= RAW;
        tst.sg_flags &= ~ECHO;
        if ( ioctl( slave, TIOCSETN, (char*)&tst ) == -1 )
        {
            fputs( "ioctl on TIOCSETN slave pty failed\n", stderr );
	    exit(1);
	}
#endif

    /* set input to non blocking operation */
    if ( fcntl( master, F_SETFL, O_NDELAY ) < 0 )
    {
        fputs( "Panic: cannot set non blocking operation.\n", stderr );
        exit(1);
    }

    /* fork to gap, dup pipe to stdin and stdout */
    GapPID = fork();
    if ( GapPID == 0 )
    {
        dup2( slave, 0 );
        dup2( slave, 1 );
#       ifdef SYS_HAS_EXECV_CCHARPP
            execv( name, (const char**) argv );
#       else
            execv( name, argv );
#       endif
        write( 1, "@-", 4 );
        close(slave);
        _exit(1);
    }
    ToGap   = master;
    FromGap = master;

    /* check if the fork was successful */
    if ( GapPID == -1 )
    {
        fputs( "Panic: cannot fork to subprocess.\n", stderr );
        exit(1);
    }

    /* wait at least 60 sec before giving up */
    timeout.tv_sec  = 60;
    timeout.tv_usec = 0;

    /* wait for an aknowledgement (@P) from the gap subprocess */
    j = 0;
    while ( j < 2 )
    {

        /* set <FromGap> port for listen */
        for ( i = FromGap/sizeof(fds.fds_bits); 0 <= i; i-- )
            fds.fds_bits[i] = 0;
        fds.fds_bits[FromGap/sizeof(fds.fds_bits)] =
                                   ( 1 << (FromGap % sizeof(fds.fds_bits)) );

        /* use 'select' to check port */
        if ( (n = select(FromGap+1, &fds, 0, 0, &timeout)) == -1 )
        {
            kill( GapPID, SIGKILL );
            perror("select failed");
            exit(1);
        }
        else if ( n == 0 )
        {
            kill( GapPID, SIGKILL );
            fputs("Panic: cannot establish communication with gap.", stderr);
            exit(1);
        }
        else
            ReadGap( &(c[j++]), 1 );
    }

    /* check if we got "@P" */
    if ( strncmp( c, "@p", 2 ) )
    {
        if ( ! strncmp( c, "@-", 2 ) )
        {
            fputs( "Panic: cannot start subprocess ", stderr );
            fputs( name, stderr );
            fputs( ".\n", stderr );
        }
        else
        {
            strcpy( c+3, "'\n" );
            fputs( "Panic: cannot talk with gap, got '", stderr );
            fputs( c, stderr );
            kill( GapPID, SIGKILL );
        }
        exit(1);
    }

    /* if the gap dies,  stop program */
    signal( SIGCHLD, GapStatusHasChanged );
}
