/************************************************************************
 *
 * runall.c - Unified runall replacement
 *
 * $Id$
 *
 ************************************************************************
 *
 * Copyright 2006 The Apache Software Foundation or its licensors,
 * as applicable.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **************************************************************************/

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <ctype.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/stat.h>

/*** watchdog structure ***/
struct exec_attrs {
    int status;
    int signo;
    int core;
    int killed;
};

/*** parse_opts structure ***/
struct env_opts {
    int timeout;
    char* exe_opts;
    char* in_root;
};

/***** Begin watchdog logic ******/

static int
alarm_timeout;   /* set to a non-zero value when a non-zero timeout expires */

const char * get_signame (const int signo)
{
    static const struct {
        int         val;
        const char* str;
    } names[] = {

#undef SIGNAL
#define SIGNAL(val)   { val, #val }

#ifdef SIGABRT
        SIGNAL (SIGABRT),
#endif   /* SIGABRT */
#ifdef SIGALRM
        SIGNAL (SIGALRM),
#endif   /* SIGALRM */
#ifdef SIGBUS
        SIGNAL (SIGBUS),
#endif   /* SIGBUS */
#ifdef SIGCANCEL
        SIGNAL (SIGCANCEL),
#endif   /* SIGCANCEL */
#ifdef SIGCHLD
        SIGNAL (SIGCHLD),
#endif   /* SIGCHLD */
#ifdef SIGCKPT
        SIGNAL (SIGCKPT),
#endif   /* SIGCKPT */
#ifdef SIGCLD
        SIGNAL (SIGCLD),
#endif   /* SIGCLD */
#ifdef SIGCONT
        SIGNAL (SIGCONT),
#endif   /* SIGCONT */
#ifdef SIGDIL
        SIGNAL (SIGDIL),
#endif   /* SIGDIL */
#ifdef SIGEMT
        SIGNAL (SIGEMT),
#endif   /* SIGEMT */
#ifdef SIGFPE
        SIGNAL (SIGFPE),
#endif   /* SIGFPE */
#ifdef SIGFREEZE
        SIGNAL (SIGFREEZE),
#endif   /* SIGFREEZE */
#ifdef SIGGFAULT
        SIGNAL (SIGGFAULT),
#endif   /* SIGGFAULT */
#ifdef SIGHUP
        SIGNAL (SIGHUP),
#endif   /* SIGHUP */
#ifdef SIGILL
        SIGNAL (SIGILL),
#endif   /* SIGILL */
#ifdef SIGINFO
        SIGNAL (SIGINFO),
#endif   /* SIGINFO */
#ifdef SIGINT
        SIGNAL (SIGINT),
#endif   /* SIGINT */
#ifdef SIGIO
        SIGNAL (SIGIO),
#endif   /* SIGIO */
#ifdef SIGIOT
        SIGNAL (SIGIOT),
#endif   /* SIGIOT */
#ifdef SIGK32
        SIGNAL (SIGK32),
#endif   /* SIGK32 */
#ifdef SIGKILL
        SIGNAL (SIGKILL),
#endif   /* SIGKILL */
#ifdef SIGLOST
        SIGNAL (SIGLOST),
#endif   /* SIGLOST */
#ifdef SIGLWP
        SIGNAL (SIGLWP),
#endif   /* SIGLWP */
#ifdef SIGPIPE
        SIGNAL (SIGPIPE),
#endif   /* SIGPIPE */
#ifdef SIGPOLL
        SIGNAL (SIGPOLL),
#endif   /* SIGPOLL */
#ifdef SIGPROF
        SIGNAL (SIGPROF),
#endif   /* SIGPROF */
#ifdef SIGPTINTR
        SIGNAL (SIGPTINTR),
#endif   /* SIGPTINTR */
#ifdef SIGPTRESCHED
        SIGNAL (SIGPTRESCHED),
#endif   /* SIGPTRESCHED */
#ifdef SIGPWR
        SIGNAL (SIGPWR),
#endif   /* SIGPWR */
#ifdef SIGQUIT
        SIGNAL (SIGQUIT),
#endif   /* SIGQUIT */
#ifdef SIGRESTART
        SIGNAL (SIGRESTART),
#endif   /* SIGRESTART */
#ifdef SIGRESV
        SIGNAL (SIGRESV),
#endif   /* SIGRESV */
#ifdef SIGSEGV
        SIGNAL (SIGSEGV),
#endif   /* SIGSEGV */
#ifdef SIGSTKFLT
        SIGNAL (SIGSTKFLT),
#endif   /* SIGSTKFLT */
#ifdef SIGSTOP
        SIGNAL (SIGSTOP),
#endif   /* SIGSTOP */
#ifdef SIGSYS
        SIGNAL (SIGSYS),
#endif   /* SIGSYS */
#ifdef SIGTERM
        SIGNAL (SIGTERM),
#endif   /* SIGTERM */
#ifdef SIGTHAW
        SIGNAL (SIGTHAW),
#endif   /* SIGTHAW */
#ifdef SIGTRAP
        SIGNAL (SIGTRAP),
#endif   /* SIGTRAP */
#ifdef SIGTSTP
        SIGNAL (SIGTSTP),
#endif   /* SIGTSTP */
#ifdef SIGTTIN
        SIGNAL (SIGTTIN),
#endif   /* SIGTTIN */
#ifdef SIGTTOU
        SIGNAL (SIGTTOU),
#endif   /* SIGTTOU */
#ifdef SIGUNUSED
        SIGNAL (SIGUNUSED),
#endif   /* SIGUNUSED */
#ifdef SIGURG
        SIGNAL (SIGURG),
#endif   /* SIGURG */
#ifdef SIGUSR1
        SIGNAL (SIGUSR1),
#endif   /* SIGUSR1 */
#ifdef SIGUSR2
        SIGNAL (SIGUSR2),
#endif   /* SIGUSR2 */
#ifdef SIGVTALRM
        SIGNAL (SIGVTALRM),
#endif   /* SIGVTALRM */
#ifdef SIGWAITING
        SIGNAL (SIGWAITING),
#endif   /* SIGWAITING */
#ifdef SIGWINCH
        SIGNAL (SIGWINCH),
#endif   /* SIGWINCH */
#ifdef SIGWINDOW
        SIGNAL (SIGWINDOW),
#endif   /* SIGWINDOW */
#ifdef SIGXCPU
        SIGNAL (SIGXCPU),
#endif   /* SIGXCPU */
#ifdef SIGXFSZ
        SIGNAL (SIGXFSZ),
#endif   /* SIGXFSZ */
#ifdef SIGXRES
        SIGNAL (SIGXRES),
#endif   /* SIGXRES */
        { -1, 0 }
    };

    size_t i;
    static char def[16];

    for (i = 0; i != sizeof names / sizeof *names; ++i) {
        if (names [i].val == signo) {
            return names [i].str;
        }
    }

    /* We've run out of known signal numbers, so use a default name */
    snprintf (def, sizeof(def), "SIG#%d", signo);
    return def;
}

