/* $Id: ttyd.C,v 1.10 2001/11/13 06:05:47 kaminsky Exp $ */

/*
 *
 * Copyright (C) 2001 Eric Peterson (ericp@lcs.mit.edu)
 * Copyright (C) 2001 Michael Kaminsky (kaminsky@lcs.mit.edu)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "sfsmisc.h"
#include "crypt.h"
#include "aios.h"
#include "rex_prot.h"

ptr <aios> paios;

/* from openssh  */
/* Changes the window size associated with the pty. */

static void
pty_change_window_size(int ptyfd, int row, int col,
		       int xpixel, int ypixel)
{
  struct winsize w;
  w.ws_row = row;
  w.ws_col = col;
  w.ws_xpixel = xpixel;
  w.ws_ypixel = ypixel;
  (void) ioctl(ptyfd, TIOCSWINSZ, &w);
}

void
windowresize (int mfd, const str data, int err)
{
  static int x = 0;
  static int a[4];

  if (!data.len ())
    return;

  //todo: use something better than atoi
  a[x++] = atoi(data.cstr());

  if (x == 4) {
    //do resize
    pty_change_window_size(mfd, a[0], a[1], a[2], a[3]);
    warn ("changing window size to col:%d  row:%d  xpix:%d  ypix:%d\n",
	  a[1], a[0], a[2], a[3]);
	  
    x = 0;
  }

  paios->readline (wrap (windowresize, mfd));
}

static void
slaveexit (int status)
{
  exit (WIFEXITED (status) ? WEXITSTATUS (status) : -WTERMSIG (status));
}

static void
postforkcb (int ttyfd)
{
  setsid ();
    
  if (ttyfd < 0)
    fatal << "could not open slave tty\n";

  dup2 (ttyfd, 0);
  dup2 (ttyfd, 1);
  dup2 (ttyfd, 2);

  close (ttyfd);

  //make us controlling terminal
  ioctl (0, TIOCSCTTY);
    
  //todo: set terminal modes here
}

int
main (int argc, char **argv)
{
  setprogname (argv[0]);
  sfsconst_init ();  

  warn ("ttyd called for utmp host:%s\n", argv[1]);

  /* Set up the command that we want to run. */
  str command;
  if (!strcmp (argv[2], "."))
    command = getenv ("SHELL");
  if (!command)
    command = find_program (argv[2]);
  if (!command)
    fatal << "Could not locate program: " << argv[2] << "\n";

  const char *name = strrchr (command.cstr (), '/');
  if (name)
    name++;
  else
    name = command;

  char buf[256];
  buf[0] = '-';
  strncpy (buf + 1, name, sizeof (buf) - 1);
  buf[sizeof (buf) - 1] = 0;

  /* Set up tty/pty. */
  int fd = suidgetfd_required ("ptyd");
  if (fd < 0)
    fatal << "connection to ptyd failed\n";

  ref<axprt_unix> ux = axprt_unix::alloc (fd);
  ref<aclnt> ac = aclnt::alloc (ux, ptyd_prog_1);

  utmphost host = argv[1];
  pty_alloc_res res;
  if (ac->scall (PTYD_PTY_ALLOC, &host, &res) || res.err) {
    fatal << "could not allocate pty via ptyd\n";
  }

  ttypath path = *res.path;	/* path to slave side of pseudo-tty */
  int mfd = ux->recvfd ();	/* fd connect to master side of pseudo-tty */
  if (mfd < 0)
    fatal << "could not receive master fd from ptyd\n";

  close_on_exec (mfd);
  close_on_exec (fd);
  
  int ttyfd = open (path, O_RDWR);

  if (writefd (0, "", 1, mfd) < 0)
    fatal << "could not pass master fd to proxy\n";

  /* Execute the shell. */
  argv[2] = buf;
  argv++;
  argv++;

  pid_t pid = aspawn (command, argv, 0, 1, 2, wrap (postforkcb, ttyfd));

  if (pid == -1)
    fatal << "could not fork\n";

  //keep mfd around for window resizing
  paios = aios::alloc (0);
  paios->readline (wrap (windowresize, mfd));
    
  close(ttyfd);
        
  chldcb (pid, wrap (slaveexit));
    
#if 0
  //is this necessary, will ptyd clean then up when master/slave fd closes ??

  //this has to be written async and put in slaveexit if it's necessary
    
  int err;
  if(ac->scall (PTYD_PTY_FREE, &path, &err) || err)
    warn("failed to free pty via ptyd\n");
#endif

  amain ();
}
