/* vim: set shiftwidth=4 tabstop=8 softtabstop=4: */
/* $Id: libspt.c,v 1.34 2003/12/12 07:36:35 shinra Exp $ */

#define IN_LIBSPT
#include "sptprivate.h"
#include <sys/wait.h>
#include <time.h>
#include <stdio.h>  /* for perror() */
#include <signal.h>
#ifdef HAVE_PTY_SVR4
#include <sys/stropts.h>
#endif

struct spt_handle_tag {
    connection *pconn;
    char *ttyname;    /* slave */
    unsigned int ptytype;
    int masterfd;
    int agentfd;
    int reply;		    /* refered outside wait_for_reply */
    int msgprocerr;	    /* used only in wait_for_reply */
    pid_t agentpid;
    dispatch_table *client_tab;
    spt_cleanup_proc cleanup;
    void *cleanuparg;
    char *utmphost;
    pid_t utmppid;
    int exit_st;
#ifdef HAVE_PTY_SVR4
    int grantpt_failed;
#endif
};

typedef struct {
    int type;
    int (*openproc) (spt_handle *phandle);
} ptyopen_table_item;

#ifdef HAVE_PTY_SVR4
static int open_pty_svr4(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_BSD
static int open_pty_bsd(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_PTC
static int open_pty_ptc(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_CLONE
static int open_pty_clone(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_SGI4
static int open_pty_sgi4(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_NUMERIC
static int open_pty_numeric(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_CRAY
static int open_pty_cray(spt_handle *phandle);
#endif
#ifdef HAVE_PTY_CYGWIN
static int open_pty_cygwin(spt_handle *phandle);
#endif

static int wait_for_reply(spt_handle *phandle);
static int send_and_wait(spt_handle *phandle, const msgunion_with_type *pmsg);
static int try_dup2(int oldfd, int newfd);
static void shutdown_agent(pid_t pid);
static int waitpid_with_timeout(pid_t pid, const struct timespec *ptimeout);
static void disconnect_before_close(spt_handle *phandle);
static int start_agent(spt_handle *phandle);
static int trivialcmd(spt_handle *phandle, const msgunion_with_type *pmsg,
	int errreply, int mappederr);
static MSGPROC(msg_result_proc);
static MSGPROC(msg_proto_violation_proc);

const dispatch_table_item client_tab_items[] = {
    { MSG_RESULT, &msg_result_proc },
    { MSG_PROTO_VIOLATION, &msg_proto_violation_proc },
};

char *const agent_args[] = {
    AGENT_BASENAME,
    NULL
};

static ptyopen_table_item ptyopen_tab[] = {
#ifdef HAVE_PTY_PTC
    { PTYTYPE_PTC, &open_pty_ptc },
#endif
#ifdef HAVE_PTY_CLONE
    { PTYTYPE_CLONE, &open_pty_clone },
#endif
#ifdef HAVE_PTY_SGI4
    { PTYTYPE_SGI4, &open_pty_sgi4 },
#endif
#ifdef HAVE_PTY_NUMERIC
    { PTYTYPE_NUMERIC, &open_pty_numeric },
#endif
#ifdef HAVE_PTY_CRAY
    { PTYTYPE_CRAY, &open_pty_cray },
#endif
#ifdef HAVE_PTY_SVR4
    { PTYTYPE_SVR4, &open_pty_svr4 },
#endif
#ifdef HAVE_PTY_CYGWIN
    { PTYTYPE_CYGWIN, &open_pty_cygwin },
#endif
#ifdef HAVE_PTY_BSD
    { PTYTYPE_BSD, &open_pty_bsd },
#endif
};

static char *agent_path = NULL;

SPT_EXPORT(int)
spt_open_pty(spt_handle **pphandle, int *pmasterfd,
	spt_cleanup_proc cleanup, void *cleanuparg)
{
    spt_handle *phandle;
    dispatch_table *client_tab;
    int retval;
    unsigned int i;
    msgunion_with_type msg;
    
    if ((phandle = MALLOC(sizeof(spt_handle))) == NULL)
	return SPT_E_NOMEM;
    phandle->cleanup = cleanup;
    phandle->cleanuparg = cleanuparg;
    phandle->utmppid = getpid();
    phandle->exit_st = 0;

    retval = SPT_E_NOMEM;
    if ((phandle->utmphost = STRDUP("(unknown)")) == NULL)
	goto err1;

    if ((client_tab = MALLOC(sizeof(dispatch_table))) == NULL)
	goto err2;

    client_tab->items = client_tab_items;
    client_tab->n_items = ARRAYLEN(client_tab_items);
    client_tab->defproc = NULL;
    client_tab->arg = phandle;
    phandle->client_tab = client_tab;
    for (i = 0; i < ARRAYLEN(ptyopen_tab); ++i) {
	phandle->ptytype = ptyopen_tab[i].type;
	retval = (*ptyopen_tab[i].openproc) (phandle);
	if (retval)
	    continue;
	retval = start_agent(phandle);
	if (!retval)
	    goto agent_ok;
	FREE(phandle->ttyname);
	close(phandle->masterfd);
    }
    goto ptyfail;
agent_ok:
#ifdef HAVE_PTY_SVR4
    if (phandle->ptytype == PTYTYPE_SVR4) {
	retval = phandle->grantpt_failed ? SPT_E_CHOWN_FAIL : SPT_E_NONE;
	goto success;
    }
#endif
#ifdef HAVE_PTY_CYGWIN
    if (phandle->ptytype == PTYTYPE_CYGWIN)
	goto success;
#endif
#ifdef HAVE_PTY_SGI4
    if (phandle->ptytype == PTYTYPE_SGI4)
	goto success;
#endif
    msg.msgtype = MSG_INIT_TTY;
    retval = send_and_wait(phandle, &msg);
    if (retval == SPT_E_NONE) {
	if (phandle->reply == ERR_CHOWN_FAIL)
	    retval = SPT_E_CHOWN_FAIL;
	else if (phandle->reply != ERR_NONE)
	    retval = SPT_E_AGENT_FATAL;
    }
    if (retval == SPT_E_NONE || retval == SPT_E_CHOWN_FAIL)
	goto success;

    if (retval != SPT_E_AGENT_DEAD && retval != SPT_E_AGENT_FATAL) {
	msg.msgtype = MSG_DISCONNECT;
	send_and_wait(phandle, &msg);
    }
    disconnect_before_close(phandle);
    close(phandle->masterfd);
    FREE(phandle->ttyname);
    goto ptyfail;

success:
#ifdef HAVE_REVOKE
    /* XXX: silently fail */
    revoke(phandle->ttyname);
#endif
    *pphandle = phandle;
    *pmasterfd = phandle->masterfd;
    return retval;

ptyfail:
    FREE(client_tab);
err2:
    FREE(phandle->utmphost);
err1:
    FREE(phandle);
    return retval;
}

SPT_EXPORT(int)
spt_close_pty(spt_handle *phandle)
{
    int r;
    int retval = SPT_E_CLEANUP_FAIL;
    connection *pconn = phandle->pconn;

    if (pconn) {
	msgunion_with_type msg;
	msg.msgtype = MSG_DISCONNECT;
	r = send_and_wait(phandle, &msg);
	if (!r && phandle->reply == ERR_NONE)
	    retval = SPT_E_NONE;
	disconnect_before_close(phandle);
    }
    close(phandle->masterfd);
    FREE(phandle->ttyname);
    FREE((dispatch_table *)phandle->client_tab);
    FREE(phandle->utmphost);
    FREE(phandle);
    return retval;
}

/*
 * If received MSG_RESULT, returns SPT_E_NONE and the result is stored
 * into phandle->reply. Otherwise, returns an appropreate error.
 * None of SPT_IE_* is returned.
 */
static int
wait_for_reply(spt_handle *phandle)
{
    int retval;
    connection *pconn = phandle->pconn;

    while ((retval = spt_p_do_read(pconn)) == SPT_IE_AGAIN);
    if (retval == SPT_E_NONE)
	retval = phandle->msgprocerr;
    if (retval == SPT_IE_PEERDEAD)
	return SPT_E_AGENT_DEAD;
    else if (retval == SPT_IE_VIOLATION)
	return SPT_E_AGENT_FATAL;
    else
	return retval;
}

static int
send_and_wait(spt_handle *phandle, const msgunion_with_type *pmsg)
{
    int retval;

    DEBUG(fprintf(stderr, "send_and_wait: type=%d\n", pmsg->msgtype));
    retval = spt_p_send_msg(phandle->pconn, pmsg);
    if (retval) {
	if (retval == SPT_IE_PEERDEAD)
	    retval = SPT_E_AGENT_DEAD;
	return retval;
    }
    return wait_for_reply(phandle);
}

static int
try_dup2(int oldfd, int newfd)
{
    int r;
    r = dup2(oldfd, newfd);
    if (r < 0)
	_exit(1);
    return r;
}

static int
start_agent(spt_handle *phandle)
{
    int r;
    int fdpair[2];
    pid_t pid;
    int retval;

#ifdef SPT_USE_SOCKET
    r = socketpair(PF_UNIX, SOCK_STREAM, 0, fdpair);
#else
    /* pipes must support bidirectional read/write */
    r = pipe(fdpair);
#endif
    if (r) {
	close(phandle->masterfd);
	FREE(phandle->ttyname);
	return SPT_E_RESOURCE;
    }
    retval = spt_p_create_conn(
	    &phandle->pconn, phandle->client_tab, fdpair[0]);
    if (retval)
	goto err1;

    pid = fork();
    if (pid < 0) {
	retval = SPT_E_RESOURCE;
	goto err2;
    } else if (pid == 0) {
	/* child */
	if (phandle->cleanup)
	    (*phandle->cleanup) (phandle->cleanuparg);  /* must do first */
	close(fdpair[0]);
	if (phandle->masterfd == AGENT_CONN_FD) {
	    if (fdpair[1] == AGENT_MASTER_FD) {
		int tmpfd;
		tmpfd = dup(AGENT_CONN_FD);
		if (tmpfd < 0)
		    _exit(1);
		try_dup2(AGENT_MASTER_FD, AGENT_CONN_FD);
		try_dup2(tmpfd, AGENT_MASTER_FD);
		close(tmpfd);
	    } else {
		/* always fdpair[1] != AGENT_CONN_FD */
		try_dup2(AGENT_CONN_FD, AGENT_MASTER_FD);
		try_dup2(fdpair[1], AGENT_CONN_FD);
		close(fdpair[1]);
	    }
	} else if (fdpair[1] == AGENT_MASTER_FD) {
	    /* always phandle->masterfd != AGENT_MASTER_FD */
	    try_dup2(AGENT_MASTER_FD, AGENT_CONN_FD);
	    try_dup2(phandle->masterfd, AGENT_MASTER_FD);
	    close(phandle->masterfd);
	} else {
	    if (phandle->masterfd != AGENT_MASTER_FD) {
		try_dup2(phandle->masterfd, AGENT_MASTER_FD);
		close(phandle->masterfd);
	    }
	    if (fdpair[1] != AGENT_CONN_FD) {
		try_dup2(fdpair[1], AGENT_CONN_FD);
		close(fdpair[1]);
	    }
	}
	execv(agent_path ? agent_path : AGENT_PATH, agent_args);
	/* exec failure */
	perror("cannot execute sptagent");
	_exit(1);
	/* NOTREACHED */
    } else {
	/* parent */
	msgunion_with_type msg;

	close(fdpair[1]);
	msg.msgtype = MSG_CONNECT;
	msg.mu.mu_connect.version = SPT_PROTO_VERSION;
	msg.mu.mu_connect.ttyname = phandle->ttyname;
	msg.mu.mu_connect.ptytype = phandle->ptytype;
	retval = send_and_wait(phandle, &msg);
	/*
	 * If the connection was closed before first reply to MSG_CONNECT, it
	 * was probably because exec() failed. But it also may be because
	 * the agent terminated abnormally, or something other than sptagent
	 * was placed in AGENT_PATH and it closed the pipe. So, we must check
	 * whether my child is alive or not, and call wait() properly.
	 * Anyway, we return SPT_E_AGENT_EXEC, which is the best assumption.
	 */
	if (retval) {
	    if (retval == SPT_IE_PEERDEAD)
		retval = SPT_E_AGENT_EXEC;
	    goto err2;
	}
	if (phandle->reply == ERR_VERSION_MISMATCH
		|| phandle->reply == ERR_UNKNOWN_PTYTYPE)
	    retval = SPT_E_AGENT_VERSION;
	else if (phandle->reply == ERR_WRONG_INST)
	    retval = SPT_E_AGENT_INST;
	else if (phandle->reply != ERR_NONE)
	    retval = SPT_E_AGENT_FATAL;
	if (retval)
	    goto err2;

	phandle->agentfd = fdpair[0];
	phandle->agentpid = pid;
	return SPT_E_NONE;
err2:
	shutdown_agent(pid);
    }
err1:
    close(fdpair[0]);
    return retval;
}

static void
shutdown_agent(pid_t pid)
{
    struct timespec timeout;

    timeout.tv_sec = 0;
    timeout.tv_nsec = 100 * 1000 * 1000; 
    if (waitpid_with_timeout(pid, &timeout)) {
	kill(pid, SIGTERM);
	if (waitpid_with_timeout(pid, &timeout)) {
	    kill(pid, SIGKILL);
	    timeout.tv_sec = 2;
	    timeout.tv_nsec = 0;
	    waitpid_with_timeout(pid, &timeout);
	    /* return anyway */
	}
    }
}

static RETSIGTYPE
sigchld_handler(int dummy)
{
    SIGNAL_RETURN;
}

static int
waitpid_with_timeout(pid_t pid, const struct timespec *ptimeout)
{
    struct timespec work;
    int r = 1;
    sighandler_type old_sigchld;

    /*
     * If waitpid() returns -1, agent zombie was not created (SA_NOCLDWAIT)
     * or wait()ed by someone (especially caller's SIGCHLD handler).
     * So we wait agent only if waitpid() returns 0. It always means
     * that agent is still alive.
     *
     * FIXME: how to determine if pselect() is implemented and sane?
     * I don't want to use SIGALRM for implement timeout to sigsuspend().
     */
    if (waitpid(pid, NULL, WNOHANG))
	return 0;
    TRAP_SIGNAL(SIGCHLD, &sigchld_handler, &old_sigchld);
    work = *ptimeout;
    while (nanosleep(&work, &work) && errno == EINTR) {
	if (waitpid(pid, NULL, WNOHANG)) {
	    r = 0;
	    break;
	}
    }
    RESTORE_SIGNAL(SIGCHLD, &old_sigchld);
    return r;
}

SPT_EXPORT(void)
spt_detach_handle(spt_handle *phandle)
{
    if (phandle->pconn) {
	spt_p_destroy_conn(phandle->pconn);
	close(phandle->agentfd);
    }
    FREE(phandle->ttyname);
    FREE((dispatch_table *)phandle->client_tab);
    FREE(phandle->utmphost);
    FREE(phandle);
}

SPT_EXPORT(int)
spt_open_slave(const spt_handle *phandle)
{
    return open(phandle->ttyname, O_RDWR | O_NOCTTY);
}

SPT_EXPORT(int)
spt_init_slavefd(const spt_handle *phandle, int slavefd)
{
    /* XXX: screen requires !linux, !sgi, !__osf__ and !M_UNIX */
#if defined(HAVE_PTY_SVR4) && defined(I_PUSH)
#if 0	/* only rxvt and mlterm do this */
#ifdef HAVE_ISASTREAM
    if (isastram(slavefd))
#endif
#endif
    {
	/*
	 * Check whether succeeded or not by isatty()?
	 * Solarks 8 returns 0 if "ldterm" is not successfully pushd.
	 * But I don't know its portability and strict semantics.
	 */
	if (ioctl(slavefd, I_PUSH, "ptem")
		/* XXX: "consem" fails on solaris 8. */
#if !defined(SVR4) && !(defined(SYSV) && defined(i386)) && 0
		|| (!getenv("CONSEM") && ioctl(slavefd, I_PUSH, "consem"))
#endif
		|| ioctl(slavefd, I_PUSH, "ldterm")
#ifndef __hpux
		|| ioctl(slavefd, I_PUSH, "ttcompat")
#endif
		)
	    return SPT_E_TTYINIT_FAIL;
    }
#endif /* HAVE_PTY_SVR4 && I_PUSH */
    return SPT_E_NONE;
}

SPT_EXPORT(int)
spt_get_slavename(const spt_handle *phandle, char **pname)
{
    char *res = STRDUP(phandle->ttyname);
    if (res == NULL)
	return SPT_E_NOMEM;
    *pname = res;
    return SPT_E_NONE;
}

SPT_EXPORT(void)
spt_free_obj(void *pobj)
{
    SAFE_FREE(pobj);
}

SPT_EXPORT(int)
spt_utmp_set_host(spt_handle *phandle, const char *host)
{
    char *newhost = STRDUP(host);
    if (newhost == NULL)
	return SPT_E_NOMEM;
    FREE(phandle->utmphost);
    phandle->utmphost = newhost;
    return SPT_E_NONE;
}

SPT_EXPORT(int)
spt_utmp_set_pid(spt_handle *phandle, pid_t pid)
{
    phandle->utmppid = pid;
    return SPT_E_NONE;
}

SPT_EXPORT(int)
spt_utmp_set_exit(spt_handle *phandle, int exit_st)
{
    phandle->exit_st = exit_st;
    return SPT_E_NONE;
}

static int
trivialcmd(spt_handle *phandle, const msgunion_with_type *pmsg,
	int errreply, int mappederr)
{
    int retval;

    if (phandle->pconn == NULL)
	return mappederr;
    retval = send_and_wait(phandle, pmsg);
    if (retval == SPT_E_NONE) {
	if (phandle->reply == errreply)
	    retval = mappederr;
	else if (phandle->reply != ERR_NONE)
	    retval = SPT_E_AGENT_FATAL;
    }
    if (retval == SPT_E_AGENT_DEAD || retval == SPT_E_AGENT_FATAL)
	disconnect_before_close(phandle);
    return retval;
}

SPT_EXPORT(int)
spt_login_utmp(spt_handle *phandle)
{
    msgunion_with_type msg;
    msg.msgtype = MSG_LOGIN_UTMP;
    msg.mu.mu_utmp.host = phandle->utmphost;
    msg.mu.mu_utmp.pid = (long)phandle->utmppid;
    return trivialcmd(phandle, &msg, ERR_UTMP_FAIL, SPT_E_UTMP_FAIL);
}

SPT_EXPORT(int)
spt_logout_utmp(spt_handle *phandle)
{
    msgunion_with_type msg;
    msg.msgtype = MSG_LOGOUT_UTMP;
    msg.mu.mu_int = phandle->exit_st;
    return trivialcmd(phandle, &msg, ERR_UTMP_FAIL, SPT_E_UTMP_FAIL);
}

SPT_EXPORT(int)
spt_login_wtmp(spt_handle *phandle)
{
    msgunion_with_type msg;
    msg.msgtype = MSG_LOGIN_WTMP;
    msg.mu.mu_utmp.host = phandle->utmphost;
    msg.mu.mu_utmp.pid = (long)phandle->utmppid;
    return trivialcmd(phandle, &msg, ERR_UTMP_FAIL, SPT_E_UTMP_FAIL);
}

SPT_EXPORT(int)
spt_logout_wtmp(spt_handle *phandle)
{
    msgunion_with_type msg;
    msg.msgtype = MSG_LOGOUT_WTMP;
    msg.mu.mu_int = phandle->exit_st;
    return trivialcmd(phandle, &msg, ERR_UTMP_FAIL, SPT_E_UTMP_FAIL);
}

SPT_EXPORT(int)
spt_update_lastlog(spt_handle *phandle)
{
    msgunion_with_type msg;
    msg.msgtype = MSG_UPDATE_LASTLOG;
    msg.mu.mu_utmp.host = phandle->utmphost;
    msg.mu.mu_utmp.pid = (long)phandle->utmppid;
    return trivialcmd(phandle, &msg, ERR_UTMP_FAIL, SPT_E_UTMP_FAIL);
}

static void
disconnect_before_close(spt_handle *phandle)
{
    assert(phandle->pconn);
    spt_p_destroy_conn(phandle->pconn);
    phandle->pconn = NULL;
    close(phandle->agentfd);
    shutdown_agent(phandle->agentpid);
}

/* only for debug, programs except spttest should not rely on this */
int
spt_set_agent_path(const char *path)
{
    char *newpath;
    if (path) {
	newpath = NDEBUG_STRDUP(path);
	if (!newpath)
	    return SPT_E_NOMEM;
    } else
	newpath = NULL;
    free(agent_path);
    agent_path = newpath;
    return SPT_E_NONE;
}

static MSGPROC(msg_result_proc) {
    spt_handle *phandle = arg;
    assert(msgtype == MSG_RESULT);
    phandle->msgprocerr = SPT_E_NONE;
    phandle->reply = pmu->mu_int;
    return SPT_E_NONE;
}

static MSGPROC(msg_proto_violation_proc) {
    spt_handle *phandle = arg;
    assert(msgtype == MSG_PROTO_VIOLATION);
    phandle->msgprocerr = SPT_E_AGENT_FATAL;
    return SPT_E_NONE;
}

/* tested */
#ifdef HAVE_PTY_SVR4
static int
open_pty_svr4(spt_handle *phandle)
{
    int retval;
    int masterfd;
    const char *ptytmp;
    sighandler_type old_sigchld;

    masterfd = phandle->masterfd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (masterfd < 0)
	return SPT_E_NOTTY;
    TRAP_SIGNAL(SIGCHLD, SIG_DFL, &old_sigchld);
    phandle->grantpt_failed = grantpt(masterfd);
    RESTORE_SIGNAL(SIGCHLD, &old_sigchld);
    if (unlockpt(masterfd)) {
	retval = SPT_E_NOTTY;
	goto err1;
    }
    if ((ptytmp = ptsname(masterfd)) == NULL) {
	retval = SPT_E_NOTTY;
	goto err1;
    }
    if ((phandle->ttyname = STRDUP(ptytmp)) == NULL) {
	retval = SPT_E_NOMEM;
	goto err1;
    }
    return SPT_E_NONE;
err1:
    close(masterfd);
    return retval;
}
#endif /* HAVE_PTY_SVR4 */

/* tested */
#ifdef HAVE_PTY_BSD
static int
open_pty_bsd(spt_handle *phandle)
{
    int masterfd;
    char *name;
    char ch1, ch2;

    if ((name = STRDUP("/dev/pty??")) == NULL)
	return SPT_E_NOMEM;
    /*
     * pty range:
     * 4.4BSD and its family: "[p-sP-S][0-9a-v]"
     * Debian potato(and perhaps some its family): "v[0-9a-f]"
     * most of others: "[p-zP-T][0-9a-f]" or its subset
     */
    for (ch1 = 'p'; ch1 != 'U';) {
	name[8] = ch1;
	for (ch2 = '0'; ch2 <= 'v'; ++ch2) {
	    name[9] = ch2;
	    masterfd = open(name, O_RDWR | O_NOCTTY);
	    if (masterfd >= 0) {
		name[5] = 't';
		phandle->masterfd = masterfd;
		phandle->ttyname = name;
		return SPT_E_NONE;
	    }
	    if (errno == EIO) {
		if (ch2 == '9')
		    ch2 = 'a' - 1;
		continue;	/* already used */
	    }
	    else if (errno != ENOENT)
		goto notty;
	    if (ch2 == '0')
		break;		/* skip this ch1 */
	    else if (ch2 == 'g')
		break;		/*  seems to have only [0-9a-f] */
	    goto notty;
	}
	if (ch1 == 'z')
	    ch1 = 'P';
	else if (ch1 == 's' && ch2 == 'g')
	    ch1 = 'P';
	else if (ch1 == 'S' && ch2 == 'g')
	    break;
	else
	    ++ch1;
    }
notty:
    FREE(name);
    return SPT_E_NOTTY;
}
#endif /* HAVE_PTY_BSD */

/* UNTESTED */
#ifdef HAVE_PTY_PTC
/* AIX, SGI3 */
static int
open_pty_ptc_aix(spt_handle *phandle)
{
    int retval;
    int masterfd;
    const char *ptytmp;

    masterfd = phandle->masterfd = open("/dev/ptc", O_RDWR | O_NOCTTY);
    if (masterfd < 0)
	return SPT_E_NOTTY;
    if ((ptytmp = ttyname(masterfd)) == NULL) {
	retval = SPT_E_NOTTY;
	goto err1;
    }
    if ((phandle->ttyname = STRDUP(ptytmp)) == NULL) {
	retval = SPT_E_NOMEM;
	goto err1;
    }
    return SPT_E_NONE;
err1:
    close(masterfd);
    return retval;
}
#endif /* HAVE_PTY_PTC */

/* UNTESTED */
#ifdef HAVE_PTY_CLONE
/* HPUX */
static int
open_pty_clone(spt_handle *phandle)
{
    int retval;
    int masterfd;
    const char *ptytmp;

    masterfd = phandle->masterfd = open("/dev/ptym/clone", O_RDWR | O_NOCTTY);
    if (masterfd < 0)
	return SPT_E_NOTTY;
    if ((ptytmp = ptsname(masterfd)) == NULL) {
	retval = SPT_E_NOTTY;
	goto err1;
    }
    if ((phandle->ttyname = STRDUP(ptytmp)) == NULL) {
	retval = SPT_E_NOMEM;
	goto err1;
    }
    return SPT_E_NONE;
err1:
    close(masterfd);
    return retval;
}
#endif /* HAVE_PTY_CLONE */

/* UNTESTED */
#ifdef HAVE_PTY_SGI4
/* SGI4 _getpty() */
static int
open_pty_sgi4(spt_handle *phandle)
{
    int retval;
    int masterfd;
    const char *ptytmp;
    sighandler_type old_sigchld;

    TRAP_SIGNAL(SIGCHLD, SIG_DFL, &old_sigchld);
    ptytmp = _getpty(&masterfd, O_RDWR | O_NOCTTY | O_NDELAY, 0622, 0);
    RESTORE_SIGNAL(SIGCHLD, &old_sigchld);
    if (masterfd < 0)
	return SPT_E_NOTTY;
    if ((phandle->ttyname = STRDUP(ptytmp)) == NULL) {
	retval = SPT_E_NOMEM;
	goto err1;
    }
    phandle->masterfd = masterfd;
    return SPT_E_NONE;
err1:
    close(masterfd);
    return retval;
}
#endif /* HAVE_PTY_SGI4 */

/* UNTESTED */
#ifdef HAVE_PTY_NUMERIC
/* SCO */
static int
open_pty_numeric(spt_handle *phandle)
{
    int masterfd;
    char *name;
    int i;

    if ((name = STRDUP("/dev/ptyp???")) == NULL)
	return SPT_E_NOMEM;
    for (i = 0; i < 256; ++i) {
	sprintf(name + 9, "%d", i);
	masterfd = open(name, O_RDWR | O_NOCTTY);
	if (masterfd < 0) {
	    if (errno == EIO)
		continue;
	    goto notty;
	}
	name[5] = 't';
	phandle->masterfd = masterfd;
	phandle->ttyname = name;
	return SPT_E_NONE;
    }
notty:
    FREE(name);
    return SPT_E_NOTTY;
}
#endif /* HAVE_PTY_NUMERIC */

/* tested */
#ifdef HAVE_PTY_CYGWIN
static int
open_pty_cygwin(spt_handle *phandle)
{
    int retval;
    int masterfd;
    const char *ptytmp;

    masterfd = phandle->masterfd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (masterfd < 0)
	return SPT_E_NOTTY;
    if ((ptytmp = ptsname(masterfd)) == NULL) {
	retval = SPT_E_NOTTY;
	goto err1;
    }
    if ((phandle->ttyname = STRDUP(ptytmp)) == NULL) {
	retval = SPT_E_NOMEM;
	goto err1;
    }
    return SPT_E_NONE;
err1:
    close(masterfd);
    return retval;
}
#endif /* HAVE_PTY_CYGWIN */

/* UNTESTED */
#ifdef HAVE_PTY_CRAY
/* UNICOS */
static int
open_pty_cray(spt_handle *phandle)
{
    int masterfd;
    char *name;
    int i;
    int highpty;

    if ((name = STRDUP("/dev/pty/???")) == NULL)
	return SPT_E_NOMEM;
#ifdef _SC_CRAY_NPTY
    highpty = sysconf(_SC_CRAY_NPTY);
    if (highpty < 0)
	highpty = 128;
    else if (highpty >= 1000)
	highpty = 999;
#else
    highpty = 128;
#endif
    for (i = 0; i < highpty; ++i) {
	sprintf(name + 9, "%03d", i);
	masterfd = open(name, O_RDWR | O_NOCTTY);
	if (masterfd < 0) {
	    if (errno == EIO)
		continue;
	    goto notty;
	}
	/* "/dev/ttyp???" */
	name[5] = 't';
	name[8] = 'p';
	phandle->masterfd = masterfd;
	phandle->ttyname = name;
	return SPT_E_NONE;
    }
notty:
    FREE(name);
    return SPT_E_NOTTY;
}
#endif /* HAVE_PTY_CRAY */