static void
handle_alrm (const int signo)
{
    if(SIGALRM==signo)
        alarm_timeout = 1;
}

static struct exec_attrs
wait_for_child (const int timeout, const char* exec_name, const pid_t child_pid)
{
    static const int signals[] = {
        SIGHUP, SIGINT, SIGTERM, SIGKILL, SIGKILL
    };

    static const unsigned sigcount = sizeof (signals) / sizeof (int);

    struct exec_attrs state = {-1, 0, -1, 0};

    unsigned tryno  =  0;

    if (timeout>0) alarm (timeout);

    while(1) {
        const pid_t wait_pid = waitpid (child_pid, &state.status, 0);
        if (child_pid == wait_pid) {

            if (WIFEXITED (state.status)) {
                state.status = WEXITSTATUS (state.status);
                state.signo = 0;
                break;
            }
            else if (WIFSIGNALED (state.status)) {
                state.signo  = WTERMSIG (state.status);
#ifdef WCOREDUMP
                state.core = WCOREDUMP (state.status);
#endif   /* WCOREDUMP */
                state.status = -1;
                break;
            }
        }
        else if ((pid_t)-1 == wait_pid && EINTR != errno){
            /* waitpid() error */
            fprintf (stderr, "waitpid(%d) error: %s\n",
                     (int)child_pid, strerror (errno));
        }
        else if (alarm_timeout) {
            /* timeout elapsed, so send a signal to the child*/
            kill (child_pid, signals [tryno]);

            /* Record the signal used*/
            state.killed  = signals [tryno];

            /* Step to the next signal */
            if(++tryno>sigcount){
                /* Still running, but we've run out of signals to try
                   Therefore, we'll set error flags and break out of 
                   the loop.
                */
                state.status = -1;
                state.signo  = -1;
                state.killed = -1;
                fputs ("No more kill options, Bailing\n", stderr);
                break;
            }

            /* Reset the alarm */
            alarm_timeout = 0;
            alarm (1);
        }
    }

    /* Clear alarm */
    alarm (0);

    return state;
}

