'Why does forking my process cause the file to be read infinitely

I've boiled down my entire program to a short main that replicates the issue, so forgive me for it not making any sense.

input.txt is a text file that has a couple lines of text in it. This boiled down program should print those lines. However, if fork is called, the program enters an infinite loop where it prints the contents of the file over and over again.

As far as I understand fork, the way I use it in this snippet is essentially a no-op. It forks, the parent waits for the child before continuing, and the child is immediately killed.

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

enum { MAX = 100 };

int main(){
    freopen("input.txt", "r", stdin);
    char s[MAX];

    int i = 0;
    char* ret = fgets(s, MAX, stdin);
    while (ret != NULL) {
        //Commenting out this region fixes the issue
        int status;
        pid_t pid = fork();
        if (pid == 0) {
            exit(0);
        } else {
            waitpid(pid, &status, 0);
        }
        //End region
        printf("%s", s);
        ret = fgets(s, MAX, stdin);
    }
}

Edit: Further investigation has only made my issue stranger. If the file contains <4 blank lines or <3 lines of text, it does not break. However, if there are more than that, it loops infinitely.

Edit2: If the file contains numbers 3 lines of numbers it will infinitely loop, but if it contains 3 lines of words it will not.



Solution 1:[1]

The exit() call closes all open file handles. After the fork, the child and parent have identical copies of the execution stack, including the FileHandle pointer. When the child exits, it closes the file and resets the pointer.

  int main(){
        freopen("input.txt", "r", stdin);
        char s[MAX];
        prompt(s);
        int i = 0;
        char* ret = fgets(s, MAX, stdin);
        while (ret != NULL) {
            //Commenting out this region fixes the issue
            int status;
            pid_t pid = fork();   // At this point both processes has a copy of the filehandle
            if (pid == 0) {
                exit(0);          // At this point the child closes the filehandle
            } else {
                waitpid(pid, &status, 0);
            }
            //End region
            printf("%s", s);
            ret = fgets(s, MAX, stdin);
        }
    }

Solution 2:[2]

As /u/visibleman pointed out, the child thread is closing the file and messing things up in main.

I was able to work around it by checking if the program is in terminal mode with

!isatty(fileno(stdin))

And if stdin has been redirected, then it will read all of it into a linkedlist before doing any processing or forking.

Solution 3:[3]

Replace exit(0) with _exit(0), and all is fine. This is an old unix tradition, if you are using stdio, your forked image must use _exit(), not exit().

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 lbenedetto
Solution 3 mevets