/* vim: set shiftwidth=4 tabstop=8 softtabstop=4: */
/* $Id: utilapi.c,v 1.6 2003/12/06 18:43:01 shinra Exp $ */

#define IN_LIBSPT
#include "sptprivate.h"
#include <stdio.h>
#ifdef HAVE_SYS_IOCTL_H /* for TIOCSCTTY */
#include <sys/ioctl.h>
#endif

SPT_EXPORT(int)
spt_set_ctty2(int slavefd)
{
#if !defined(TIOCSCTTY) && !defined(TCSETCTTY)
    int fd;
    const char *name;
#endif
    if (!isatty(slavefd))
	return SPT_E_INVAL;
#ifdef TIOCSCTTY /* BSD and Linux */
    return ioctl(slavefd, TIOCSCTTY, NULL) ? SPT_E_CTTY_FAIL : SPT_E_NONE;
#elif defined(TCSETCTTY) /* CRAY */
    return ioctl(slavefd, TCSETCTTY, NULL) ? SPT_E_CTTY_FAIL : SPT_E_NONE;
#else /* SysV */
#if (defined(BSD) || defined(linux)) && !defined(TIOCSCTTY)
# warn "sys/ioctl.h missing?"
#endif
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd >= 0) {
	/* already has controlling tty */
	close(fd);
	return SPT_E_CTTY_FAIL;
    }
    name = ttyname(slavefd);
    if (!name)
	return SPT_E_CTTY_FAIL;
    fd = open(name, O_RDWR);
    if (fd < 0)
	return SPT_E_CTTY_FAIL;
    close(fd);
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd < 0)	/* does not have controlling tty yet */
	return SPT_E_CTTY_FAIL;
    close(fd);
    return SPT_E_NONE;
#endif
}

SPT_EXPORT(int)
spt_detach_ctty(void)
{
#ifdef HAVE_SETSID /* POSIX */
    return (setsid() >= 0) ? SPT_E_NONE : SPT_E_CTTY_FAIL;
#elif defined(TIOCNOTTY) /* BSD */
    int fd, r;
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd < 0)
	return SPT_E_NONE;  /* alredy have detached controlling tty */
    r = ioctl(fd, TIOCNOTTY, NULL);
    close(fd);
    return r ? SPT_E_CTTY_FAIL : SPT_E_NONE;
#elif defined(HAVE_SETPGRP) && defined(SETPGRP_VOID) /* SysV */
    /* no error description in Solaris8 manpage but I guess this */
    int fd;
    if (setpgrp() < 0)
	return SPT_E_CTTY_FAIL;
    /* check if really controlling tty is detached just in case */
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd >= 0) {
	/* still have controlling tty */
	close(fd);
	return SPT_E_CTTY_FAIL;
    }
    return SPT_E_NONE;
#else
# warn "don't kow how to detach controlling pty"
    return SPT_E_CTTY_FAIL;
#endif
}

SPT_EXPORT(int)
spt_fork_pty(spt_handle **pphandle, int *pmasterfd,
	int *pslavefd, pid_t *pidptr, const char *host)
{
    int r;
    pid_t pid;
    int masterfd;
    int slavefd;
    spt_handle *phandle;

    r = spt_open_pty(&phandle, &masterfd, NULL, NULL);
    if (r && r != SPT_E_CHOWN_FAIL)
	return r;
    r = SPT_E_NOTTY;
    slavefd = spt_open_slave(phandle);
    if (slavefd < 0)
	goto err1;
    /* silently fail for variety of this ioctl */
    spt_init_slavefd(phandle, slavefd);
    r = spt_termios_fd_default(slavefd);
    if (r)
	goto err2;
    r = SPT_E_RESOURCE;
    pid = fork();
    if (pid < 0)
	goto err2;
    else if (pid == 0) {
	/* child */
	spt_detach_handle(phandle);
	close(masterfd);
	if ((r = spt_detach_ctty())
		|| (r = spt_set_ctty2(slavefd))) {
	    /* fatal error! */
	    spt_perror(NULL, r);
	    _exit(1);
	}
	*pphandle = NULL;
	*pmasterfd = -1;
	*pslavefd = slavefd;
    } else {
	/* parent */
	if (!host || !spt_utmp_set_host(phandle, host))
	    spt_login_utmp(phandle);	/* ignore utmp logging error */
	/* invoke spt_login_wtmp yourself if you want to */
	*pphandle = phandle;
	*pmasterfd = masterfd;
	*pslavefd = slavefd;	/* does not close slave side */
    }
    *pidptr = pid;
    return SPT_E_NONE;
err2:
    close(slavefd);
err1:
    spt_close_pty(phandle);
    return r;
}