/**
 * @desc takes a directory strucure root and an executable name, and tries
 * to open an input file based on these parameters
 * @param root the root of the directory structure storing input files
 * @param exec_name the name of executable being run
 * @returns -1 if no file was opened, or the descriptor of the opened file
 **/

static int open_input(const char* root, const char* exec_name){
    const size_t root_len = strlen (root);
    const size_t exec_len = strlen (exec_name);
    char* tmp_name;
    int intermit;

    if(!root || !root_len) return -1;
    
    tmp_name = (char*)malloc (root_len + exec_len + 17);

    /* Try in_root/manual/in/exec_name.in */
    memcpy (tmp_name, root, root_len+1);
    strcat (tmp_name, "/manual/in/");
    strcat (tmp_name, exec_name);
    strcat (tmp_name, ".in");
    intermit = open (tmp_name, O_RDONLY);
    
    /* If we opened the file, return the descriptor */
    if(0 <= intermit) return intermit;

    /* If the file exists (errno isn't ENOENT), exit */
    if(ENOENT != errno){
        fprintf(stderr, "open(%s) failed: %s\n", tmp_name, 
                strerror (errno));
        exit(1);
    }

    /* Try in_root/tutorial/in/exec_name.in */
    memcpy (tmp_name, root, root_len+1);
    strcat (tmp_name, "/tutorial/in/");
    strcat (tmp_name, exec_name);
    strcat (tmp_name, ".in");
    intermit = open (tmp_name, O_RDONLY);

    /* If there was an error and the file exists (errno isn't ENOENT), exit */
    if(-1 == intermit && ENOENT != errno){
        fprintf (stderr, "open(%s) failed: %s\n", tmp_name, 
                strerror (errno));
        exit (1);
    }

    /* otherwise, return the file descriptor */
    return intermit;
}

#define FILE_TEST(op, x)                                                \
    if(-1==(x)){                                                        \
        fprintf (stderr, op " failed: %s\n", strerror (errno));         \
        exit (1);                                                       \
    }

