Subversion Repositories tendra.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
                 Crown Copyright (c) 1997
    
    This TenDRA(r) Computer Program is subject to Copyright
    owned by the United Kingdom Secretary of State for Defence
    acting through the Defence Evaluation and Research Agency
    (DERA).  It is made available to Recipients with a
    royalty-free licence for its use, reproduction, transfer
    to other parties and amendment for any purpose not excluding
    product development provided that any such use et cetera
    shall be deemed to be acceptance of the following conditions:-
    
        (1) Its Recipients shall ensure that this Notice is
        reproduced upon any copies or amended versions of it;
    
        (2) Any amended version of it shall be clearly marked to
        show both the nature of and the organisation responsible
        for the relevant amendment or amendments;
    
        (3) Its onward transfer from a recipient to another
        party shall be deemed to be that party's acceptance of
        these conditions;
    
        (4) DERA gives no warranty or assurance as to its
        quality or suitability for any purpose and DERA accepts
        no liability whatsoever in relation to any use to which
        it may be put.
*/


#include "config.h"
#include "external.h"
#include "filename.h"
#include "list.h"
#include "archive.h"
#include "execute.h"
#include "flags.h"
#include "main.h"
#include "utility.h"


/*
    CURRENT COMMAND

    The current command is a variable sized array of strings, command.
    A complete command is terminated by a null string.
*/

static char **command = null ;
static int command_size = 0 ;
static int cmd_no = 0 ;


/*
    DELAY SIGNAL HANDLING

    Because the producer occasionally dies with a signal after it has
    output some useful errors it is benificial to run the tot even
    after the signal has been caught. These globals and the functions
    below for using them tell the execute function to delay calling the
    signal handler until after the tot is called.
*/

static char *last_signaled_cmd = null ;
static int last_signal = 0 ;
static int delay_signal_handling = 0 ;

void enable_delayed_signal
    PROTO_Z () 
{
    delay_signal_handling = 1 ;
    return ;
}

void disable_delayed_signal
    PROTO_Z ()
{
    delay_signal_handling = 0 ;
    return ;
}

void process_delayed_signal
    PROTO_Z ()
{
    if ( last_signal != 0 ) {
        last_command = last_signaled_cmd ;
        handler ( last_signal ) ;
    }
    return ;
}


/*
    ADD A STRING TO THE CURRENT COMMAND

    This routine adds the string s to the command array.  If s is null
    then the array counter is reset to the beginning.  The array counter
    is not advanced for empty strings.
*/

void cmd_string
    PROTO_N ( ( s ) )
    PROTO_T ( char *s )
{
    if ( cmd_no >= command_size ) {
        command_size += 1000 ;
        command = realloc_nof ( command, char *, command_size ) ;
    }
    command [ cmd_no ] = s ;
    if ( s == null ) {
        cmd_no = 0 ;
    } else if ( *s ) {
        cmd_no++ ;
    }
    return ;
}


/*
    ADD A FILENAME TO THE CURRENT COMMAND

    This routine adds the names of the files given by p to the command
    array.
*/

void cmd_filename
    PROTO_N ( ( p ) )
    PROTO_T ( filename *p )
{
    for ( ; p != null ; p = p->next ) cmd_string ( p->name ) ;
    return ;
}


/*
    ADD A LIST TO THE CURRENT COMMAND

    This routine adds the list of strings given by p to the command array.
*/

void cmd_list
    PROTO_N ( ( p ) )
    PROTO_T ( list *p )
{
    for ( ; p != null ; p = p->next ) cmd_string ( p->item ) ;
    return ;
}


/*
    OVERALL COMPILATION STATUS

    This flag is true is an execution error occurs.
*/

boolean exec_error = 0 ;

void reset_exec_error 
    PROTO_Z ()
{
    exec_error = 0 ;
    return ;
}


/*
    LAST COMMAND

    The name of the last command executed, and its return value (zero
    indicating success) are stored.
*/

char *last_command = null ;
int last_return = 0 ;


