//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may 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 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <ldastoolsal_config.h>

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h> // kill - to terminate child

#include <iostream>
#include <stdexcept>
#include <sstream>

#include "ldastoolsal/Fork.hh"

namespace LDASTools
{
    namespace AL
    {
        const int Fork::F_STDIN = 0;
        const int Fork::F_STDOUT = 1;
        const int Fork::F_STDERR = 2;

        const int Fork::F_READ = 0;
        const int Fork::F_WRITE = 1;

        Fork::Fork( )
        {
            // Initialize the file descriptors that will be used for storing
            // pipe
            //   information.
            for ( int x = 0; x < 3; x++ )
            {
                for ( int y = 0; y < 2; y++ )
                {
                    m_pipes[ x ][ y ] = -1;
                }
            }
        }

        Fork::~Fork( )
        {
            close_pipes( );
            if ( isParent( ) )
            {
                kill( m_child_pid, SIGKILL );
            }
        }

        bool
        Fork::IsChildAlive( ) const
        {
            if ( isParent( ) )
            {
                return ( kill( m_child_pid, 0 ) == 0 );
            }
            return true;
        }

        void
        Fork::Spawn( )
        {
            try
            {
                //-----------------------------------------------------------------
                // Create a pipe
                //-----------------------------------------------------------------
                if ( ( pipe( m_pipes[ F_STDIN ] ) < 0 ) ||
                     ( pipe( m_pipes[ F_STDOUT ] ) < 0 ) ||
                     ( pipe( m_pipes[ F_STDERR ] ) < 0 ) )
                {
                    throw std::runtime_error( "Unable to create pipes" );
                }
                //-----------------------------------------------------------------
                // Create child
                //-----------------------------------------------------------------
                int flags = fcntl( m_pipes[ F_STDIN ][ F_READ ], F_GETFD );

                if ( flags == -1 )
                {
                    throw std::runtime_error( "Unable to query pipes" );
                }
                m_child_pid = fork( );
                switch ( m_child_pid )
                {
                case -1:
                    // Error condition
                    break;
                case 0:
                    // Child process
                    close_pipe( F_STDIN, F_WRITE );
                    close_pipe( F_STDOUT, F_READ );
                    close_pipe( F_STDERR, F_READ );
                    evalChild( );
                    break;
                default:
                    // Parent process
                    close_pipe( F_STDIN, F_READ );
                    close_pipe( F_STDOUT, F_WRITE );
                    close_pipe( F_STDERR, F_WRITE );
                    evalParent( );
                    break;
                }
            }
            catch ( ... )
            {
                close_pipes( );
                throw; // Rethrow the execption
            }
        }

        void
        Fork::close_pipes( )
        {
            for ( int x = 0; x < 3; x++ )
            {
                for ( int y = 0; y < 2; y++ )
                {
                    if ( m_pipes[ x ][ y ] >= 0 )
                    {
                        close_pipe( x, y );
                    }
                }
            }
        }

        void
        Fork::close_pipe( int Major, int Minor )
        {
            close( m_pipes[ Major ][ Minor ] );
            m_pipes[ Major ][ Minor ] = -1;
        }
    } // namespace AL
} // namespace LDASTools
