I do the regular thing:
If execvp fails because no cmd is found, how can I notice this error in parent process?
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.
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.
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).
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With