/*
    THE CURRENT PROCESS

    When a process is active, its pid is stored as running_pid.  The
    value -1 is used to indicate that no process is active.
*/

#if FS_FORK
static long running_pid = -1 ;
#endif


/*
    KILL ANY STRAY PROCESSES

    Occasionally a runaway process may occur.  This routine is indended
    to deal with these by sending the signal SIGTERM to the process.
    This routine is POSIX compliant.
*/

void kill_stray
    PROTO_Z ()
{
#if FS_FORK
    if ( running_pid == -1 ) return ;
    IGNORE kill ( ( pid_t ) running_pid, SIGTERM ) ;
    running_pid = -1 ;
#endif
    return ;
}


/*
    LIST OF FILES TO BE REMOVED BY REMOVE_JUNK

    This gives the list of the files which are to be removed if an
    error occurs.
*/

static filename *junk = null ;


/*
    REMOVE ANY INCOMPLETE OUTPUT FILES

    Any files which are being created when an error occurs should be
    removed.
*/

void remove_junk
    PROTO_Z ()
{
    if ( !dry_run && !flag_keep_err ) {
        filename *p ;
        for ( p = junk ; p != null ; p = p->next ) {
            if ( p->storage == OUTPUT_FILE ) IGNORE remove ( p->name ) ;
        }
    }
    junk = null ;
    return ;
}


/*
    PRINT COMMAND INTO BUFFER

    This routine prints the current command into a buffer and returns
    a pointer to the result.
*/

static void print_cmd
    PROTO_N ( ( b ) )
    PROTO_T ( char *b )
{
    char **s ;
    for ( s = command ; *s != null ; s++ ) {
        *b = ' ' ;
        IGNORE strcpy ( b + 1, *s ) ;
        b += strlen ( b ) ;
    }
    return ;
}


/*
    EXECUTE THE CURRENT COMMAND

    This routine executes the command given by the command array.  It
    returns either output, the list of all output files, if successful,
    or null, otherwise.  The routine is POSIX compliant.  It uses fork
    and execv from unistd.h to fork a process and various routines from
    sys/wait.h to analyse the result.  The interface with sys/wait.h
    has been abstracted to also allow the BSD implementation.
*/

