1

I'm implementing my own tiny shell. Both ls | cat and ls | more work well if I don't create a new group for these two child processes.

However, after I create a new process group for these two processes (the first one ls process is the leader process) by simply calling setpgid in the parent process. ls | cat always works but ls | more hangs.

It looks like one of my process is getting stuck at the dup2 system call. Is there any other things I need to consider? What happens if ls terminates first before I call setpgid for more process? Do I need to block ls process not to call execve until setpgid is done for more process?


EDIT:

My code: Sometimes it gets stuck. But when I tried to add printf to locate the position, it's finished successfully. No error arises.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define CHECK(syscall, msg) do {                    \
    if ((syscall) == -1) {                          \
      perror(msg);                                  \
      exit(1);                                      \
    }                                               \
  } while(0)

#define SAFE_CLOSE_NOT_STD(fd) do {                                 \
    if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != -1) {    \
      CHECK(close(fd), "close error");                              \
      fd = -1;                                                      \
    }                                                               \
  } while(0)

int main () {
  int ls_pid, more_pid;
  int pipefd[2];
  char *ls_argv[] = { "/bin/ls", NULL };
  char *more_argv[] = { "/usr/bin/more", NULL };

  CHECK(pipe(pipefd), "pipe error");

  CHECK(ls_pid = fork(), "fork error");
  if (!ls_pid) {
    CHECK(dup2(pipefd[1], STDOUT_FILENO), "dup2 error");
    CHECK(close(pipefd[1]), "close error");
    CHECK(execvp(ls_argv[0], ls_argv), "execvp error");
  } else {
    SAFE_CLOSE_NOT_STD(pipefd[1]);
  }
  setpgid(ls_pid, ls_pid);

  CHECK(more_pid = fork(), "fork error");
  if (!more_pid) {
    CHECK(dup2(pipefd[0], STDIN_FILENO), "dup2 error");
    CHECK(close(pipefd[0]), "close error");
    CHECK(execvp(more_argv[0], more_argv), "execvp error");
  } else {
    SAFE_CLOSE_NOT_STD(pipefd[0]);
  }

  setpgid(more_pid, ls_pid); // it works if I remove this line

  CHECK(wait(NULL), "wait error");
  CHECK(wait(NULL), "wait error");

  printf("Finish\n");
}
K.Miao
  • 811
  • 7
  • 21

1 Answers1

2

Use _exit(), not exit(), after fork(). (That's not the real problem here, but you should fix it.)

What happens here is that more is not in the current foreground process group, so it is not allowed to read input from the terminal and gets SIGTTIN instead. Use tcsetpgrp.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • Do you mean the `more` process becomes background process after the pgid of the `ls` process is changed? But if I change `/usr/bin/more` to `/bin/cat`, it works well and never hangs. Why? – K.Miao Jan 29 '18 at 15:06
  • @K.Miao `more` is initially in the same process group as your program, which is foreground. As soon as you put it into the new process group, it is in the background. `cat` doesn't read from the terminal so it doesn't get `SIGTTIN`. Depending on terminal configuration, it could potentially get `SIGTTOU`, but all shells turn that off by default. `stty tostop` to demonstrate. – ephemient Jan 29 '18 at 16:40
  • I've overwritten the stdin to the pipe read end for the `more` process. It reads from the pipe but not stdin. Does it still get `SIGTTIN`? I just tried to register handler for `SIGTTIN` and `SIGTTOU` in the parent process before calling any `fork()`. The program hangs but I didn't see any `SIGTTIN` or `SIGTTOU`. – K.Miao Jan 29 '18 at 17:43
  • @K.Miao `more` reads user input from the terminal even when stdin is not the terminal (see https://stackoverflow.com/a/1441368 for how `less` works). The parent process doesn't get `SIGTTIN`, only the process trying to read from stdin while in the background. – ephemient Jan 29 '18 at 18:32
  • Since I register the handlers in the parent process, wouldn't it be inherited to both child processes? Is there any other ways to verify the answer? THANKS!! – K.Miao Jan 29 '18 at 18:56
  • @K.Miao Even if you ignore `SIGTTIN`, it doesn't change the fact that `read` returns nothing and `more` refuses to work. Actually, tracing the whole thing with `strace -f` it appears that `more` putting the terminal into raw mode causes it to receive a `SIGTTOU` before writing anything else. Still, the underlying cause is the same: not the terminal's foreground process group. – ephemient Jan 29 '18 at 21:10
  • I register a `SIGTTOU` and a `SIGTTIN` handler for the `more` process. In the handler I simply call `_exit(1)`. But it still gets stuck. Shouldn't it terminate instead of stuck? – K.Miao Jan 30 '18 at 01:56
  • @K.Miao That handler can't be inherited across `exec` - the functions live in a specific address space, which gets wiped out. Only default and ignore signal handlers can be inherited. – ephemient Jan 30 '18 at 02:47
  • So I need to guarantee `tcsetpgrp` is executed before `execve` is executed in the `more` process, right? How do I achieve this? Do I need to use shared memory to keep the `more` process waiting until `tcsetpgrp` is executed? Any elegant ways to deal with this? – K.Miao Jan 30 '18 at 18:42