/*
 * This file is taken from pty-sharp (http://tirania.org/software/pty-sharp)
 *
 * Simplified and modified for bareFTP by Christian Eide <christian@eide-itc.no> 
 * using some code from gftp, Copyright (C) 1998-2007 Brian Masney <masneyb@gftp.org>
 */
 
/* Original copyright: 
 *
 * Copyright (C) 2001,2002 Red Hat, Inc.
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "../../config.h"
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
#endif
#include <sys/uio.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#ifdef HAVE_SYS_SYSLIMITS_H
#include <sys/syslimits.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#include <unistd.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#include <glib.h>
#include "pty.h"

#ifdef MSG_NOSIGNAL
#define PTY_RECVMSG_FLAGS MSG_NOSIGNAL
#else
#define PTY_RECVMSG_FLAGS 0
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(String) dgettext(PACKAGE, String)
#else
#define _(String) String
#endif

#ifdef HAVE_SOCKETPAIR
static int
_vte_pty_pipe_open(int *a, int *b)
{
	int p[2], ret = -1;
#ifdef PF_UNIX
#ifdef SOCK_STREAM
	ret = socketpair(PF_UNIX, SOCK_STREAM, 0, p);
#else
#ifdef SOCK_DGRAM
	ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, p);
#endif
#endif
	if (ret == 0) {
		*a = p[0];
		*b = p[1];
		return 0;
	}
#endif
	return ret;
}
#else
static int
_vte_pty_pipe_open(int *a, int *b)
{
	int p[2], ret = -1;

	ret = pipe(p);

	if (ret == 0) {
		*a = p[0];
		*b = p[1];
	}
	return ret;
}
#endif

/* [bareFTP/Christian Eide] This method is taken from gftp */
/* Copyright (C) 1998-2007 Brian Masney <masneyb@gftp.org> */
static int
_gftp_tty_raw (int fd)
{
  struct termios buf;

  if (tcgetattr (fd, &buf) < 0)
    return (-1);

  buf.c_iflag |= IGNPAR;
  buf.c_iflag &= ~(ICRNL | ISTRIP | IXON | IGNCR | IXANY | IXOFF | INLCR);
  buf.c_lflag &= ~(ECHO | ICANON | ISIG | ECHOE | ECHOK | ECHONL);
#ifdef IEXTEN
  buf.c_lflag &= ~(IEXTEN);
#endif

  buf.c_oflag &= ~(OPOST);
  buf.c_cc[VMIN] = 1;
  buf.c_cc[VTIME] = 0;

  if (tcsetattr (fd, TCSADRAIN, &buf) < 0)
    return (-1);
  return (0);
}



/* [bareFTP/Christian Eide] Modified to behave more as the gftp equivalent */
/* Open the named PTY slave, fork off a child (storing its PID in child),
 * and exec the named command in its own session as a process group leader */
static int
_vte_pty_fork_on_pty_name(const char *path, int parent_fd, int *slave_fd, char **env_add,
			  const char *command, char **argv,
			  const char *directory, pid_t *child)
{
	int fd, i;
	char c;
	int s[2];
	pid_t pid;

	/* Open pipes for synchronizing between parent and child. */
	if (_vte_pty_pipe_open(&s[0], &s[1]) == -1) {
		/* Error setting up pipes.  Bail. */
		*child = -1;
		return -1;
	}

	/* Start up a child. */
	pid = fork();
	switch (pid) {
	case -1:
		/* Error fork()ing.  Bail. */
		*child = -1;
		return -1;
		break;
	case 0:
		/* Start a new session and become process-group leader. */
		setsid();
		setpgid(0, 0);		
		
		/* Open the slave PTY, acquiring it as the controlling terminal
		 * for this process and its children. */
		fd = open(path, O_RDWR);
		if (fd == -1) {
			return -1;
		}
		
		/* Child. Close the parent's ends of the pipes. */
		close(s[0]);
		close(parent_fd);
		
		/* Close most descriptors. */
		for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
			if ((i != s[1]) && (i != fd)) {
				close(i);
			}
		}

		_gftp_tty_raw (s[0]);
      		_gftp_tty_raw (fd);
		
		dup2 (s[1], 0);
      		dup2 (s[1], 1);
      		dup2 (fd, 2);


#ifdef TIOCSCTTY
		/* TIOCSCTTY is defined?  Let's try that, too. */
		ioctl(fd, TIOCSCTTY, NULL);
#endif
		/* Store 0 as the "child"'s ID to indicate to the caller that
		 * it is now the child. */
		*child = 0;
		/*return _vte_pty_run_on_pty(fd, ready_b[0], ready_a[1],
					   env_add, command, argv, directory);
		*/
		/* char c = 0; */
		char **args, *arg;
		if (command != NULL) {
			/* Outta here. */
			if (argv != NULL) {
				for (i = 0; (argv[i] != NULL); i++) ;
				args = g_malloc0(sizeof(char*) * (i + 1));
				for (i = 0; (argv[i] != NULL); i++) {
					args[i] = g_strdup(argv[i]);
				}
				execvp(command, args);
			} else {
				arg = g_strdup(command);
				execlp(command, arg, NULL);
			}
	
			/* Avoid calling any atexit() code. */
			_exit(0);
			g_assert_not_reached();
		}
		return 0;
		break;
	default:
		/* Parent.  Close the child's ends of the pipes, do the ready
		 * handshake, and return the child's PID. */
		close(s[1]);
		
		*slave_fd = s[0];
      		_gftp_tty_raw (s[0]);
      		_gftp_tty_raw (parent_fd);
		
		/* Wait for the child to be ready, set the window size, then
		 * signal that we're ready.  We need to synchronize here to
		 * avoid possible races when the child has to do more setup
		 * of the terminal than just opening it. */
		
		*child = pid;
		return 0;
		break;
	}
	g_assert_not_reached();
	return -1;
}


