5

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:

  1. the parent to spawn a child process of cat, and
  2. the child to start interacting with the terminal.

However, what happens is:

  1. the parent spawns cat with no problem, but
  2. the child is stopped (with process state code T in ps), and
  3. the child accepts no input from the terminal, and
  4. the child does not respond to any of SIGINT, 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:

  • the child cat is trying to read stdin and is waiting (thus stopped), but
  • the input to the terminal is first passed to bash, 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:

  1. Is this correct?
  2. Whether it is or not, how can I have cat to receive inputs from stdin?
Namnamseo
  • 187
  • 1
  • 15
  • 1
    There seems to be an issue. If I'm not mistaken, signals from the terminal are sent to the foreground process group, that is the one which is accepting output and input. Here you seem to want to decouple the two, that does not fit my understanding (which is not perfect, I'd have expected at least to be able to use `tcsetpgrp` with your program to start a new group and change the foreground process group to that group but I was wrong). – AProgrammer Nov 18 '19 at 16:05
  • I cannot reproduce the problem on Ubuntu 19.04. I run the program as `./program < inputfile` and it prints the contents of `inputfile`. When I run it with `strace` as `strace -f ./program < inputfile` I can see that it executes `/usr/bin/cat` and that `cat` reads the input file from stdin and writes the data to stdout. – Bodo Nov 18 '19 at 16:21
  • @AProgrammer You are right! `tcsetpgrp` was what I needed. Thank you for letting me know. About `tcsetpgrp` not working one can refer to [another answer in SO](https://stackoverflow.com/questions/5341220/how-do-i-get-tcsetpgrp-to-work-in-c). – Namnamseo Nov 18 '19 at 16:42
  • 1
    @Namnamseo, thanks. I'll have to dig into that answer to complete my understanding. If your problem is solved, I suggest you to write an answer. – AProgrammer Nov 18 '19 at 16:52

1 Answers1

3

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
Namnamseo
  • 187
  • 1
  • 15