Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle execvp(...) errors after fork()?

I do the regular thing:

  • fork()
  • execvp(cmd, ) in child

If execvp fails because no cmd is found, how can I notice this error in parent process?

like image 948
Łukasz Lew Avatar asked Oct 18 '09 13:10

Łukasz Lew


People also ask

What happens if execvp fails?

execvp() returns a negative value if the execution fails (e.g., the request file does not exist). The following is an example (in file shell.

Does execvp exit?

Once you call execvp the child process dies (for all practical purposes) and is replaced by the exec 'd process. The only way you can reach exit(0) there is if execvp itself fails, but then the failure isn't because the new program ended.

What is the purpose of execvp() system call?

The execvp function is most commonly used to overlay a process image that has been created by a call to the fork function. identifies the location of the new process image within the hierarchical file system (HFS).

How do I know if Execvp failed?

If any of the exec() functions returns, an error will have occurred. The return value is -1, and errno will be set to indicate the error.


2 Answers

The well-known self-pipe trick can be adapted for this purpose.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>

int main(int argc, char **argv) {
    int pipefds[2];
    int count, err;
    pid_t child;

    if (pipe(pipefds)) {
        perror("pipe");
        return EX_OSERR;
    }
    if (fcntl(pipefds[1], F_SETFD, fcntl(pipefds[1], F_GETFD) | FD_CLOEXEC)) {
        perror("fcntl");
        return EX_OSERR;
    }

    switch (child = fork()) {
    case -1:
        perror("fork");
        return EX_OSERR;
    case 0:
        close(pipefds[0]);
        execvp(argv[1], argv + 1);
        write(pipefds[1], &errno, sizeof(int));
        _exit(0);
    default:
        close(pipefds[1]);
        while ((count = read(pipefds[0], &err, sizeof(errno))) == -1)
            if (errno != EAGAIN && errno != EINTR) break;
        if (count) {
            fprintf(stderr, "child's execvp: %s\n", strerror(err));
            return EX_UNAVAILABLE;
        }
        close(pipefds[0]);
        puts("waiting for child...");
        while (waitpid(child, &err, 0) == -1)
            if (errno != EINTR) {
                perror("waitpid");
                return EX_SOFTWARE;
            }
        if (WIFEXITED(err))
            printf("child exited with %d\n", WEXITSTATUS(err));
        else if (WIFSIGNALED(err))
            printf("child killed by %d\n", WTERMSIG(err));
    }
    return err;
}

Here's a complete program.

$ ./a.out foo
child's execvp: No such file or directory
$ (sleep 1 && killall -QUIT sleep &); ./a.out sleep 60
waiting for child...
child killed by 3
$ ./a.out true
waiting for child...
child exited with 0

How this works:

Create a pipe, and make the write endpoint CLOEXEC: it auto-closes when an exec is successfully performed.

In the child, try to exec. If it succeeds, we no longer have control, but the pipe is closed. If it fails, write the failure code to the pipe and exit.

In the parent, try to read from the other pipe endpoint. If read returns zero, then the pipe was closed and the child must have exec successfully. If read returns data, it's the failure code that our child wrote.

like image 56
ephemient Avatar answered Nov 15 '22 22:11

ephemient


You terminate the child (by calling _exit()) and then the parent can notice this (through e.g. waitpid()). For instance, your child could exit with an exit status of -1 to indicate failure to exec. One caveat with this is that it is impossible to tell from your parent whether the child in its original state (i.e. before exec) returned -1 or if it was the newly executed process.

As suggested in the comments below, using an "unusual" return code would be appropriate to make it easier to distinguish between your specific error and one from the exec()'ed program. Common ones are 1, 2, 3 etc. while higher numbers 99, 100, etc. are more unusual. You should keep your numbers below 255 (unsigned) or 127 (signed) to increase portability.

Since waitpid blocks your application (or rather, the thread calling it) you will either need to put it on a background thread or use the signalling mechanism in POSIX to get information about child process termination. See the SIGCHLD signal and the sigaction function to hook up a listener.

You could also do some error checking before forking, such as making sure the executable exists.

If you use something like Glib, there are utility functions to do this, and they come with pretty good error reporting. Take a look at the "spawning processes" section of the manual.

like image 28
Isak Savo Avatar answered Nov 15 '22 20:11

Isak Savo