Search code examples
cioreturn-valueexit

How do I Exit a Process if a Child Fails?


The program I'm working with requires a secondary executable to run (for asset compression). For error handling reasons, I need the return value of the child process. According to the system man page,

If all system calls succeed, then the return value is the termination status of the child shell used to execute [the] command. (The termination status of a shell is the termination status of the last command it executes.)

According to this, my system call's return state should be the called process's return value. But it doesn't seem to be. Instead, when the Generator returns EXIT_FAILURE, the program receives an EXIT_SUCCESS, or 0. Am I misunderstanding something?

My code is below.

// Checks to see if the generator still exists using access().
bool asset_generator_availability = CheckAssetGenerator();

if (!asset_generator_availability) LogMessage("Assets already generated.");
else
{
    LogWarning(
        "Assets still uncompressed and unformatted. Running the Generator.");

    u8 generator_state = system("./AssetGenerator && rm AssetGenerator");
    // Here is the problem line.
    if (generator_state == EXIT_FAILURE) exit(EXIT_FAILURE);
}

Solution

  • This issue is occurring because when system successfully runs the called process it doesn't return the direct return value of the process run. It instead returns a "wait status" that can be examined using the macros described in waitpid(2). (i.e., WIFEXITED(), WEXITSTATUS(), and so on). The manual pages have more detail on this matter but for your code you could use WIFEXITED and WEXITSTATUS as follows:

    if (WIFEXITED(generator_state) && WEXITSTATUS(generator_state) == EXIT_FAILURE)
    {
    exit(EXIT_FAILURE);
    }
    

    In this code WIFEXITED checks that the child exited normally, and WEXITSTATUS checks the exit status of the child. However, system also provides other return values if the command is null, a child process can't be created or it's status can't be retrieved, a shell can't be executed in the child process. To account for these you can use a few other waitpid macros as follows:

    if (generator_state == -1)
    {
        exit(EXIT FAILURE);
    } //exits if system fails to create the child process or retrieve it's status.
    else
    {
        if (WIFEXITED(generator_state))
        {
            int exit_status = WEXITSTATUS(generator_state);
            if (exit_status == EXIT_FAILURE)
            {
                exit(EXIT_FAILURE);
            } //exits on the failure in the child code. You could add other failures in the same way.
            else
            {
            //Handle successful completion of the asset generator.
            }
        }
        else if (WIFSIGNALED(generator_state))
        {
            int signum = WTERMSIG(generator_state);
            LogError("Asset Generator was terminated by signal: %d", signum);
            exit(EXIT_FAILURE);
        }// Logs if the generator is terminated by a signal, and the signal number.
        else
        {
            if (WEXITSTATUS(generator_state) == 127)
            {
                LogError("Failed to execute shell in child process.");
            } //Logs if the shell couldn't be executed in the child process.
            else
            {
                LogError("Asset Generator terminated abnormally.");
                exit(EXIT_FAILURE);
            }// Logs if the generator is terminated in some other unexpected way.
        }
    }
    

    For the sake of the example I used a 'LogError' function for separate errors since I saw you had LogWarning in your previous code, if you don't have a LogError function you could create one or use some other method to account for separate errors.

    Hope this helps!

    Edit: Below are the manual pages for system and wait. https://man7.org/linux/man-pages/man3/system.3.html https://man7.org/linux/man-pages/man2/waitpid.2.html