// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/CORBA.h>
#include <Factory_impl.h>

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>

#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>

//
// Sequence of children.
//
static Factory::ChildInfoSeq children;

//
// Called on a SIGCHLD.
//
void
FactorySignalHandler(int sig)
{
    for(;;)
    {
	//
	// Find out which child has died.
	//
	int status;
	pid_t pid = ::waitpid(-1, &status, WNOHANG);

	//
	// A signal? Continue.
	//
	if(pid < 0 && errno == EINTR)
	    continue;

	//
	// No further dead children, we're done!
	//
	if(pid <= 0)
	    break;

	//
	// Update the status of the child in our list.
	//
	if(WIFEXITED(status))
	{
	    for(CORBA_ULong i = 0; i < children.length(); i++)
	    {
		if(children[i].pid == pid)
		{
		    children[i].dead = true;
		    break;
		}
	    }
	}
    }

    //
    // Reset the signal handler.
    //
    ::signal(SIGCHLD, FactorySignalHandler);
}

// ----------------------------------------------------------------------
// Factory_impl private member functions
// ----------------------------------------------------------------------

void
Factory_impl::reap()
{
    CORBA_ULong i = 0;
    while(i < children.length())
    {
	if(children[i].dead)
	    children.remove(i);
	else
	    i++;
    }
}

// ----------------------------------------------------------------------
// Factory_impl constructor and destructor
// ----------------------------------------------------------------------

Factory_impl::Factory_impl(CORBA_ORB_ptr orb, const char* pname)
    : orb_(CORBA_ORB::_duplicate(orb)), pname_(pname)
{
    ::signal(SIGCHLD, FactorySignalHandler);
}

Factory_impl::~Factory_impl()
{
    ::signal(SIGCHLD, 0);
}

// ----------------------------------------------------------------------
// Factory_impl public member functions
// ----------------------------------------------------------------------

Hello_ptr
Factory_impl::createHello()
{
    //
    // Reap child processes. If we're not using MT then this could be
    // done on a timed basis. See: <OB/Timer.h>
    //
    reap();

    //
    // Create a pipe. fds[0] is reader-end, fds[1] is writer-end.
    //
    int fds[2];
    if(::pipe(fds) < 0)
    {
	const char* err = strerror(errno);
	cerr << pname_ << ": pipe() failed: " << err << endl;
	throw Factory::CreateFailed();
    }

    //
    // Fork a new hello world process.
    //
    pid_t pid = ::fork();

    if(pid == 0) // Child
    {
	//
	// Close all file descriptors except the write end of the
	// pipe.
	//
	int maxFds = sysconf(_SC_OPEN_MAX);
	if(maxFds <= 0)
	{
	    const char* err = strerror(errno);
	    cerr << pname_ << ": sysconf() failed: " << err << endl;
	    exit(-1);
	}
	for(int i = 0; i < maxFds; i++)
	{
	    if(i != fds[1])
		::close(i);
	}

	//
	// Make the write end of the pipe STDOUT.
	//
	if(::dup2(fds[1], 1) < 0)
	{
	    const char* err = strerror(errno);
	    cerr << pname_ << ": dup2() failed: " << err << endl;
	    exit(-1);
	}

	//
	// Exec the `hello world' server. Note that the `hello world'
	// server uses blocking mode. This is because the server is
	// going to only support one connected client. When the client
	// goes away, then the server goes away, too.
	//
	char* argv[2];
	argv[0] = strdup("./server");
	argv[1] = 0;
	execv("./server", argv);
	free(argv[0]);

	//
	// This should not be reached
	//
	const char* err = strerror(errno);
	cerr << pname_ << ": execv() failed: " << err << endl;
	exit(-1);
    }
    else if(pid == -1) // Failed
    {
	const char* err = strerror(errno);

	::close(fds[0]);
	::close(fds[1]);

	cerr << pname_ << ": fork() failed: " << err << endl;
	throw Factory::CreateFailed();
    }

    //
    // Add the child to the children list.
    //
    CORBA_ULong i = children.length();
    children.length(children.length()+1);
    children[i].pid = pid;
    children[i].dead = false;

    //
    // Wait for the pid to appear down the pipe.
    //
    char buf[1024];

    int count;
    for(;;)
    {
	count = read(fds[0], buf, sizeof(buf) - 1);
	if(count == -1 && errno == EINTR)
	{
	    //
	    // Find out if the child died.
	    //
	    if(children[i].dead)
	    {
		children.remove(i);

		//
		// Close the pipe fd's.
		//
		::close(fds[0]);
		::close(fds[1]);
		throw Factory::CreateFailed();
	    }
	    continue;
	}
	break;
    }

    //
    // Close the pipe filedescriptors.
    //
    ::close(fds[0]);
    ::close(fds[1]);

    //
    // Read error?
    //
    if(count <= 0)
    {
	const char* err = strerror(errno);
	cerr << pname_ << ": read() failed: " << err << endl;
	throw Factory::CreateFailed();
    }

    //
    // Verify that the read data is an IOR.
    //
    if(strncmp(buf, "IOR:", 4) != 0)
    {
	cerr << pname_ << ": returned value is not an IOR." << endl;
	throw Factory::CreateFailed();
    }

    //
    // Ensure the buffer is null terminated.
    //
    buf[count] = '\0';

    //
    // Turn the string into a Hello object reference.
    //
    CORBA_Object_var obj = orb_ -> string_to_object(buf);
    Hello_var hello = Hello::_narrow(obj);
    if(CORBA_is_nil(hello))
    {
	cerr << pname_ << ": returned IOR is not a `Hello' object." << endl;
	throw Factory::CreateFailed();
    }

    return hello._retn();
}
