stefnto
stefnto

Reputation: 135

Sending SIGINT signal to child process that uses execvp in C

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

Answers (1)

user58697
user58697

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

Related Questions