/*
 * Interface to BSD-style (SYSV-style if -D_USE_STREAMS) Pseudo-ttys.
 *
 * You may want to lookup the pty ioctl's:
 *
 * HP-UX Only!:
 *     TIOCTRAP    - (disabled default) => no exceptional selects will happen.
 *                 = enabled => select detects open and close 
 *		     slave hangs on open or ioctl until master acks.
 *		     non-termio ioctl's detected.
 *		     termio ioctl's detected if TIOCTTY disabled.
 *     TIOCTTY     - enabled => termio ioctl's valid on slave
 *                              termio ioctl's detected only if TIOCMONITOR
 *                 - disabled => TIOCTRAP detects termio ioctl's.
 *     TIOCMONITOR - enabled  => termio ioctls detected if TIOCTRAP & TIOCTTY
 *
 *     if TIOCTRAP is enabled, then traps must be ack'd by a TIOCREQCHECK,
 *     TIOCREQSET pair in sequence.
 *
 *     TIOCSIGMODE - TIOCSIGBLOCK - Caught Slave signals are posponed on ioctl
 *                   TIOCSIGABORT - All slave signals return EINTR on ioctl
 *                   TIOSIGNORMAL - default, restarted requests => a new ioctl
 *
 * None of the above exists on any BSD implemenation I can find.  Some mention
 * is made of setting B0 baudrate causing the master to see a close.
 */
#undef _POSIX_SOURCE		/* this is definitely *not* posix source */
#include <unistd.h>		/* open close */
#include <stdlib.h>		/* free malloc */
#include <stdio.h>
#include <string.h>		/* strerror */
#include <errno.h>		/* errno */
#include <fcntl.h>

#if defined(DGUX)
#define _USE_STREAMS
#endif

#if defined(_USE_STREAMS)
#include <sys/stream.h>
#include <sys/stropts.h>	/* I_PUSH I_FIND */
#endif

#if defined(__sgi) && !defined(_USE_STREAMS)
#include <sys/types.h>
#include <sys/stat.h>		/* S_IRUSR S_IWUSR */
#endif

#include "pty.h"
#include "log.h"

#if defined(_USE_STREAMS) /* { */
#define MASTER_PTY	"/dev/ptmx"	/* __ALPHA __SOLARIS__ DGUX */
#define SLAVE_PTY	"/dev/pts/n"	/* __ALPHA n = 0,1,...,47 */
#define PTY_STREAM	"ptem"		/* see ptem(7) */
#define LDIS_STREAM	"ldterm"	/* SYSV line discipline */
#define COMPAT_STREAM	"ttycompat"	/* tty compatability stream */

#if defined(_OSF_SOURCE)
#undef  LDIS_STREAM
#define LDIS_STREAM	0		/* OSF1 includes line discipline */
#endif

#if defined(__SOLARIS__) /* { */
#undef	COMPAT_STREAM
#define COMPAT_STREAM	0		/* no tty compatability stream reqd */

/*
 * gcc gives an error on using ptsname.  The man page is ambiguous.
 */
#if defined(__STDC__) || defined(__cplusplus)
extern char *ptsname(int);		/* ptsname.3c says in stdio.h; isn't */
#else
extern char *ptsname();
#endif
#endif	/* } __SOLARIS__ */

#if defined(__sgi)
#undef MASTER_PTY
#define MASTER_PTY	"/dev/ptc"
#undef SLAVE_PTY
#define SLAVE_PTY	"/dev/ttyqn"	/* tty[q-z][0-99] */
#endif

#endif /* } _USE_STREAMS */

#if !defined(MASTER_PTY) /* { */
#if defined(__UNICOS__) /* { */			
#define MASTER_PTY	"/dev/pty/0xx"		/* 0-132 */
#define SLAVE_PTY	"/dev/ttyp0xx" 		/* still incorrect. */
#endif/* } UNICOS */

