/*
 *      openpty - open a pseudo-terminal
 *
 */
static char rcsid[] = "$Id: openpty.c 1.2 1994/06/06 22:38:20 Ezra_Story Exp Ezra_Story $";

#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#ifdef SVR4
#include <stropts.h>
#include <sys/termios.h>
#endif
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#ifdef STRING
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef SYSSELECT
#include <sys/select.h>
#endif
#include <stdlib.h>
#include "openpty.h"
#include "lists.h"
#include "mydefs.h"

proto fildes_t forkpty();
proto closepty();

/* null fix */
#ifndef NULL
#define NULL 0
#endif

/* hp-ux fix */
#ifndef TIOCNOTTY
#define TIOCNOTTY (_IO('t', 113))
#endif

/* old to new NDELAY fix */
#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif

#ifdef USEGETPTY
openpty(pt)
struct ptydesc *pt;
{
    char *line;

    line = _getpty(&pt->pt_pfd, O_RDWR|O_NDELAY, 0600,0);
    if (line == 0) return(-1);
    if ((pt->pt_tfd = open(line,O_RDWR)) < 0)
        {
        (void)close(pt->pt_pfd);
        return(-1);
        }
    pt->pt_tname = line;
    return(0);
}

#else
#ifdef SYSV
openpty(pt)
struct ptydesc *pt;
{
  void (*savesig)(int);

    if ((pt->pt_pfd = open("/dev/ptmx", O_RDWR)) == -1) return (-1);
    savesig = signal(SIGCHLD, SIG_DFL);

    if ((pt->pt_tname = ptsname(pt->pt_pfd)) == NULL || unlockpt(pt->pt_pfd) || grantpt(pt->pt_pfd))
        {
        signal(SIGCHLD, savesig);
        close(pt->pt_pfd);
        return (-1);
        }

    signal(SIGCHLD, savesig);

#ifdef USETCFLUSH
    tcflush(pt->pt_pfd, TCIOFLUSH);
#else
#ifdef TIOCFLUSH
    (void)ioctl(pt->pt_pfd, TIOCFLUSH, (char *) 0);
#endif
#endif

    if ((pt->pt_tfd = open(pt->pt_tname,O_RDWR)) < 0)
        {
        close (pt->pt_pfd);
        return (-1);
        }

#ifdef SVR4
    ioctl(pt->pt_tfd, I_PUSH, "ptem");
    ioctl(pt->pt_tfd, I_PUSH, "ldterm");
    ioctl(pt->pt_tfd, I_PUSH, "ttcompat");
#endif

    return(0);
}
#else

#ifdef __hpux
static  char masterline[]="/dev/ptym/ptyXY";
static  char slaveline[]="/dev/pty/ttyXY";
static  char *first ="pqrstuv";
#else                           /*  not hpux. */
static  char masterline[]="/dev/ptyXY";
static  char *first ="pqrstuvwxyz";
static  char slaveline[]="/dev/ttyXY";
#endif
#define SFIRST  (sizeof (slaveline) - 3)
#define SSECOND (sizeof (slaveline) - 2)
#define MFIRST  (sizeof (masterline) - 3)
#define MSECOND (sizeof (masterline) - 2)
static  char ptyarray[] = "0123456789abcdef";

openpty(pt)
struct ptydesc *pt;
{
  char *ptr;
  int  i,c;
  struct stat statbuff;

  /* loop through all possible combinations */
  ptr = first;
  for (c = *ptr; (c = *ptr); ++ptr)
    {
    masterline[MFIRST] = c;
    masterline[MSECOND] = '0';

    /* no pty's on this bank /dev/ptyX0? */
    if (stat (masterline, &statbuff) < 0) continue;

    /* try to find an openable master pty */
    for (i = 0; i < 16; ++i)
        {
        masterline[MSECOND] = ptyarray[i];
        if ((pt->pt_pfd = open (masterline, O_RDWR, 0)) > 0) break;
        }

    /* if we found one check the slaves */
    if (i != 16)
        {
        slaveline[SFIRST] = masterline[MFIRST];
        slaveline[SSECOND] = masterline[MSECOND];

        /* can we open it? */
        if ((pt->pt_tfd = open (slaveline, O_RDWR, 0)) < 0)
            {
            close(pt->pt_pfd);
            continue;
            }
        break;
        }

    }

    if (c==0) return(-1);
    pt->pt_tname = slaveline;
    pt->pt_pname = masterline;

    return(0);
}
#endif
#endif

fildes_t
forkpty(w)
WIN *w;
{
    struct ptydesc pt;
    fildes_t       datafd,fd;
    int            pid;
    char           *shell;
#ifdef SYSV
    void (*savesig)(int);
#endif

    if (openpty(&pt) == -1) return(-1);

    datafd = pt.pt_pfd;

#ifdef SYSV
    savesig = signal(SIGCLD, SIG_DFL);
#endif

    while ((pid = fork()) < 0) sleep(5);
    if (!pid)
        {
        /* this is the pty process */

        /* reset signals to defaults */
        (void)signal(SIGHUP, SIG_DFL);
        (void)signal(SIGINT, SIG_DFL);
        (void)signal(SIGQUIT, SIG_DFL);
        (void)signal(SIGTERM, SIG_DFL);
        (void)signal(SIGTSTP, SIG_IGN);
        (void)signal(SIGCHLD, SIG_DFL);

        /* Set the user/group ids of process */
        (void)setuid(getuid());
        (void)setgid(getgid());

        /* break terminal affiliation */
        /* create a new session with the child process as the group leader */
        /* (mod for hp/ux (and perhaps others) by Maurice LeBrun */
#ifdef SYSV
        setpgrp();
#else
#ifdef USESETSID
        setsid();
#else
        (void)ioctl(fd = open("/dev/tty",O_RDWR,0),(int)TIOCNOTTY, (char *)0);
        close(fd);
#endif
#endif

        /* close all the shared fd's except for the slave pty */
        for (fd=0; fd < nfds; fd++)
            if (fd != pt.pt_tfd) (void)close(fd);

        /* open our own copy of slave pty */
        fd = open(pt.pt_tname, O_RDWR);

        /* close parent's copy of slave pty */
        (void)close(pt.pt_tfd);

        /* redirect slave side (t) to stdin/out/err */
        if (fd != 0) (void)dup2(fd, 0);
        if (fd != 1) (void)dup2(fd, 1);
        if (fd != 2) (void)dup2(fd, 2);

        /* make sure we are controlling, and close tmp fd */
        w->pr = getpid();
#ifdef TIOCSPGRP
        ioctl(fd, TIOCSPGRP, (char *)&w->pr);
#else
        tcsetpgrp(fd, w->pr);
#endif
        if (fd > 2) (void)close(fd);

        /* set to normal tty mode */
        TtyRestore();

        /* get shell */
        if (!(shell = getenv("SHELL"))) shell = "/bin/sh";

        /* do it! */
        execl(shell, shell, (char *)0);
        exit(1);
        }
#ifdef SYSV
    signal(SIGCLD, savesig);
#endif

/* race condition occurs here.. right now we just use a terrible hack */
sleep(2);

    /* parent.. just close slave side for us */
    (void)close(pt.pt_tfd);

    /* set nonblocking IO */
    (void)fcntl(datafd, F_SETFL, FNDELAY);

    /* set select() mask */
    FD_SET(datafd,&selmask[0].sm_rd);
    w->fd = datafd;
    return(datafd);
}

closepty(w)
WIN *w;
{
    FD_CLR(w->fd, &selmask[0].sm_rd);
    (void)close(w->fd);
}

