5

How can the master process know that the child process failed to execute the file (e.g. no such file or directory)? For example, in the following code, how can we get run() to return something other than 0? Thanks!

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

enum ErrorType { noError, immediateFailure, returnFailure, forkFailure };

using namespace std;

int run(void)
{
    int error = noError;
    //char proc[] = "/bin/uname";
    char proc[] = "/usr/bin/Idontexist";
    char *const params[] = {proc, NULL};

    pid_t pid = fork();

    printf("pid = %d\n",pid);

    if (pid == 0)
    {
        if ( execv(proc,params)==-1 )
        {
            error = immediateFailure;
        }
        printf("child: error = %d\n",error);
    }
    else if (pid > 0)
    {
        /*  This is the parent process
         *  incase you want to do something
         *  like wait for the child process to finish
         */
        int waitError;
        waitpid(pid, &waitError, 0);

        if ( waitError )
            error = returnFailure;

        printf("parent: error = %d, waitError = %d\n",error,waitError);
    }
    else
    {
        error = forkFailure;
    }

    return error;
}

int main(void)
{
    printf("run() = %d\n",run());

    return 0;
}

output:

pid = 4286
pid = 0
child: error = 1
run() = 1
parent: error = 0, waitError = 0
run() = 0
psilouette
  • 83
  • 1
  • 6

4 Answers4

1

You need to return the error code from main, or exit() with the value. Now you're returning 0 from main in all cases, and that will be the exit code of the child that the parent receives from waitpid.

The best fix is to add _exit(error) in the child process:

printf("child: error = %d\n",error);
_exit(error);

In any case use integer constants instead of random enum constants enum. 1 and 2 are common return values from many unix commands with 1 meaning that the command was not successful (for example grep didn't found any matches), and 2+ meaning that the command really failed (wrong arguments or such)`.

bash, zsh and other shells use 127 as the exit code to signal command not found / not runnable; thus this would be recommended:

#define COMMAND_NOT_RUNNABLE 127

/* ... */

execv(proc, params);  // exec never returns if successful.
perror(proc);
_exit(COMMAND_NOT_RUNNABLE);

The reason to use _exit in the child process is that it doesn't flush the stdio buffers (that would exist on the parent side too) and that it doesn't run the atexit hooks. (Thanks Andrew Henle)

  • 2
    A bit old, but calling `exit()` after a failed `exec()` after a `fork()` can cause serious problems. `exit()` will flush all output `FILE *` streams and the data they hold inherited from the parent process's address space. If the parent process is multithreaded that can deadlock if any of the streams were locked by different threads when `exec()` was called. Functions registered by `atexit()` will also be called. If any C++ code is linked in, static destructors will be called. `_exit()` is much safer. – Andrew Henle Oct 10 '19 at 19:22
1

In my case exit(EXIT_FAILURE) don't lead to return to waitpid() EXIT_FAILURE status of child process, always 0. My solution is to abort() the child process if execv()/execve() failed to execute.

J. Scott Elblein
  • 4,013
  • 15
  • 58
  • 94
nnnsoft
  • 46
  • 3
0

The problem is the handling of the error after the execv fails. The child sets error to immediateFailure and returns that to main. Then main prints the error number, and returns 0. But 0 means success, so the parent process thinks that the child succeeded.

The solution is to return the error number from main, or call exit in the child code, e.g.

if (pid == 0)
{
    execv(proc,params);
    error = immediateFailure;
    printf("child: error = %d\n",error);
    exit( error );
}

Note that you don't need to check the return value from execv, since execv won't return if it succeeds. It will only return if it fails, and will always return -1.

user3386109
  • 34,287
  • 7
  • 49
  • 68
0

In addition to the proposed exit(error); you should give specific options for waitpid. Your code did not work for me until I changed the call to waitpid(pid, &waitError, WUNTRACED | WCONTINUED);

GMichael
  • 2,726
  • 1
  • 20
  • 30