filename *execute
    PROTO_N ( ( input, output ) )
    PROTO_T ( filename *input X filename *output )
{
    char *cmd ;
    int err = 0 ;
    boolean filled_buff = 0 ;
    char buff [ buffer_size ] ;   
  
    cmd_string ( ( char * ) null ) ;
    cmd = command [0] ;
    if ( cmd == null ) {
        error ( INTERNAL, "Empty command" ) ;
        return ( null ) ;
    }
    last_command = cmd ;
    last_return = 0 ;
    junk = output ;

    if ( taciturn ) {
        /* Print input files if in taciturn mode */
        filename *p ;
        for ( p = input ; p != null ; p = p->next ) {
            if ( p->storage == INPUT_FILE ) {
                comment ( 1, "%s:\n", p->name ) ;
            }
        }
    }

    if ( verbose ) {
        /* Print command if in verbose mode */
        print_cmd ( buff ) ;
        filled_buff = 1 ;
        comment ( 1, "%s\n", buff + 1 ) ;
    }

    if ( cmd && strneq ( cmd, "builtin/", 8 ) ) {
        /* Check built in commands */
        cmd += 8 ;
        switch ( *cmd ) {
            case 'b' : {
                if ( streq ( cmd, "build_archive" ) ) {
                    err = build_archive ( command [1], command + 2 ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 'c' : {
                if ( streq ( cmd, "cat" ) ) {
                    err = cat_file ( command [1] ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 'm' : {
                if ( streq ( cmd, "mkdir" ) ) {
                    err = make_dir ( command [1] ) ;
                    goto execute_error ;
                }
                if ( streq ( cmd, "move" ) ) {
                    err = move_file ( command [1], command [2] ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 'r' : {
                if ( streq ( cmd, "remove" ) ) {
                    err = remove_file ( command [1] ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 's' : {
                if ( streq ( cmd, "split_archive" ) ) {
                    err = split_archive ( command [1], &output ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 't' : {
                if ( streq ( cmd, "touch" ) ) {
                    err = touch_file ( command [1], command [2] ) ;
                    goto execute_error ;
                }
                break ;
            }
            case 'u' : {
                if ( streq ( cmd, "undef" ) ) {
                    int sev ;
                    if ( dry_run ) {
                        sev = WARNING ;
                    } else {
                        sev = INTERNAL ;
                        err = 1 ;
                    }
                    cmd = command [1] ;
                    error ( sev, "The tool '%s' is not available", cmd ) ;
                    goto execute_error ;
                }
                break ;
            }
        }
        error ( SERIOUS, "Built-in '%s' command not implemented", cmd ) ;
        err = 1 ;

    } else if ( !dry_run ) {
        /* Call system commands */
#if FS_FORK
        {
            pid_t pid = fork () ;
            if ( pid == ( pid_t ) -1 ) {
                error ( SERIOUS, "Can't fork process" ) ;
                err = 1 ;
            } else {
                if ( pid ) {
                    wait_type status ;
                    running_pid = ( long ) pid ;
                    while ( process_wait ( &status ) != pid ) /* empty */ ;
                    running_pid = -1 ;
                    if ( process_exited ( status ) ) {
                        err = process_exit_value ( status ) ;
                        /* This only returns if there was no remembered
                           signal. */
                        process_delayed_signal () ;
                    } else {
                        if ( process_signaled ( status ) ) {
                            /* delay_signal_handling is a global that tells us
                               that it is ok to let the next call to execute
                               report that the command received a signal.
                               This supports the way that the producer is called. */
                            int sig = process_signal_value ( status ) ;
                            if ( delay_signal_handling && last_signal == 0 ) {
                                last_signaled_cmd = string_copy ( cmd ) ;
                                last_signal = sig ;
                            } else {
                                handler ( sig ) ;
                            }                                   
                        }
                        err = 1 ;
                    }
                    goto execute_error ;
                }
                IGNORE execve ( cmd, command, environment ) ;
                running_pid = -1 ;
                error ( SERIOUS, "Can't execute '%s'", cmd ) ;
                exit ( 2 ) ;
            }
        }
#else
        {
            wait_type status ;
            if ( !filled_buff ) {
                print_cmd ( buff ) ;
                filled_buff = 1 ;
            }
            err = system ( buff + 1 ) ;
            process_return ( status, err ) ;
            if ( process_exited ( status ) ) {
                err = process_exit_value ( status ) ;
                process_delayed_signal () ;
            } else {
                if ( process_signaled ( status ) ) {
                    /* delay_signal_handling is a global that tells us
                       that it is ok to let the next call to execute
                       report that the command received a signal.
                       This supports the way that the producer is called. */
                    int sig = process_signal_value ( status ) ;
                    if ( delay_signal_handling && last_signal == 0 ) {
                        last_signaled_cmd = string_copy ( cmd ) ;
                        last_signal = sig ;
                    } else {
                        handler ( sig ) ;
                    }
                }
                err = 1 ;
            }
        }
#endif
    }

    /* Deal with errors */
    execute_error : {      
        disable_delayed_signal () ;
        last_return = err ;
        if ( tidy_up ) {
            /* Remove unneeded files */
            filename *p ;
            for ( p = input ; p != null ; p = p->next ) {
                if ( p->storage == TEMP_FILE && p->type != BINARY_OBJ ) {
                    IGNORE remove ( p->name ) ;
                }
            }
        }
        if ( err ) {
            exec_error = 1 ;
            exit_status = EXIT_FAILURE ;
            if ( show_errors ) {
                /* Show when the error occurred */
                if ( !filled_buff ) print_cmd ( buff ) ;
                error ( INFO, "Error in '%s'", buff + 1 ) ;
            }
            remove_junk () ;
            return ( null ) ;
        }
        junk = null ;
        return ( output ) ;
    }
}