/* Copyright © 2006  Roger Leigh <rleigh@debian.org>
 *
 * ptyjoin 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 of the License, or
 * (at your option) any later version.
 *
 * ptyjoin 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 <iostream>
#include <cerrno>
#include <cstdlib>
#include <pty.h>
#include <pthread.h>
#include <termios.h>
#include <unistd.h>

using std::cout;
using std::cerr;
using std::endl;

void
hardfail (const char *msg)
{
  cerr << "E: " << msg << ": " << std::strerror(errno) << endl;
  exit(EXIT_FAILURE);
}

class iochannel
{
public:
  iochannel(int source, int sink):
    source(source),
    sink(sink),
    bufstart(0),
    buflen(0)
  {}

  ~iochannel()
  {}

  void fill()
  {
    this->bufstart = this->buflen = 0;
    cerr << "Reading fd " << this->source << endl;
    if ((this->buflen = read(this->source, this->buf, BUFSIZ)) < 0)
      hardfail("read error");
    cerr << "Read buffer (" << this->buflen << " bytes)"
      " from fd " << this->source << endl;
  }

  void drain()
  {
    while (this->buflen > 0)
      {
	cerr << "Writing fd " << this->sink << endl;
	ssize_t wbytes = write(this->sink, this->buf + this->bufstart,
			       this->buflen);
	if (wbytes < 0)
	  hardfail("write error");
	cerr << "Wrote buffer (" << wbytes << '/' << this->buflen
	     << " bytes) to fd " << this->sink << endl;
	this->bufstart += wbytes;
	this->buflen -= wbytes;
	cerr << "Buffer is now " << buflen << " bytes" << endl;
      }
  }

  void flow()
  {
    while (1)
      {
	fill();
	drain();
      }
  }

private:
  int source;
  int sink;
  size_t bufstart;
  ssize_t buflen;
  char buf[BUFSIZ];
};

void *
exec_thread (void * arg)
{
  iochannel *channel = static_cast<iochannel *>(arg);

  channel->flow();

  pthread_exit(NULL);
}

termios
make_raw_termios()
{
  termios tp;

  tp.c_iflag = BRKINT | ICRNL | IXON | IXANY | IMAXBEL;
  tp.c_oflag = OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0;
  tp.c_cflag = CS8 | HUPCL | CREAD;
  tp.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE |
    ECHOK | ECHOCTL | ECHOKE;
  tp.c_cc[VINTR] = 003; // C-C
  tp.c_cc[VQUIT] = 034; // C-backslash
  tp.c_cc[VERASE] = 0177; // DEL
  tp.c_cc[VKILL] = 025; // KILL
  tp.c_cc[VEOF] = 004; // C-D
  tp.c_cc[VEOL2] = 00;
  tp.c_cc[VSTART] = 021; // DC1/XON;
  tp.c_cc[VSTOP] = 023; // DC3/XOFF;
  tp.c_cc[VSUSP] = 032; // C-Z;
  cfmakeraw(&tp);
  cfsetispeed(&tp, B57600);
  cfsetospeed(&tp, B57600);

  return tp;
}

int
main (void)
{
  int pty1m, pty1s, pty2m, pty2s;
  termios tp = make_raw_termios();

  if (openpty(&pty1m, &pty1s, NULL, &tp, NULL) < 0)
    hardfail("Failed to open PTY1");
  if (openpty(&pty2m, &pty2s, NULL, &tp, NULL) < 0)
    hardfail("Failed to open PTY1");

  cout << "PTY1: " << ttyname(pty1s) << endl;
  cout << "PTY2: " << ttyname(pty2s) << endl;

  if (close (STDIN_FILENO) < 0)
    hardfail("Failed to close stdin");
  if (close (STDOUT_FILENO) < 0)
    hardfail("Failed to close stdout");
// Can't close until opened by client...
//   if (close (pty1s) < 0)
//     hardfail("Failed to PTY1 slave");
//   if (close (pty2s) < 0)
//     hardfail("Failed to PTY1 slave");
  if (daemon(0, 1) < 0)
    hardfail("Failed to become a daemon");

  iochannel channel1(pty1m, pty2m);
  iochannel channel2(pty2m, pty1m);

  pthread_t thread1, thread2;
  pthread_create(&thread1, NULL,
		 exec_thread, static_cast<void *>(&channel1));
  pthread_create(&thread2, NULL,
		 exec_thread, static_cast<void *>(&channel2));

  pthread_join(thread1, NULL);

  return 0;
}