static char *
_vte_pty_ptsname(int master)
{
#if defined(HAVE_PTSNAME_R)
	gsize len = 1024;
	char *buf = NULL;
	int i;
	do {
		buf = g_malloc0(len);
		i = ptsname_r(master, buf, len - 1);
		switch (i) {
		case 0:
			/* Return the allocated buffer with the name in it. */
			return buf;
			break;
		default:
			g_free(buf);
			buf = NULL;
			break;
		}
		len *= 2;
	} while ((i != 0) && (errno == ERANGE));
#elif defined(HAVE_PTSNAME)
	char *p;
	if ((p = ptsname(master)) != NULL) {
		return g_strdup(p);
	}
#elif defined(TIOCGPTN)
	int pty = 0;
	if (ioctl(master, TIOCGPTN, &pty) == 0) {
		return g_strdup_printf("/dev/pts/%d", pty);
	}
#endif
	return NULL;
}

static int
_vte_pty_getpt(void)
{
	int fd, flags;
#ifdef HAVE_GETPT
	/* Call the system's function for allocating a pty. */
	fd = getpt();
#else
	/* Try to allocate a pty by accessing the pty master multiplex. */
	fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
	if ((fd == -1) && (errno == ENOENT)) {
		fd = open("/dev/ptc", O_RDWR | O_NOCTTY); /* AIX */
	}
#endif
	/* Set it to blocking. */
	flags = fcntl(fd, F_GETFL);
	flags &= ~(O_NONBLOCK);
	fcntl(fd, F_SETFL, flags);
	return fd;
}

static int
_vte_pty_grantpt(int master)
{
#ifdef HAVE_GRANTPT
	return grantpt(master);
#else
	return 0;
#endif
}

static int
_vte_pty_unlockpt(int fd)
{
#ifdef HAVE_UNLOCKPT
	return unlockpt(fd);
#elif defined(TIOCSPTLCK)
	int zero = 0;
	return ioctl(fd, TIOCSPTLCK, &zero);
#else
	return -1;
#endif
}

static int
_vte_pty_open_unix98(pid_t *child, int *slave, char **env_add,
		     const char *command, char **argv, const char *directory)
{
	int fd;
	char *buf;

	/* Attempt to open the master. */
	fd = _vte_pty_getpt();

	if (fd != -1) {
		/* Read the slave number and unlock it. */
		if (((buf = _vte_pty_ptsname(fd)) == NULL) ||
		    (_vte_pty_grantpt(fd) != 0) ||
		    (_vte_pty_unlockpt(fd) != 0)) {
			close(fd);
			fd = -1;
		} else {
			/* Start up a child process with the given command. */
			if (_vte_pty_fork_on_pty_name(buf, fd, slave, env_add, command,
						      argv, directory, child) != 0) {
				close(fd);
				fd = -1;
			}
			g_free(buf);
		}
	}
	return fd;
}

