/* Copyright (c) 1997-2005
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de

   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: http://www.gnu.org/licenses/gpl.txt.

   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.
*/

#ident "$Project: polymake $$Id: pipestream.cc,v 1.9 2005/12/08 23:05:16 gawrilow Exp $"

#if defined(__GNUC__) && __GNUC__==3 && __GNUC_MINOR__<3 && !defined(__APPLE__) && !defined(__COMO__)
#pragma implementation
#endif

#include <stdexcept>
#include <pipestream.h>
#include <fstream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <cerrno>

namespace std {

namespace {
   int get_fd(istream *in)
   {
      ifstream *fs=dynamic_cast<ifstream*>(in);
      if (fs)
	 return pm::FileBuffer::get_fd(fs->rdbuf());
      procstream *ps=dynamic_cast<procstream*>(in);
      if (ps) {
	 pollfd dummy_poll;
	 ps->rdbuf()->fill(dummy_poll);
	 return dummy_poll.fd;
      }
      return -1;
   }

   int get_fd(ostream *out)
   {
      ofstream *fs=dynamic_cast<ofstream*>(out);
      if (fs) {
	 int fd=pm::FileBuffer::get_fd(fs->rdbuf());
	 // assume both processes writing to the file in parallel
	 fcntl(fd, F_SETFL, O_APPEND);
	 return fd;
      }
      procstream *ps=dynamic_cast<procstream*>(out);
      if (ps) {
	 pollfd dummy_poll;
	 ps->rdbuf()->fill(dummy_poll);
	 return dummy_poll.fd;
      }
      return -1;
   }

   void redirect(int from_fd, int to_fd)
   {
      if (from_fd<0) {
	 cerr << "fd(" << to_fd << ") redirect failed: source stream not bound to any file\n";
	 exit(1);
      }
      if (dup2(from_fd, to_fd)<0) {
	 int err=errno;
	 cerr << "dup2(" << from_fd << ',' << to_fd << ") failed: errno=" << err << '\n';
	 exit(1);
      }
   }
}

   void background_process::start(const char* progname, const char* const command[],
				  istream *Stdin, ostream *Stdout, ostream *Stderr)
   {
      _pid=fork();
      if (_pid<0)
	 throw std::runtime_error("background_process: fork() failed");
      if (!_pid) {
	 // child branch
	 if (Stdin)  redirect(get_fd(Stdin),0);
	 if (Stdout) redirect(get_fd(Stdout),1);
	 if (Stderr) redirect(get_fd(Stderr),2);
	 execvp(progname, const_cast<char* const*>(command));
	 cerr << "background_process: exec(" << progname << ") failed" << endl;
	 exit(1);
      }
   }

   background_process::~background_process()
   {
      if (_pid>0) waitpid(_pid,0,0);
   }

   void pipestream_base::open(const char* progname, const char* const command[])
   {
      int skp[2];
      if (socketpair(AF_UNIX,SOCK_STREAM,0,skp))
	 throw std::runtime_error("pipestream: socketpair() failed");
      _pid=fork();
      if (_pid<0)
	 throw std::runtime_error("pipestream: fork() failed");
      if (!_pid) {
	 // child branch
	 close(skp[0]);

	 if (dup2(skp[1],0)<0 || dup2(skp[1],1)<0) {
	    cerr << "pipestream: dup() failed" << endl;
	    exit(1);
	 }
	 close(skp[1]);
	 execvp(progname, const_cast<char* const*>(command));
	 cerr << "pipestream: exec(" << progname << ") failed" << endl;
	 exit(1);
      }

      // parent branch goes on here
      close(skp[1]);
      _fd=skp[0];      
   }

   pipestream_base::~pipestream_base()
   {
      close(_fd);
      if (_pid>0) waitpid(_pid,0,0);
   }
} // end namespace std
