/* vim:set sw=4 sts=4 et: */

/* for ptsname_r */
#if defined __ANDROID__
# define _GNU_SOURCE
#endif

#include <sys/types.h>
#include <sys/ioctl.h>

#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#if defined __sun__
# include <stropts.h>
#endif
#include <unistd.h>
#include <termios.h>

#include "ptytty.h"

static int
ptsname_compat(int fd, char **buf)
{
#if defined __ANDROID__
    static char b[16];

    if (ptsname_r(fd, b, sizeof(b)) == -1)
        return -1;
    *buf = b;
#else
    if ((*buf = ptsname(fd)) == NULL)
        return -1;
#endif
    return 0;
}

static int
_internal_get_pty(int *master, char **path)
{
    if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1)
        return -1;
    if (grantpt(*master) != 0)
        return -1;
    if (unlockpt(*master) != 0)
        return -1;
    if (ptsname_compat(*master, path) == -1)
        return -1;

    return 0;
}

static int
_internal_get_tty(int *slave, const char *path,
        struct termios *termp, struct winsize *winp, int ctty)
{
    if (path != NULL) {
        if ((*slave = open(path, O_RDWR|O_NOCTTY)) == -1)
            return -1;
    }
#ifdef TIOCSCTTY
    if (ctty && ioctl(*slave, TIOCSCTTY, NULL) == -1)
        return -1;
#endif
#ifdef I_PUSH
    if (ioctl(*slave, I_PUSH, "ptem") == -1)
        return -1;
    if (ioctl(*slave, I_PUSH, "ldterm") == -1)
        return -1;
#if defined __sun__
    if (ioctl(*slave, I_PUSH, "ttcompat") == -1)
        return -1;
#endif
#endif

    if (termp != NULL)
        tcsetattr(*slave, TCSAFLUSH, termp);
    if (winp != NULL)
        ioctl(*slave, TIOCSWINSZ, winp);

    return 0;
}

static int
_internal_login_tty(int fd, const char *path,
        struct termios *termp, struct winsize *winp)
{
    setsid();

    if (_internal_get_tty(&fd, path, termp, winp, 1) != 0)
        return -1;

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2)
        close(fd);
    return 0;

}

int
openpty(int *amaster, int *aslave, char *name,
        struct termios *termp, struct winsize *winp)
{
    char *path = NULL;
    int master = -1, slave = -1;

    if (amaster == NULL || aslave == NULL)
        return -1;

    if (_internal_get_pty(&master, &path) != 0)
        goto out;
    if (_internal_get_tty(&slave, path, termp, winp, 0) != 0)
        goto out;
    if (name != NULL)
        strcpy(name, path);

    *amaster = master;
    *aslave = slave;
    return 0;

out:
    if (master != -1)
        close(master);
    if (slave != -1)
        close(slave);
    return -1;
}

int
forkpty(int *amaster, char *name,
        struct termios *termp, struct winsize *winp)
{
    char *path;
    int master = -1;
    pid_t pid;

    if (amaster == NULL)
        return -1;

    if (_internal_get_pty(&master, &path) != 0)
        goto out;
    if (name != NULL)
        strcpy(name, path);

    if ((pid = fork()) == -1)
        goto out;
    if (pid == 0) {
        close(master);

        if (_internal_login_tty(-1, path, termp, winp) != 0)
            _exit(EXIT_FAILURE);

        return 0;
    }

    *amaster = master;
    return pid;

out:
    if (master != -1)
        close(master);
    return -1;
}

int
login_tty(int fd)
{
    return _internal_login_tty(fd, NULL, NULL, NULL);
}