Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++, calling fork when cin is a bash heredoc causes repeated input fragments

I am implementing a shell-like program in C++. It has a loop that reads from cin, forks, and waits for the child.

This works fine if the input is interactive or if it's piped from another program. However, when the input is a bash heredoc, the program rereads parts of the input (sometimes indefinitely).

I understand that the child process inherits the parent's file descriptors, including shared file offset. However, the child in this example does not read anything from cin, so I think it shouldn't touch the offset. I'm kind of stumped about why this is happening.


test.cpp:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
    std::string line;
    while (std::getline(std::cin, line)) {
        pid_t pid = fork();
        if (pid == 0) { // child
            break; // exit immediately
        }
        else if (pid > 0) { // parent
            waitpid(pid, nullptr, 0);
        }
        else { // error
            perror("fork");
        }

        std::cout << getpid() << ": " << line << "\n";
    }
    return 0;
}

I compile it as follows:

g++ test.cpp -std=c++11

Then I run it with:

./a.out <<EOF
hello world
goodbye world
EOF

Output:

7754: hello world
7754: goodbye world
7754: goodbye world

If I add a third line foo bar to the input command, the program gets stuck in an infinite loop:

13080: hello world
13080: goodbye world
13080: foo bar
13080: o world
13080: goodbye world
13080: foo bar
13080: o world
[...]

Versions:

  • Linux kernel: 4.4.0-51-generic
  • Ubuntu: 16.04.1 LTS (xenial)
  • bash: GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu)
  • gcc: g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
like image 204
Kevin Chen Avatar asked Dec 05 '16 00:12

Kevin Chen


1 Answers

I was able to reproduce this problem, not only using the heredoc but also using a standard file redirection.

Here is the test script that I used. In both the first and second cases, I got a duplication of the second line of input.

./a.out < Input.txt
echo

cat Input.txt | ./a.out
echo

./a.out <<EOF
hello world
goodbye world
EOF

Closing the stdin before exiting in the child seems to eliminate both of the problems.

#include <iostream>
#include <sstream>
#include <unistd.h>
#include <sys/wait.h>
#include <limits>

int main(int argc, char **argv)
{
    std::string line;
    while (std::getline(std::cin, line)) {
        pid_t pid = fork();
        if (pid == 0) { // child
            close(STDIN_FILENO);
            break; // exit after first closing stdin
        }
        else if (pid > 0) { // parent
            waitpid(pid, nullptr, 0);
        }
        else { // error
            perror("fork");
        }

        std::cout << getpid() << ": " << line << "\n";
    }
    return 0;
}
like image 112
merlin2011 Avatar answered Nov 15 '22 14:11

merlin2011