///////////////////////////////////////////////////////////////////////////////////////
// File   :  init.c
// Date   :  January 2018
// Author :  Alain Greiner
///////////////////////////////////////////////////////////////////////////////////////
// This single thread application implement the "init" process for ALMOS-MKH. 
// It uses the fork/exec syscalls to create N KSH child processes 
// (one child process per user TXT terminal).
// Then it calls the wait() function to block, and recreate any child KSH process
// that has been deleted, using a new fork/exec.
///////////////////////////////////////////////////////////////////////////////////////

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <almosmkh.h>
#include <hal_macros.h>
#include <sys/wait.h>
#include <shared_syscalls.h>

#define DEBUG_PROCESS_INIT    0

////////////////
int main( void )
{
    unsigned int  i;
    int           ret_fork;      // fork return value  
    int           ret_exec;      // exec return value  
    int           rcv_pid;       // pid received from the wait syscall
    int           status;        // used by the wait syscall
    char          string[64];    // log messages on kernel TXT0
    unsigned int  cxy;           // target cluster identifier

#if DEBUG_PROCESS_INIT
display_string("[init] process enters");
#endif

    // get number of TXT channels and number of clusters
    hard_config_t config;      

    get_config( &config );

    unsigned int  txt_channels = config.txt_channels;
    unsigned int  x_size       = config.x_size;
    unsigned int  y_size       = config.y_size;
    unsigned int  n_clusters    = x_size * y_size;

    // check number of TXT channels
    if( txt_channels < 2 )
    {
        snprintf( string , 64 ,
        "\n[init ERROR] number of TXT channels must be larger than 1");
        display_string( string );
        exit( EXIT_FAILURE );
    }

    cxy = 0;

    // create the KSH processes (one per user terminal)
    for( i = 1 ; i <  txt_channels ; i++ )
    {
        // compute target cluster
        cxy = (cxy + 1) % n_clusters;

        // select target cluster
        if( place_fork( cxy ) )
        {
            // INIT display error message  
            snprintf( string , 64 , 
            "\n[init ERROR] cannot place fork for child[%d] => suicide" , i );
            display_string( string );

            // INIT suicide
            exit( EXIT_FAILURE );
        }


        // INIT process fork process CHILD[i]
        ret_fork = fork();

        if( ret_fork < 0 )   // error in fork
        {
            // INIT display error message  
            snprintf( string , 64 , 
            "\n[init ERROR] cannot fork child[%d] => suicide" , i );
            display_string( string );

            // INIT suicide
            exit( EXIT_FAILURE );
        }
        else if( ret_fork == 0 )                    // we are in CHILD[i] process
        {

#if DEBUG_PROCESS_INIT
            snprintf( string , 64 ,
            "\n[init] CHILD[%d] process forked / call execve", i );
            display_string( string );
#endif
            // CHILD[i] process exec process KSH[i]
            ret_exec = execve( "/bin/user/ksh.elf" , NULL , NULL ); 

            if ( ret_exec )   // error in exec              
            {
                // CHILD[i] display error message
                snprintf( string , 64 , 
                "\n[init ERROR] CHILD[%d] cannot exec KSH" , i );
                display_string( string );

                // CHILD[i] suicide
                exit( EXIT_FAILURE );
            }
        }
        else                                      // we are in INIT process
        {
            // INIT display CHILD[i] process PID
            snprintf( string , 64 ,
            "[init] (pid 0x1) create ksh[%d] (pid %x)", i , ret_fork ); 
            display_string( string );
        }
    } 
  
#if DEBUG_PROCESS_INIT
{
    // keep blocked for 2 seconds
    // to allow all KSH[i] process 
    // to be launched before display 

    sleep( 2 );

    unsigned int  x;             // cluster x coordinate
    unsigned int  y;             // cluster y coordinate
    unsigned int  cxy;           // cluster identifier
    unsigned int  lid;           // core local index

    unsigned int  x_size       = config.x_size;
    unsigned int  y_size       = config.y_size;
    unsigned int  ncores       = config.ncores;

    // INIT displays processes and threads in all clusters
    for( x = 0 ; x < x_size ; x++ )
    {
        for( y = 0 ; y < y_size ; y++ )
        {
            cxy = HAL_CXY_FROM_XY( x , y );
            display_cluster_processes( cxy , 0 );
            for( lid = 0 ; lid < ncores ; lid++ )
            { 
                display_sched( cxy , lid );
            }
        }
    }
}
#endif

    // This loop detects the termination of the KSH[i] processes,
    // and recreate a new KSH[i] process when required.
    while( 1 )
    {
        // block on child processes termination
        rcv_pid = wait( &status );

        if( WIFSTOPPED( status ) )                         // stopped => unblock it
        {
            // display string to report unexpected KSH process block
            snprintf( string , 64 , "[init] KSH process %x stopped => unblock it" , rcv_pid );
            display_string( string ); 

            // TODO : unblock KSH [AG]

        }  // end KSH stopped handling

        if( WIFSIGNALED( status ) || WIFEXITED( status ) )  // killed => recreate it
        {
            // display string to report KSH process termination
            snprintf( string , 64 , "[init] KSH process %x terminated => recreate", rcv_pid );
            display_string( string ); 

            // INIT process fork a new CHILD process
            ret_fork = fork();

            if( ret_fork < 0 )                          // error in fork
            {
                // INIT display error message
                snprintf( string , 64 , "[init ERROR] cannot fork child => suicide");
                display_string( string );

                // INIT suicide
                exit( 0 );
            }
            else if( ret_fork == 0 )                    // we are in CHILD process
            {
                // CHILD process exec process KSH
                ret_exec = execve( "/bin/user/ksh.elf" , NULL , NULL ); 

                if ( ret_exec )   // error in exec              
                {
                    // CHILD display error message on TXT0 terminal
                    snprintf( string , 64 , "[init ERROR] CHILD cannot exec KSH" );
                    display_string( string );
                }
            }
            else                                       // we are in INIT process
            {
                // INIT display new KSH process PID
                snprintf( string , 64 , "[init] re-created KSH / pid = %x", ret_fork ); 
                display_string( string );
            }
        } // end KSH kill handling

    }  // end while waiting KSH[i] termination

} // end main()

