Reputation: 154
I'm trying to write a program where,
Hence, the way I went about trying to do the same is, I added a SIGINT handler for my parent process and my guess is when the execvp() function is invoked, the child process would perform the SIGINT process using the default handlers.
void sigint_handler(int signum){
printf("");
}
int main(int argc, char **argv){
pid_t pid = fork();
if(pid<0)
{
printf("fork: error");
exit(-1);
}
else if(pid>0){
int status;
signal(SIGINT,sigint_handler);
pid_t child_pid= waitpid(pid, &status, 0);
printf("");
}
else if(pid==0){
printf("");
if(execvp(argv[1],&argv[1]) == -1) {
printf(" ");
exit(-1);
}
}
}
Is this code sufficient to handle the same? Also, if my child process is terminated by SIGINT or SIGSEGV etc, how would I go about finding whether my child process was terminated after completion or because of a signal and what signal was used.
Upvotes: 1
Views: 2998
Reputation: 755054
The value from wait()
or friends for the status tells you — use the macros WIFEXITED()
and WIFEXITSTATUS()
for ordinary exit, or WIFSIGNALED()
and WIFTERMSIG()
for signal (the IF
macros test how it exited, of course). There are also WIFSTOPPED()
, WIFCONTINUED()
and WSTOPSIG()
for job control.
See POSIX waitpid()
(which documents wait()
too). And most Unix-based systems also provide a WCOREDUMP()
too, but POSIX does not mandate it. You can also look at the exit status in hex (4 digits) and it does not take long to spot the patterns, but you should use the macros.
For the first part of my question, would my above-mentioned code ignore the
SIGINT
signal while it is still in the parent part of the process?
Beware: avoid using printf() in signal handlers.
It wasn't clear how your signal handling code was working; you'd chopped too much code from the question. However, you've now added enough of the missing code, and, as I guessed, your code isn't bullet proof because the signal()
call shown is after the fork()
and in the parent process. You should disable the signal before calling fork()
; then the child should re-enable it. That protects the processes properly.
For example (file sig11.c
— compiled to create sig11
using gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes sig11.c -o sig11
):
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s command [arg ...]\n", argv[0]);
return 1;
}
bool handle_signals = (signal(SIGINT, SIG_IGN) != SIG_IGN);
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork: error\n");
return 1;
}
else if (pid > 0)
{
int status;
pid_t child_pid = waitpid(pid, &status, 0);
printf("child %d exited with status 0x%.4X\n", (int)child_pid, status);
}
else
{
assert(pid == 0);
if (handle_signals)
signal(SIGINT, SIG_DFL);
execvp(argv[1], &argv[1]);
fprintf(stderr, "failed to exec %s\n", argv[1]);
exit(-1);
}
return 0;
}
Example output:
$ ./sig11 sleep 40
^Cchild 19721 exited with status 0x0002
$
We can debate whether you should be using sigaction()
rather than signal()
another day.
The check for handle_signals
means that if the program is run with SIGINT ignored, it does not suddenly enable the signal. This is a standard defensive technique. Note that errors are reported on standard error, not standard output. There's no need to check the return value from execvp()
; it doesn't return if it is successful and if it does return it definitively failed. It would be possible to report a better error using errno
and strerror()
, but what's shown is not wholly unreasonable. The check for enough arguments is another defensive measure; reporting the correct usage is good manners. I also converted the if
/ else if
/ else if
into if
/ else if
/ else
+ assertion — the third test is unnecessary.
Using the macros from <sys/wait.h>
:
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s command [arg ...]\n", argv[0]);
return 1;
}
bool handle_signals = (signal(SIGINT, SIG_IGN) != SIG_IGN);
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork: error\n");
return 1;
}
else if (pid > 0)
{
int status;
int corpse = waitpid(pid, &status, 0);
printf("child %d exited with status 0x%.4X\n", corpse, status);
if (WIFEXITED(status))
printf("child %d exited normally with exit status %d\n", corpse, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child %d exited because of signal %d\n", corpse, WTERMSIG(status));
else
printf("child %d neither exited normally nor because of a signal\n", corpse);
}
else
{
assert(pid == 0);
if (handle_signals)
signal(SIGINT, SIG_DFL);
execvp(argv[1], &argv[1]);
fprintf(stderr, "failed to exec %s\n", argv[1]);
exit(-1);
}
return 0;
}
Example outputs:
$ ./sig11 sleep 4
child 19839 exited with status 0x0000
child 19839 exited normally with exit status 0
$ ./sig11 sleep 10
^Cchild 19842 exited with status 0x0002
child 19842 exited because of signal 2
$
Upvotes: 2