-1

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?

stefnto
  • 85
  • 3
  • 8

1 Answers1

0

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.

user58697
  • 7,808
  • 1
  • 14
  • 28
  • The problem was that I wasnt restoring the default signal handling in the child... Now about the SIGSTOP, i'll probably remove it since we weren't asked to implement something like that – stefnto May 03 '23 at 13:28