#if defined(__sgi)	/* { IRIX */
#define MASTER_PTY	"/dev/ptc"    /* /dev/ptc==/dev/ptmx /dev/ptc[1234] */
#define SLAVE_PTY	"/dev/pts/n"	/* n = 0,1,...,12 */
	       /* IRIX man page also says: /dev/tty[qrstuvwxyz][0-99] */
#endif	/* } __sgi */
#endif /* } !MASTER_PTY */

#if !defined(MASTER_PTY)
#define USE_DEFAULT
#define MASTER_PTY	"/dev/ptyxx"
#endif
#if !defined(SLAVE_PTY)
#define SLAVE_PTY	"/dev/ttyxx"
#endif

static char defSlave[]    = SLAVE_PTY;
static char defMaster[]   = MASTER_PTY;
static char ptynum[]      = "0123456789abcdef";
static char ptylet[]      = "pqrs";

#define LOC	((sizeof defMaster) - 2)	/* index of last character */

extern int debug;
extern char *progName;

#if defined(_USE_STREAMS) /* { */
int pushStream(fd, module) int fd; char *module; {
   int res;
   
   if (module != (char *) 0) {
      res = ioctl(fd, I_FIND, module);	/* see streamio(7) sys/stropt.h */
      if (res < 0) {
	 log("%s: ioctl(%d, I_FIND, \"%s\") failed--%s\n", 
	    progName, fd, module, ERRMSG);
	 close(fd);
	 return -1;
      }
      if (res == 0) {	/* module not already pushed */
	 res = ioctl(fd, I_PUSH, module);
	 if (res < 0) {
	    log("%s: pushStream(%d, \"%s\") failed--%s\n", 
	       progName, fd, module, ERRMSG);
	    close(fd);
	    return -1;
	 }
      }
   }
   return fd;
}
#endif /* } */

/*
 * This funcion is necessary so that re-open's of the slave pty can 
 * make sure to initialize it correctly on some machines which will lose
 * it if the slave is closed and  reopened again.
 */
int openSlavePty(sname, mode) char *sname; int mode; {
   int res, fd = open(sname, mode, 0);
   if (fd < 0) {
      log("%s: openSlavePty(\"%s\", 0%o) failed--%s\n", 
	 progName, sname, mode, ERRMSG);
      return -1;
   }
#if defined(_USE_STREAMS) /* { */
   fd = pushStream(fd, PTY_STREAM);
   if (fd >= 0) {
      fd = pushStream(fd, LDIS_STREAM);
      if (fd >= 0) {
	 fd = pushStream(fd, COMPAT_STREAM);
      }
   }
#endif /* } _USE_STREAMS */
   return fd;
}

/*
 * Find an available unused pty.  Open the master side and slave side.
 * Return the fd and names for the master and slave sides.
 *
 * The reasons you must open the slave are:
 *   - The open must be atomic to opening the master to prevent another
 *     process from grabbing the slave and stealing control.
 *
 *   - The slave open may be impossible because of permissions.  The slave
 *     pty changes ownership when login processes grab it.
 *
 * This can cause problems because the slave could become the new controlling
 * terminal for this process under certain conditions.
 * HP-UX9.0:pty(7)manPage:
 *
 *    The slave side of the pty interprets opening or closing the master
 *    side as a modem connection or disconnection on a real terminal.  Only
 *    one open to the master side of a pty is permitted.  An attempt to open
 *    an already open master side returns -1 and sets the external variable
 *    errno to EBUSY.  An attempt to open the master side of a pty that has
 *    a slave with an open file descriptor returns -1 and sets errno to
 *    EBUSY...An ioctl() request made on the slave side of a pty after
 *    the master side is closed returns -1 and sets the external variable
 *    errno to EIO.
 *
 * Ultrix:pty(4): The slave device can be opened multiple times, while the 
 *    master half can be opened only once.
 *
 * Thus, at the instant the open succeeds, we are guarenteed that the slave
 * side was not open by any other process, and we are the exclusive holder
 * of the open fd for the master side.  
 *
 * Two questions remain:  What happens if a read occurs on the master
 * before any process has opened the slave?  and, how can the master prevent
 * multiple opens on the slave?  On my machine, the only thing that prevents
 * a sufficiently determined process from opening the slave (even if it's 
 * already open by another process as the controlling terminal) is the 
 * permissions on the slave pty filename.
 *
 * The DEC alpha provides an openpty(3) and forkpty(3) call, but they are not 
 * available on HP-UX or Ultrix.  SGI IRIX provides something called  _getpty.
 *
 * Actually, the slave opening code is not really required. All we want to do 
 * is make sure that the slave has the correct permisisons.
 */
