Below is the source of a program that executes cat
:
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
pid_t pid = fork();
if (!pid) {
// create a new process group
setpgid(0, 0);
execlp("cat", "cat", NULL);
}
pid_t reaped = wait(NULL);
printf("reaped PID = %d\n", (int) reaped);
return 0;
}
Note the call to setpgid(0, 0)
.
When run in a shell (either sh
or bash
), I expected:
cat
, andHowever, what happens is:
cat
with no problem, butT
in ps
), andSIGINT
, SIGSTOP
or SIGQUIT
, only to be killed by SIGKILL
.When the call to setpgid()
is commented out, everything is as expected.
I suspect the behavior is caused by:
cat
is trying to read stdin and is waiting (thus stopped), butbash
, and then to the program above, however not to the cat
, possibly because bash
does not recognize its grandchildren, cat
, because of its different process group.Of course, removing setpgid()
call is the simplest solution. Unfortunately there are some reasons; mainly to intercept some signals (such as SIGINT or SIGSTOP) in the parent. In other words, a <Ctrl-C>
should not kill the cat
but somehow signal the program above. (There are no signal handlers in the program above, yes, for illustrative purpose.)
I'd like to ask:
cat
to receive inputs from stdin?As suggested in the comments, the foreground process group (which assumes all STDIN) can be changed via tcsetpgrp()
.
The function may as well be called from the child. Otherwise the parent will have to wait for the child to do a successful setpgid()
call and a concurrency issue will happen.
However, as described in this SO question, when the child (which is not yet foreground) call tcsetpgrp
, it will get a signal of SIGTTOU, according to the manual of tcsetpgrp
. The default action for SIGTTOU is to stop the process, and this should be manually ignored.
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
// ignore SIGTTOU
signal(SIGTTOU, SIG_IGN);
pid_t pid = fork();
if (!pid) {
// create a new process group
setpgid(0, 0);
tcsetpgrp(STDIN_FILENO, getpgid(0));
execlp("cat", "cat", NULL);
}
pid_t reaped = wait(NULL);
printf("reaped PID = %d\n", (int) reaped);
return 0;
}
Now the underlying cat
starts to interact with the terminal, and the problem is solved.
uuu@hhh:~$ ./a
sajkfla
sajkfla
wjkelfaw
wjkelfaw
reaped PID = 774
uuu@hhh:~$ ./a