ENBYSS
ENBYSS

Reputation: 929

How to only kill the child process in the foreground?

I am trying to build a shell, and I've managed to code most of the functionality in, however I have a small problem.

Say I type firefox &. Firefox will open up as a background process. The & activates a BG flag that makes the parent not wait for the child process.

Then I type gedit. Gedit will open as a foreground process. Meaning that currently the parent is waiting for the process to close.

At this point, the parent has two processes - firefox and gedit. Firefox hasn't been waited on, and is currently in the background, whereas we are currently waiting for Gedit to finish. So far so good.

However, if I decide to send a SIGINT signal by pressing ctrl-c, both firefox and gedit will close. Not good, only gedit should be closing.

Here is my signal handler function:

pid_t suspended_process[10];
int last_suspended = -1;

void signal_handler(int signo){
    pid_t process = currentpid();

    // Catches interrupt signal
    if(signo == SIGINT){
        int success = kill(process, SIGINT);
    }

    // Catches suspend signal
    else if(signo == SIGTSTP){
        int success = kill(process, SIGTSTP);
        resuspended = 1;
        suspended_process[last_suspended+1] = process;
        last_suspended++;
    }
}

And here's the part in fork-exec code that either waits on a process, or keeps on going.

  else if(pid > 0){ //Parent
    current_pid = pid;

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

This also happens if I suspend a process beforehand. For example, I run firefox and then press ctrl-z to suspend it. Then I run gedit and press ctrl-c to close it. Right after, if I press fg to restore the suspended firefox, it closes immediately.

I cannot find a way to only send the SIGINT signal to the foreground process, it always sends the signal to ALL children other than the parent, no matter if they are in the background, or suspended.

Just in case, this is the function that initialises the signal handler:

void init_handler(){
    struct sigaction sa;

    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    // If conditions for signal handling.
    // Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
    if(sigaction(SIGINT, &sa, NULL) == -1)
        printf("Couldn't catch SIGINT - Interrupt Signal\n");
    if(sigaction(SIGTSTP, &sa, NULL) == -1)
        printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}

Upvotes: 1

Views: 4922

Answers (2)

ENBYSS
ENBYSS

Reputation: 929

With the help of Antti, I managed to find the problem. I added a single line to the fork-exec code:

  else if(pid > 0){ //Parent
    current_pid = pid;

    if(setpgid(pid, pid) == 0) perror("setpid");

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

if(setpgid(pid, pid) == 0) perror("setpid");

From what I could gather, setpgid sets the process group ID of a process. Meaning that in the line above, I am setting the pgid of the process with the pid pid to pid.

I might be wrong, I still don't fully understand the process, but the reason why this works, is that the SIGINT signal is only sent to the process with pgid pid. Meaning before, since I wasn't setting the pgid of each process, they all had the same pgid, hence they'd all receive the signal. However, once I set the pgid for each process, if I press CTRL-C in the middle of a foreground process, it only exits that running process.

At least that's from what I could gather. I still don't fully understand tcsetpgrp, especially what I could set as the first parameter, which is the file descriptor. Adding this line right after setpgid:

tcsetpgrp(STDIN_FILENO, pid)

Simply launches the entire program in the background whenever I exec a command. Instead of running firefox and it shows up, I run firefox and the whole program gets stopped (according to what the terminal says at least). I have no idea why that happens.

Still, thanks to Antti!

Upvotes: 0

This is rather simple, but it isn't done with signals. Instead you must use a feature called process groups. Each single job (executable or pipeline or so) will be a separate process group. You can create process groups with setpgid (or on some systems with setpgrp). You can simply set the process group of the child process after fork but before exec, and then store the process group id of this job into the job table.

Now, the process group that is in the foreground is set as the active process group for the terminal (the /dev/tty of the shell) with tcsetpgrp - this is the process group that will receive CTRL+C. Those process groups that belong to the same session, but not to the group set to foreground with tcsetpgrp will be completely oblivious to CTRL+C.

Upvotes: 4

Related Questions