Reputation: 135
I'm making a shell in C and I need to be able to handle SIGINT and SIGTSTP signals so as the shell doesn't terminate but it passes the signals to the child processes. I've found various posts here saying I should either use kill(child_pid,SIGINT) in the signal handler with child_pid being the id fork() returns, or to use killpg() while setting a new groupid to the child process before it uses execvp(). None of these work for me, as the handler only displays what I've put in it using the write() function, but doesn't terminate the child.
// global variable
__pid_t child_pid = 0;
int main(){
// init variables
pid_t childpid;
char *commandp;
while(1){
ignore();
//do stuff that read input
// do stuff that end up using fork to make child process
if ( ( childpid = fork() ) == -1 ){
perror ( " Fork fail " );
exit (2); // exit value 2 means fork fail inside the shell
}
if (childpid == 0){
setpg(getpid(), getpid());
child_pid = getpgrp();
commandp = argv[0];
execvp(commandp, argv);
}
else {
ignore_child();
waitpid(childpid, &status, WUNTRACED);
}
}
}
//signal handling functions
void ignore(){
struct sigaction act;
act.sa_handler=SIG_IGN;
// sigaction()
sigaction(SIGINT, &act, NULL);
sigaction(SIGTSTP, &act, NULL);
}
void ignore_child(){
struct sigaction act;
act.sa_handler = &handle_signal;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTSTP, &act, NULL);
}
void handle_signal(int sig){
if (child_pid != 0)
if (sig == SIGINT){
write(STDERR_FILENO, "Received SIGINT (CTRL-C) signal. Exiting...\n", 46);
killpg(child_pid, SIGINT);
}
else if (sig == SIGTSTP){
write(STDERR_FILENO, "\nReceived SIGSTP (CTRL-Z) signal. Stopping...\n", 47);
killpg(child_pid, SIGTSTP);
}
}
Any idea why the child process doesn't terminate but the message from the signal handler displays fine?
Upvotes: 0
Views: 335
Reputation: 7923
The first thing your loop does is ignore()
, setting the SIGINT
disposition to SIG_IGN
. This disposition is inherited by the child, and travels accross exec
. Quoting man execve
,
Signals set to be ignored in the calling process are set to be ignored in the new process.
so no surprise it is not killed. The first instinct is to restore the SIGINT
disposition back to SIG_DFL
in the child. This would appear to work, but...
... the SIGSTOP
problem will sill be unresolved, because SIGSTOP
cannot be caught. The right solution is not to tinker with the signals at all. Instead, let the shell detach from the controlling terminal. This way, no signal generated by the tty driver will reach the shell. In a very broad strokes,
signal(SIGTTOU, SIF_IGN);
if ((child = fork()) < 0)
perror("fork")
if (child == 0) {
setpgid(0, 0);
tcsetpgrp(0, getpgrp());
execve(...);
perror("exec");
} else {
setpgid(child, child);
tcsetpgrp(STDIN_FILENO, child);
waitpid(...);
}
I don't know the scope of your project, but keep in mind that job control is far from easy.
Upvotes: 1