Reputation: 3
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
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 aSIGCHLD
signal, and the data returned bywait()
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 forSIGCHLD
, 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 theSIGCHLD
signal is set to be ignored or to the default action in the new process image.
Some other things you should note:
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 useint main(void)
if you ignore command-line arguments. Also, the return; at the end ofmain()
should bereturn 0
; or something similar. Even under the C90 rules, the function should return a value as the type is implicitlyint
. (You could omit the return altogether under C99 or later rules, and that's equivalent to addingreturn 0;
, but I think that rule is an abomination.)
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.
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 toSIG_DFL
orSIG_IGN
.
Replace it with sigaction()
.
Upvotes: 1