struct exec_attrs exec_file (const struct env_opts opts, const char* exec_name, 
                             const char *argv[])
{
    struct sigaction act;
    const pid_t child_pid = fork ();

    if (0 == child_pid) {   /* child */
        char* tmp_name = (char*)malloc (strlen (argv[0]) + 5);
        int error_cache = dup (2);
        int intermit;
        FILE* error_file;

        /* Cache stdout (hopefully) for use if execv() fails */
        FILE_TEST ("dup (stderr)", error_cache);
        error_file = fdopen (error_cache,"a");
        if(0 == error_file){
            fprintf (stderr, "open (stderr) failed: %s\n", strerror (errno));
            exit (1);
        }

        /* Redirect stdout */
        strcpy (tmp_name, argv[0]);
        strcat (tmp_name, ".out");
        intermit = open (tmp_name, O_WRONLY | O_CREAT | O_TRUNC, 
                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        FILE_TEST ("open (stdout)",intermit);
        FILE_TEST ("dup2 (stdout)",dup2(intermit, 1));
        FILE_TEST ("close (temp-stdout)",close(intermit));
        free (tmp_name);

        /* Redirect or close stdin */
        intermit = open_input (opts.in_root, exec_name);
        if(-1 != intermit){
            FILE_TEST ("dup2 (stdin)", dup2 (intermit, 0));
            FILE_TEST ("close (temp-stdin)", close (intermit));
        }else{
            FILE_TEST ("close (stdin)", close (0));
        }

        /* Close stderr */
        FILE_TEST("close (stderr)", close (2));

        execv (argv [0], argv);

        fprintf (error_file, "execv (\"%s\", ...) error: %s\n",
                 argv [0], strerror (errno));

        exit (1);
    }

    /* parent */

    /* Set up alarm */
    alarm_timeout=0;

    /* Need to use sigaction rather than signal due to linux glitch */
    memset (&act, 0, sizeof act);
    act.sa_handler = handle_alrm;
    sigaction (SIGALRM, &act, 0);
    
    return wait_for_child (opts.timeout, exec_name, child_pid);
}

/***** Begin Option parsing ******/

void show_usage(const char* exec, const int status)
{
    fprintf (stderr, "Usage: %s [OPTIONS] [targets]\n", exec);
    fputs ("\n"
"  Treats each token in targets as the path to an executable.  Each target\n"
"  enumerated is executed, killed if execution takes longer than a certain\n"
"  (configurable) period of time, and the output is processed.\n"
"\n", stderr);
    fputs ("    -d dir      Directory root for output reference files\n"
"    -h, -?      Display usage information and exit\n"
"    -t seconds  Watchdog timeout before killing target (default is 10 "
"       seconds)\n"
"    -x opts     Command line options to pass to targets\n"
"    --          Terminate option processing, treat all following arguments "
          "       as targets\n", stderr);
    exit (status);
}

int eval_options (struct env_opts * opts, const int argc, const char *argv[])
{
    int i;

    for (i = 1; i < argc && '-' == argv [i][0]; ++i) {

        switch (argv [i][1]){
        case '?':
        case 'h':
            show_usage (argv [0], 0);
        case 'r':
            ++i; /* Ignore -r option makefile (compat) */
            break;
        case 't':
            opts->timeout = atoi (argv [++i]);
            break;
        case 'd':
            opts->in_root = argv [++i];
            break;
        case 'x':
            opts->exe_opts = argv [++i];
            break;
        case '-':
            /*const size_t arglen = strlen (argv [i]);*/
            switch (argv [i][2]){
            case '\0':
                /* abort processing on --, eat token */
                return i+1;
            }
        default:
            printf ("Unknown option: %s\n", argv [i]);
            show_usage(argv [0], 1);
        }
    }

    return i;
}

#define ALLOC_TEST(x)                                                   \
    if(0==(x)){                                                         \
        fprintf (stderr, "malloc() failed: %s\n", strerror (errno));    \
        exit (1);                                                       \
    }

void split_child_opts (const char* raw_opts, char*** argc_out)
{
    if(raw_opts && strlen(raw_opts)){
        /* Need to turn the opts string into an argc/argv array pair.
           This logic isn't UTF-8 safe.
           Also, note that this doesn't support embeding redirection
           or piping into the arguments-we aren't a full shell after all
        */
        char in_quote = 0;
        int in_escape = 0;
        int in_token = 0;
        char *pos, *target, *last;
        char **table_pos;

        table_pos = (*argc_out) = (char**)malloc (
            (strlen (raw_opts) + 5) * sizeof (char*) / 2);
        ALLOC_TEST (*argc_out);
        /* (strlen(raw_opts)+5)/2 is overkill for the most situations, 
           but it is just large enough to handle the worst case scenario
           (worst case is similar to 'x y' or 'x y z', requiring lengths
           of 4 and 5 respectively.)
        */

        last = target = (*argc_out)[1] = (char *)malloc (strlen (raw_opts) + 2);
        ALLOC_TEST ((*argc_out)[1]);

        /* Transcribe the contents, handling escaping and splitting */
        for (pos = raw_opts; *pos; ++pos){
            if(in_escape){
                *(target++) = *pos;
                in_escape = 0;
                continue;
            }
            if(isspace(*pos)){
                if(in_quote){
                    *(target++)=*pos;
                }else {
                    if(in_token){
                        *(target++)='\0';
                        *(++table_pos)=last;
                        in_token=0;
                    }
                    last = target;
                }
                continue;
            }
            in_token=1;
            switch (*pos){
            case '\\':
                in_escape=1;
                break;
            case '"':
            case '\'':
                if(*pos==in_quote){
                    in_quote=0;
                    break;
                }else if(0==in_quote){
                    in_quote=*pos;
                    break;
                }
                /* intentionally falling through */
            default:
                *(target++)=*pos;
            }
        }

        if(in_token){ /* close and record the final token */
            *(target++)='\0';
            *(++table_pos)=last;
        }
        *(++table_pos)=(char*)0;/*And terminate the array*/

    }else{
        /* Alloc a an index array to hold the program name  */
        (*argc_out)=(char**)malloc(2*sizeof(char*));
        ALLOC_TEST(*argc_out);

        /* And tie the two together */
        (*argc_out)[1]=(char*)0;
    }
}

/***** Begin Main Loop ******/

/*
const char* log_level_names
*/

static void check_test(char* exec_name, char* out_name){
    FILE* data = fopen(out_name, "r");
    unsigned r_lvl=0, r_active=0, r_total=0;
    unsigned asserts=0, failed=0;
    int fmt_ok=0;
    unsigned fsm=0;
    char tok;

    if(0 == data){
        if(ENOENT != errno){
            printf("Error opening %s: %s\n", out_name, strerror (errno));
            return;
        }
        puts("OUTPUT\n");
        return;
    }

    for(tok = fgetc(data); fsm<6 && !feof(data); tok = fgetc(data)){
        switch(tok){
        case '\n':
            fsm=1;
            break;
        case '#':
            fsm=(1==fsm)?2:0;
            break;
        case ' ':
            fsm=(2==fsm)?3:((4==fsm)?5:0);
            break;
        case '|':
            fsm=(3==fsm)?4:0;
            break;
        case '(':
            if(5==fsm){
                fseek(data, -6, SEEK_CUR);
                fsm++;
                break;
            }
        default:
            fsm=0;
        }
    }

    if(!feof(data)){
        while(3==fscanf(data, "# | (S%u) %*s | %u | %u | %*u%% |\n", 
                        &r_lvl, &r_active, &r_total)){
            if(6<r_lvl){
                /* The 0.new tests produces errors, and are all 
                   expected to be active, so invert the total */
                if(8==r_lvl && 0==strcmp(exec_name,"0.new"))
                    r_active = r_total-r_active;
                failed += r_active;
                asserts += r_total;
                if(failed < r_active || asserts < r_total){
                    puts("OVERFLOW\n");
                    return;
                }
            }
            /* else if(1 < r_lvl) warning*/
            fmt_ok=1;
        }
    }
    
    if(fmt_ok){
        unsigned pcnt=0;
        if(asserts){
            pcnt=100*(asserts-failed)/asserts;
        }
        printf("     0 %6d %6d %5d%%\n", asserts, failed, pcnt);
    }else{
        puts("FORMAT\n");
    }

    fclose(data);
}

#undef FILE_TEST
#define FILE_TEST(op, x)                                                \
    if(-1==(x)){                                                        \
        fprintf(stderr, op " failed: %s\n", strerror (errno));          \
        exit(1);                                                        \
    }

static void check_example(char* root, char* exec_name, char* out_name){
    struct stat file_info;
    char* ref_name = (char*)malloc(strlen(root) 
                                   + strlen(exec_name) + 19);
    int state = -1;

    /* Try in_root/manual/out/exec_name.out */
    strcpy(ref_name, root);
    strcat(ref_name, "/manual/out/");
    strcat(ref_name, exec_name);
    strcat(ref_name, ".out");

    if(0 > stat(ref_name, &file_info)){
        if(ENOENT != errno){
            printf("stat(%s) error: %s\n", ref_name, 
                   strerror (errno));
            free(ref_name);
            return;
        }
                        
        /* If that doesn't exist, try 
           in_root/tutorial/out/exec_name.out */
        strcpy(ref_name, root);
        strcat(ref_name, "/tutorial/out/");
        strcat(ref_name, exec_name);
        strcat(ref_name, ".out");

        if(0 > stat(ref_name, &file_info)){
            if(ENOENT != errno){
                printf("stat(%s) error: %s\n", ref_name, 
                       strerror (errno));
            }else{
                puts("OUTPUT\n");
            }
            free(ref_name);
            return;
        }
    }

    const pid_t child_pid = fork ();

    if (0 == child_pid) {   /* child */
        /* Cache stdout (hopefully) for use if execv() fails */
        int error_cache = dup(2);
        FILE* error_file;
        FILE_TEST("dup(stderr)", error_cache);

        FILE_TEST("close(stdin)",close(0));
        FILE_TEST("close(stdin)",close(1));
        FILE_TEST("close(stderr)",close(2));

        /* Todo: diff with --strip-trailing-cr on windows */
        execlp("diff", "diff", "-q", ref_name, out_name, (char *)0);

        if((error_file = fdopen(error_cache,"a")))
            fprintf (error_file, "execlp(\"diff\", ...) error: %s\n",
                     strerror (errno));

        exit(2);
    }

    while(1){
        const pid_t wait_pid = waitpid (child_pid, &state, 0);

        if (child_pid == wait_pid) {

            if (WIFEXITED (state)) {
                switch((state=WEXITSTATUS (state))){
                case 0:
                    puts("     0\n");
                    break;
                case 1:
                    puts("OUTPUT\n");
                    break;
                default:
                    printf("diff returned %d\n",state);
                }
                break;
            }
            else if (WIFSIGNALED (state)) {
                printf("diff exited with %s\n", 
                       get_signame (WTERMSIG (state)));
                break;
            }
/*
            else if (WIFSTOPPED (state)) {
                printf ("process %d stopped\n", (int)child_pid);
            }
            else if (WIFCONTINUED (state)) {
                printf ("process %d continued\n", (int)child_pid);
            }
*/
        }
    }

    free(ref_name);
    free(out_name);
}

int main (int argc, const char *argv[])
{
    struct env_opts opts = {10, 0, 0};
    char **childargv = 0;

    if (1 < argc && '-' == argv [1][0]) {
        const int nopts = eval_options (&opts, argc, argv);

        if (0 > nopts)
            return 1;

        argc -= nopts;
        argv += nopts;
    }else{
        --argc;
        ++argv;
    }

    split_child_opts(opts.exe_opts, &childargv);

    if (0 < argc){
        int i;
        puts("NAME               STATUS ASSRTS FAILED PERCNT\n");
        for(i=0; i<argc; ++i){
            struct exec_attrs status;
            struct stat file_info;
            char* exec_name = basename((childargv[0]=argv[i]));

            printf("%-18.18s ",exec_name);
            
            if(0 > stat(childargv[0], &file_info)){
                if(ENOENT != errno){
                    printf("Stat error: %s\n",strerror (errno));
                    continue;
                }
                file_info.st_mode = 0; /* force mode on non-existant file to 0 */
            }
            if(0 == (file_info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
                size_t name_len=strlen(exec_name);
                char* tmp_name;

                /* If target is a .o file, it's good (since it built) */
                if('.'==exec_name[name_len-1] && 'o'==exec_name[name_len]){
                    puts("     0\n");
                    continue;
                }

                /* Otherwise, check for the .o file */
                tmp_name = (char*)malloc(strlen(argv[i])+3);
                strcpy(tmp_name, argv[i]);
                strcat(tmp_name,".o");

                if(0 > stat(tmp_name, &file_info)){
                    if(ENOENT != errno){
                        printf("stat(%s) error: %s\n", tmp_name, 
                               strerror (errno));
                    }else{
                        puts("COMP\n");
                    }
                }else{
                    puts("LINK\n");
                }
                
                free(tmp_name);
                continue;
            }

            status = exec_file (opts, exec_name, childargv);

            if(0 == status.signo && 0 == status.status){
                char* out_name = (char*)malloc(strlen(argv[i])+5);
                strcpy(out_name, argv[i]);
                strcat(out_name,".out");

                if(!opts.in_root || !strlen(opts.in_root)){
                    /* If there not an input directory, look at the 
                       assertion tags */
                    check_test(exec_name, out_name);
                }else{
                    /* Otherwise, diff against the output file */
                    check_example(opts.in_root, exec_name, out_name);
                }
            }else if(0 == status.signo && 0 <= status.status){
                switch(status.status){
                case 126:
                    puts("EXIST\n");
                    break;
                case 127:
                    puts("EXEC\n");
                    break;
                default:
                    printf("%6d\n", status.status);
                }
            }else if(-1 == status.status && 0 < status.signo){
                printf("%s\n", get_signame (status.signo));
            }else {
                printf("(%d,%d,%d)\n", status.status, status.signo, 
                       status.core);
            }
        }
    }

    if(childargv[1]) free(childargv[1]);
    free(childargv);

    return 0;
}

