wofster
wofster

Reputation: 3

How do I get my signal handler to catch a signal sent by a child process?

I'm writing a very basic shell in C with some basic job control. If the user enters a non-built in command, then the parent forks and creates a child then the child can execute a program.

I have signal handlers to handle SIGCHLD, SIGINT, and SIGTSTP. These handlers are able to catch signals sent by the terminal (i.e. if I press ctrl+z then the termial sends SIGTSTP and the SIGTSTP signal is caught by my sigtstp_handler).

But I need the handler to also catch a signal sent by a child process, i.e. if the child executes a program like ./prog and in prog is an instruction "kill(prog_pid, SIGTSTP)", then I need my sigtstp_handler to catch that signal.

So for example, here's my sigtstp_handler:

void sigtstp_handler(int sig) 
{
    pid_t pid;

    if ((pid = fgpid(jobs)) > 0) { //make sure foreground job exists
        kill(-pid, SIGTSTP);
        printf("caught SIGTSTP");
    }
    
    return;
}

Then my main function basically (cutting a lot out since its too long) looks like:

main() {
   Signal(SIGTSTP, sigtstp_handler); //install sigtstp_handler
   
   if ((output = builtin_cmd(argv)) == 0) {
        sigset_t mask, prev_mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGTSTP);

        //block signal
        sigprocmask(SIG_BLOCK, &mask, &prev_mask);
     
        pid = fork();
        //CHILD 
        if (pid == 0) {
            //unblock signal for child
            sigprocmask(SIG_SETMASK, &prev_mask, NULL); 
            
            setpgid(0,0); //set pgid
                        
            //run user program
            if (execve(argv[0], argv, environ) < 0) { //
                printf("Command not found\n");
                exit(0);
            } 
        }
        //PARENT
        if (pid != 0){ 
            addjob(pid);

            //unblock signals for parent
            sigprocmask(SIG_SETMASK, &prev_mask, NULL);

            if (!background) {     
                waitfg(pid); 
            }  
        }
    }

    return;
}

So sigtstp_handler catches my signal if its sent from the terminal by ctrl+z. But it does not catch it if it was sent by a child process via kill(pid, SIGTSTP) (and I know it doesn't catch it because it doesn't do the print statement).

Why is it not catching the signal from a child process? I thought that when I install the handler, it basically makes it so anytime a SIGTSTP signal is sent, instead of doing whatever the default is, it executes my handler. So shouldn't it catch SIGTSTP no matter what it comes from? I'm having the same trouble with my sigint_handler not catching SIGINT when it comes from a child process.

And how can I make sure it does catch that signal?

I'm thinking maybe its a problem with my signal blockers? So I block SIGTSTP in the parent, then I fork. Then in the child I unblock all signals then run a program. So the signals are unblocked when the child proces calls kill(). And in the parent I add the job, then unblock, then wait for a foreground process to finish. But the parent can accept SIGCHILD signals just fine, so I don't know why it would be blocking the signal from kill().

I also know (at least an pretty sure) its not a problem with my signal handler only acting on a foreground process because its a foreground process that calls kill(pid, SIGTSTP).

I appreciate any help.

Upvotes: 0

Views: 109

Answers (1)

Madagascar
Madagascar

Reputation: 7345

You have to install signal handlers specifically for the child process. As Barmar mentions in the comments:

The parent process doesn't receive signals sent to the child. When the child stops due to receiving a SIGTSTP signal, the parent receives a SIGCHLD signal, and the data returned by wait() will indicate that the child was stopped.

and:

The child can't use the parent's handlers after it calls exec(), since those signal functions don't exist in the child process any more.

According to the man page:

Signals set to the default action (SIG_DFL) in the calling process image shall be set to the default action in the new process image. Except for SIGCHLD, signals set to be ignored (SIG_IGN) by the calling process image shall be set to be ignored by the new process image. Signals set to be caught by the calling process image shall be set to the default action in the new process image (see <signal.h>).

If the SIGCHLD signal is set to be ignored by the calling process image, it is unspecified whether the SIGCHLD signal is set to be ignored or to the default action in the new process image.

Some other things you should note:

  1. The return type of main(). As Jonathan Leffer mentions in the comments:

Note that main() { is not valid C99, let alone C17 (or C23) — you must specify the return type. You should normally use int main(void) if you ignore command-line arguments. Also, the return; at the end of main() should be return 0; or something similar. Even under the C90 rules, the function should return a value as the type is implicitly int. (You could omit the return altogether under C99 or later rules, and that's equivalent to adding return 0;, but I think that rule is an abomination.)

  1. printf() is async-signal-unsafe. Calling it in a signal handler has undefined behavior according to both the POSIX Standard and the C standard. Though, POSIX specifies write() to be async-signal-safe, and that can be used in a signal handler.

  2. About Signal(SIGTSTP, sigtstp_handler); //install sigtstp_handler, there is no such thing as Signal, unless you've defined it as a wrapper around the signal() call. According to the man page for signal():

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN.

Replace it with sigaction().

Upvotes: 1

Related Questions