#ifdef HAVE_RECVMSG
static void
_vte_pty_read_ptypair(int tunnel, int *parentfd, int *childfd)
{
	int i, ret;
	char control[LINE_MAX], iobuf[LINE_MAX];
	struct cmsghdr *cmsg;
	struct msghdr msg;
	struct iovec vec;

	for (i = 0; i < 2; i++) {
		vec.iov_base = iobuf;
		vec.iov_len = sizeof(iobuf);
		msg.msg_name = NULL;
		msg.msg_namelen = 0;
		msg.msg_iov = &vec;
		msg.msg_iovlen = 1;
		msg.msg_control = control;
		msg.msg_controllen = sizeof(control);
		ret = recvmsg(tunnel, &msg, PTY_RECVMSG_FLAGS);
		if (ret == -1) {
			return;
		}
		for (cmsg = CMSG_FIRSTHDR(&msg);
		     cmsg != NULL;
		     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
			if (cmsg->cmsg_type == SCM_RIGHTS) {
				memcpy(&ret, CMSG_DATA(cmsg), sizeof(ret));
				switch (i) {
					case 0:
						*parentfd = ret;
						break;
					case 1:
						*childfd = ret;
						break;
					default:
						g_assert_not_reached();
						break;
				}
			}
		}
	}
}
#else
#ifdef I_RECVFD
static void
_vte_pty_read_ptypair(int tunnel, int *parentfd, int *childfd)
{
	int i;
	if (ioctl(tunnel, I_RECVFD, &i) == -1) {
		return;
	}
	*parentfd = i;
	if (ioctl(tunnel, I_RECVFD, &i) == -1) {
		return;
	}
	*childfd = i;
}
#endif
#endif


static gint
_vte_direct_compare(gconstpointer a, gconstpointer b)
{
	return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
}


/**
 * _pty_sharp_open:
 * @child: location to store the new process's ID
 * @env_add: a list of environment variables to add to the child's environment
 * @command: name of the binary to run
 * @argv: arguments to pass to @command
 * @directory: directory to start the new command in, or %NULL

 * Starts a new copy of @command running under a psuedo-terminal, optionally in
 * the supplied @directory, with window size set to @rows x @columns
 * and variables in @env_add added to its environment.  
 *
 * Returns: an open file descriptor for the pty master, -1 on failure
 */
int
_pty_sharp_open(pid_t *child, int *slave, char **env_add,
	      const char *command, char **argv, const char *directory)
{
	int ret = -1;
	
        ret = _vte_pty_open_unix98(child, slave, env_add, command, argv, directory);
	return ret;
}

/**
 * _vte_pty_set_utf8:
 * @pty: The pty master descriptor.
 * @utf8: Whether or not the pty is in UTF-8 mode.
 *
 * Tells the kernel whether the terminal is UTF-8 or not, in case it can make
 * use of the info.  Linux 2.6.5 or so defines IUTF8 to make the line
 * discipline do multibyte backspace correctly.
 */
void
_pty_sharp_set_utf8(int pty, gboolean utf8)
{
#ifdef IUTF8
	struct termios tio;
	tcflag_t saved_cflag;
	if (pty != -1) {
		if (tcgetattr(pty, &tio) != -1) {
			saved_cflag = tio.c_iflag;
			tio.c_iflag &= ~IUTF8;
			if (utf8) {
				tio.c_iflag |= IUTF8;
			}
			if (saved_cflag != tio.c_iflag) {
				tcsetattr(pty, TCSANOW, &tio);
			}
		}
	}
#endif
}

int
_bareftp_get_ready_fd(int fd_console, int fd_data, gboolean retry)
{

 fd_set rset, eset;
 int maxfd, ret;
 FD_ZERO (&rset);
 FD_ZERO (&eset);
 maxfd = fd_data > fd_console ? fd_data : fd_console;
 
 errno = 0;
 int ok;
 while (1)
    {
      FD_SET (fd_data, &rset);
      FD_SET (fd_console, &rset);

      FD_SET (fd_data, &eset);
      FD_SET (fd_console, &eset);

      ret = select (maxfd + 1, &rset, NULL, &eset, NULL);
      if (ret < 0)
        {
          if (errno == EINTR || errno == EAGAIN)
            {
	            /* Retryable error */
              if (!retry)
                {
                  return (-1);
                }

              continue;
            }
          else
            {
	          /* Fatal error */
              return -2;
            }
        }

      if (FD_ISSET (fd_data, &eset) || FD_ISSET (fd_console, &eset))
        {
          return -3;
        }
        
      if (FD_ISSET (fd_data, &rset))
        {
          return fd_data;
        }
      else if (!FD_ISSET (fd_console, &rset))
        continue;

      return fd_console;
    }
 return 0;	
}

