/*
 * console.c: A dumb serial console client
 *
 * Copyright (C) 2007, 2008 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Daniel Berrange <berrange@redhat.com>
 */

#include <config.h>

#ifndef __MINGW32__

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <poll.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

#include "console.h"
#include "internal.h"
#include "logging.h"
#include "util.h"

/* ie  Ctrl-]  as per telnet */
#define CTRL_CLOSE_BRACKET '\35'

static int got_signal = 0;
static void do_signal(int sig ATTRIBUTE_UNUSED) {
    got_signal = 1;
}

#ifndef HAVE_CFMAKERAW
static void
cfmakeraw (struct termios *attr)
{
    attr->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                         | INLCR | IGNCR | ICRNL | IXON);
    attr->c_oflag &= ~OPOST;
    attr->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    attr->c_cflag &= ~(CSIZE | PARENB);
    attr->c_cflag |= CS8;
}
#endif /* !HAVE_CFMAKERAW */

int vshRunConsole(const char *tty) {
    int ttyfd, ret = -1;
    struct termios ttyattr, rawattr;
    void (*old_sigquit)(int);
    void (*old_sigterm)(int);
    void (*old_sigint)(int);
    void (*old_sighup)(int);
    void (*old_sigpipe)(int);


    /* We do not want this to become the controlling TTY */
    if ((ttyfd = open(tty, O_NOCTTY | O_RDWR)) < 0) {
        VIR_ERROR(_("unable to open tty %s: %s\n"),
                  tty, strerror(errno));
        return -1;
    }

    /* Put STDIN into raw mode so that stuff typed
       does not echo to the screen (the TTY reads will
       result in it being echoed back already), and
       also ensure Ctrl-C, etc is blocked, and misc
       other bits */
    if (tcgetattr(STDIN_FILENO, &ttyattr) < 0) {
        VIR_ERROR(_("unable to get tty attributes: %s\n"),
                  strerror(errno));
        goto closetty;
    }

    rawattr = ttyattr;
    cfmakeraw(&rawattr);

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) {
        VIR_ERROR(_("unable to set tty attributes: %s\n"),
                  strerror(errno));
        goto closetty;
    }


    /* Trap all common signals so that we can safely restore
       the original terminal settings on STDIN before the
       process exits - people don't like being left with a
       messed up terminal ! */
    old_sigquit = signal(SIGQUIT, do_signal);
    old_sigterm = signal(SIGTERM, do_signal);
    old_sigint = signal(SIGINT, do_signal);
    old_sighup = signal(SIGHUP, do_signal);
    old_sigpipe = signal(SIGPIPE, do_signal);
    got_signal = 0;


    /* Now lets process STDIN & tty forever.... */
    for (; !got_signal ;) {
        unsigned int i;
        struct pollfd fds[] = {
            { STDIN_FILENO, POLLIN, 0 },
            { ttyfd, POLLIN, 0 },
        };

        /* Wait for data to be available for reading on
           STDIN or the tty */
        if (poll(fds, (sizeof(fds)/sizeof(struct pollfd)), -1) < 0) {
            if (got_signal)
                goto cleanup;

            if (errno == EINTR || errno == EAGAIN)
                continue;

            VIR_ERROR(_("failure waiting for I/O: %s\n"), strerror(errno));
            goto cleanup;
        }

        for (i = 0 ; i < (sizeof(fds)/sizeof(struct pollfd)) ; i++) {
            if (!fds[i].revents)
                continue;

            /* Process incoming data available for read */
            if (fds[i].revents & POLLIN) {
                char buf[4096];
                int got, sent = 0, destfd;

                if ((got = read(fds[i].fd, buf, sizeof(buf))) < 0) {
                    VIR_ERROR(_("failure reading input: %s\n"),
                              strerror(errno));
                    goto cleanup;
                }

                /* Quit if end of file, or we got the Ctrl-] key */
                if (!got ||
                    (got == 1 &&
                     buf[0] == CTRL_CLOSE_BRACKET))
                    goto done;

                /* Data from stdin goes to the TTY,
                   data from the TTY goes to STDOUT */
                if (fds[i].fd == STDIN_FILENO)
                    destfd = ttyfd;
                else
                    destfd = STDOUT_FILENO;

                while (sent < got) {
                    int done;
                    if ((done = safewrite(destfd, buf + sent, got - sent))
                        <= 0) {
                        VIR_ERROR(_("failure writing output: %s\n"),
                                  strerror(errno));
                        goto cleanup;
                    }
                    sent += done;
                }
            } else { /* Any other flag from poll is an error condition */
                goto cleanup;
            }
        }
    }
 done:
    ret = 0;

 cleanup:

    /* Restore original signal handlers */
    signal(SIGQUIT, old_sigpipe);
    signal(SIGQUIT, old_sighup);
    signal(SIGQUIT, old_sigint);
    signal(SIGQUIT, old_sigterm);
    signal(SIGQUIT, old_sigquit);

    /* Put STDIN back into the (sane?) state we found
       it in before starting */
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr);

 closetty:
    close(ttyfd);

    return ret;
}

#endif /* !__MINGW32__ */