2

I am trying to install a CTRL-Z (SIGTSTP) handler for a running foreground process.

I set the handler (sigaction) right before I wait in the parent. Is this the right place? It doesn't seem to work right..

EDIT:

I am writing a shell. Here is an outline of how my code looks like. I currently set the handler in the parent as shown below (which doesn't seem to work).

// input from user for command to run
pid_t pid;
pid = fork();

// Child Process
if (pid == 0) {
    // handle child stuff here
    // exec() etc...
}

else if (pid < 0)
    // error stuff

/* Parent Here */
else {
    // Give process terminal access
    // SET HANDLER FOR SIGTSTP HERE???
    wait()
    // Restore terminal access
}
darksky
  • 20,411
  • 61
  • 165
  • 254
  • 1
    Can you please post a minimal code sample to reproduce your situation? – Niklas B. Mar 24 '12 at 17:42
  • Yes - please refer to the question again. Thanks – darksky Mar 24 '12 at 17:52
  • What do you want to achieve? I thought you wanted to send SIGTSTP from the parent to the child and handle it there, but you neither set the signal handler in the child, nor do you actually send SIGTSTP... Or doesn't this have anything to do with the child process at all? If this is the case, why did you include the forking code? Also, please include the code that's supposed to register the handler. – Niklas B. Mar 24 '12 at 17:54
  • I want to send the signal from the parent to the child. This is happening. My child is quitting. But I want to print out a message, the same way `Bash` does, which means I need a handler to be able to catch the signal and print something out. `SIGTSTP` is sent via `CTRL-Z` on the keyboard. I am setting the handler two lines after the parent's `else` statement. – darksky Mar 24 '12 at 18:44

2 Answers2

3

You are doing things complete wrong.

You DON'T send SIGTSTP to child process, the tty send SIGTSTP to child process DIRECTLY.

Try

$ stty -a
speed 38400 baud; rows 55; columns 204; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

Notice:

susp = ^Z;

that tells the tty how to deal with "CTRL-Z", when the tty gets ^Z it sends SIGTSTP signal to all process in the current foreground process group

How to handle process group

When you launch a new process in the shell, before execvX, put the new process into a new process group, then call tcsetpgrp to set the new process group foreground. So any future signal will send to the child process directly. if the child forks new process, they will be in the same process group; so the entire process group will be suspended when ^Z is pressed.

pid = fork()
if (pid) {
  // parent
  setpgid(pid, pid); // put child into a new process group
  int status;
  wait(pid, &status, 0);
} else {
  // child
  pid = getpid();
  setpgid(pid, pid);
  if (isatty(0)) tcsetpgrp(0, pid);
  if (isatty(1)) tcsetpgrp(1, pid);
  if (isatty(2)) tcsetpgrp(2, pid);
  execvX ...
}

Once any signal comes from tty causing child processes stop/term/exit your shell will return from wait, check status to know what happened to the child.

Prevent your shell from stopping

Your shell should mask SIGTSTP signal, because shell do not suspend. You do this at the beginning, when you start the shell. but don't forget fork will derive sigmask, so you need to enable SIGTSTP after fork in the child process.

Zang MingJie
  • 5,164
  • 1
  • 14
  • 27
  • I already have my child in a different group - so hitting CTRL-Z does work and my shell comes back up. But I need to print a statement saying job suspended or something, so how will I do that? I need to catch the signal in order to be able to send a print statement. – darksky Mar 24 '12 at 23:42
  • @Nayefc: you don't need to catch the signal, just `wait*(...WUNTRACED...)` for the child to stop. – ninjalj Mar 25 '12 at 00:00
  • If I do `if (WUNTRACED)`, this will ALWAYS execute as soon as the child has finished. I want a way to catch it *ONLY* if CTRL-Z (SIGTSTP) was called on the child. How can I do that? – darksky Mar 25 '12 at 13:57
  • use `WIFSTOPPED(status)` to check the result of `wait` call. (Refer to the manpage of `waitpid`) – Zang MingJie Mar 25 '12 at 16:57
  • Yep - I found that out on my own. – darksky Mar 25 '12 at 21:41
0

To handle a SIGTSTP to the child, this is needed after waitpid:

if (WIFSTOPPED(status)) {
    printf("Child received SIGTSTP");
}
darksky
  • 20,411
  • 61
  • 165
  • 254