2

EDIT: THE QUESTION IS ANSWERED IN COMMENTS

So, i'm studying pipes. Long story short, i have two programs:

  1. first program creates a pipe and two forks: first fork closes read descriptor and writes some stuff to write one (then closes it), second fork closes write one, dup2 on read side of pipe to standard input (end closes read side itself) and execl second program, giving a size of a text the first fork writes as an argument; the parent closes both pipe sides and waitpids for child that wasexecld (second one).
  2. second program just reads from its standard input (pipe side) the stuff and writes it out to standard output, then closes pipe side just in case.

In such a setup everything works as I intended, but when I delete waitpid in first program (or just wait for the first child that writes instead of the second one), the second one behaves weirdly - it executes till the end, passing through all the IO (that is, the printf before exit got executed), and then doesn't give me the prompt back. That is, the terminal looks like the program awaits for an input from standard input. If i execute the first program without execl, then everything works fine, If I execute just the second one with one argument, then it waits only until input is provided to standard input (as it should as it is not a part of a pipe in this case).

As i know, when parent terminates, the child is "inherited" by init and got waited. But even if it wasn't, that is, even if it remained as a zombie, then it still would be weird - why i cannot get my prompt back until i wait explicitly?

The code is below (of the setup that works correctly):

First program

  /* headers */
  int main(void)      
  {   
      int fildes[2];
      pid_t p1, p2;
      int status;
      char mess[] = "written from execved program!\n";
      int buf = strlen(mess);
      
      if(pipe(fildes) == -1) {
          perror("pipe in main");
          exit(EXIT_FAILURE);
      }
      p1 = fork(); 
      if(p1 == -1) {
          perror("fork p1 in main");
          exit(EXIT_FAILURE);
      }
      else if (p1 == 0) {
          printf("Child 1!\n");
          close(fildes[0]);
          write(fildes[1], mess, buf);
          close(fildes[1]);
          printf("Before exit in child 1!\n");
          exit(EXIT_SUCCESS);
      }
      
      p2 = fork(); 
      if(p2 == -1) {
          perror("fork p2 in main");
          exit(EXIT_FAILURE);
      }
      else if (p2 == 0) {
          printf("Child 2!\n");
          dup2(fildes[0], 0);
          close(fildes[0]);
          close(fildes[1]);
          char s_buf[30];
          sprintf(s_buf, "%d", buf);
          execl("./pipe2slave", "pipe2slave", s_buf, (char *) 0);
          perror("execl have returned");
          exit(EXIT_FAILURE);
      }
      close(fildes[0]);
      close(fildes[1]);
  /* 
   below if I wait for, say, p1, or don't wait it all, 
   the weird behavior described in my question happens 
  */
      if(waitpid(p2, &status, 0) == -1) { 
          perror("waitpid in main");
          exit(EXIT_FAILURE);
      }   
      if(WIFEXITED(status))
          printf("pipe2slave exit status is %d\n", WEXITSTATUS(status));
      printf("End of main in pipe2!\n"); 
      exit(EXIT_SUCCESS);  
  }

Second program

   /* headers */ 
   int main(int argc, char **argv) 
   {
        if (argc != 2) {
            perror("pipe2slave - not enough args");
            exit(EXIT_FAILURE);
        }
        printf("program name is %s\n", argv[0]);
        int buf = atoi(argv[1]);
        printf("%d\n", buf);
        char mess_in[buf];
        read(0, mess_in, buf);
        write(1, mess_in, buf);
        fsync(1);
        close(0);
        printf("end of slave!\n");
        exit(EXIT_SUCCESS); 
    }

Thank you in advance!

Anton Tretyakov
  • 303
  • 1
  • 9
  • 1
    Your code works fine for me with and without the `waitpid`, terminal does not hang at all. How are you compiling and running these programs? Also, which shell are you using? – Marco Bonelli Oct 29 '20 at 19:50
  • @MarcoBonelli thanks for an coment! Weird, probably something's wrong with my system (Manjaro) then. I compile with `gcc` and `clang`, both without any special flags. Shell's `bash`. Could I ask for your setup? – Anton Tretyakov Oct 29 '20 at 20:03
  • Your code fails for me without the wait, in exactly the way you describe, but I'm having trouble identifying why. I haven't yet recognized a flaw in the code. – John Bollinger Oct 29 '20 at 20:09
  • @MarcoBonelli i think i got it and this is a massive facepalm. It seems that without a wait the first program terminates before the second one could do anything, and prompt's given - after the prompt is given, the second program executes and gives an output - and an output is printed after the prompt so an impression is made that no prompt is given, but it is. – Anton Tretyakov Oct 29 '20 at 20:12
  • @JohnBollinger could you check my comment above? What do you think? – Anton Tretyakov Oct 29 '20 at 20:13
  • I've tried both with the shell I normally use ([Fish](https://fishshell.com)) and `bash`, and it works fine in both with or without `waitpid`. – Marco Bonelli Oct 29 '20 at 20:14
  • Yep, that explains it. Upon closer inspection, I do see the prompt mixed in with the program output. And, having realized that, I observe that I can get a fresh prompt simply by pressing the enter key. – John Bollinger Oct 29 '20 at 20:15
  • @AntonTretyakov that must be it. That's a really good explanation, should have thought about it earlier. So your shell is not waiting, it still accepts commands, right? – Marco Bonelli Oct 29 '20 at 20:15
  • @JohnBollinger so, the only flaw in my code is myself, apparently =( . This is weird, though, that Marco's output in not mixed. – Anton Tretyakov Oct 29 '20 at 20:17
  • @MarcoBonelli correct, I should have checked it earlier. But your shell doesn't mix output with prompt, right? – Anton Tretyakov Oct 29 '20 at 20:18
  • 1
    It's a form of race condition. It is not too surprising that it manifests differently on different machines or with different shells or whatever. The bottom line, really, is that if you fork a child then it is almost always a mistake to fail to wait for it. – John Bollinger Oct 29 '20 at 20:22
  • 1
    @AntonTretyakov it did not but that was just luck. – Marco Bonelli Oct 29 '20 at 20:25
  • @AntonTretyakov That happens a lot when you put a program into the background and it writes to the terminal - you have a prompt but it is hidden in the background process' output. It makes sense that this case should do that too because the forked process is running in the background if the parent finishes. – Jerry Jeremiah Oct 29 '20 at 20:29
  • @JerryJeremiah but shouldn't it be a foreground group in this case as `pgid`s are preserved upon `exec`? – Anton Tretyakov Oct 29 '20 at 20:38
  • Yes, it is in the foreground process group, else it could not write to the terminal. – John Bollinger Oct 29 '20 at 20:39
  • @JohnBollinger as i've read it is determined by the TOSTOP flag in terminal setting in termios. Read is prohibited in all the cases (sorry, that is out of question's scope already). – Anton Tretyakov Oct 29 '20 at 20:46

0 Answers0