int openPty(mode, slaveName, masterName, size, slaveFd)
   int mode;
   char *slaveName, *masterName;
   unsigned size;
   int *slaveFd;
{
   register char *letp = ptylet, *nump; 
   char *sname = defSlave, *mname = defMaster;
   int fd = -1, res;

#if defined(__SOLARIS__) || defined(DGUX) /* { */
   fd = open(mname, mode);
   if (debug > 1) {
      log("%s: open(\"%s\", 0%o) returned %d\n", progName, mname, mode, fd);
   }
   if (fd >= 0) {
      /* grantpt does chmod(mfd, 0620); chown(mfd, geteuid()), via a fork.
       * Failure of grantpt is not usually fatal; if we're root already 
       * then we'll do it later explicitly anyway 
       */
      res = grantpt(fd); 
      if (debug > 1) {
	 log("%s: grantpt(%d) returned %d\n", progName, fd, res);
      }
      res = unlockpt(fd); /* clear lock on slave w/master fd for open() */
      if (debug > 1) {
	 log("%s: unlockpt(%d) returned %d\n", progName, fd, res);
      }
      if (res < 0) goto ptyFail;
      sname = ptsname(fd); /*  <stdio.h> "/dev/pts/N", N = non-negative int */
      if (sname == 0) {
	 log("%s: ptsname(%d) failed--%s\n", progName, fd);
	 goto ptyFail;
      }
      if (debug > 1) {
	 log("%s: ptsname(%d) returned \"%s\"\n", progName, fd, sname);
      }
      if (slaveFd != (int *) 0) {
	 *slaveFd = openSlavePty(sname, mode);
	 if (*slaveFd < 0) goto ptyFail;
      }
   }
#endif /* } __SOLARIS__ || DGUX  */
#if defined(__sgi) && !defined(_USE_STREAMS)	/* { */
   sname = _getpty(&fd, mode, S_IRUSR | S_IWUSR | S_IWGRP, 1);
   if (sname == 0) {
      log("%s: _getpty(&fd, %o, %o, 1) failed--%s\n", 
         progName, mode, S_IRUSR | S_IWUSR | S_IWGRP, ERRMSG);
      goto ptyFail;
   }
   if (slaveFd != (int *) 0) {
      *slaveFd = openSlavePty(sname, mode);
      if (*slaveFd <= 0) goto ptyFail;
   }
#endif 	/* } __sgi */
#if defined(USE_DEFAULT)	/* { */
   do {
      mname[LOC-1] = sname[LOC-1] = *letp;
      nump = ptynum;
      do {
	 mname[LOC] = sname[LOC] = *nump;
	 fd = open(mname, mode, 0);
	 if (fd >= 0) {
	    if (slaveFd == (int *) 0) goto gotPty;
	    *slaveFd = openSlavePty(sname, mode);
	    if (*slaveFd >= 0) goto gotPty;
	    close(fd);
	    fd = -1;
	 }
      } while (*++nump != '\0');
   } while (*++letp != '\0');
#endif	/* } USE_DEFAULT */

gotPty:
   if (fd >= 0) {
      if (masterName != (char *) 0) {
	 strncpy(masterName, mname, size);
      }
      if (slaveName != (char *) 0) {
	 strncpy(slaveName, sname, size);
      }
   }
ptyRtn:
   return fd;
ptyFail:
   close(fd);
   fd = -1;
   goto ptyRtn;
}
