Reading input using fgets returns duplicate lines in C

Viewed 992

I was experimenting with some C code for shell implementation and found fgets() returns duplicate lines when after I fork a process, which I could not understand, and I would greatly appreciate any help.

My question is: does forking changes the offset in any open files in the parent process? This seems to happen in my program.

FROM THE ANSWER BELOW @Vadim Ponomarev and my understanding: fgets() is not thread-safe (or strictly speaking, it is, yet forking a process causes the stdin to be initialized in some way, resulting in the change of the shared file offset).

The code goes like this:

int main() {

  char buf[200];
  int r;
  pid_t pid = 0;

  while(getcmd(buf, 200, pid) >= 0) {
    fprintf(stderr, "current pid: %d\n", getpid());
    pid = fork();
    // Without forking the fgets() reads all lines normally
    if(pid == 0)
      exit(0);

    wait(&r);
  }

  return 0;
}

The getcmd() function is just a wrapper:

int
getcmd(char *buf, int nbuf, pid_t pid)
{
  memset(buf, 0, nbuf);
  if (fgets(buf, nbuf, stdin) == NULL) {
    fprintf(stderr, "EOF !!!\n");
    return -1;
  }
  fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf);
  return 0;
}

I also have an input file temp with some random texts:

line 1
line 2
line 3

After compilation, and I run a.out < temp, the output shows that 6 lines are printed and usually some lines are duplicated. But if I delete the line

pid = fork()
...

then the output becomes normal (just show all the lines one by one, which means fgets() is called 3 times).

Any idea what is going wrong?

Output (this is what got):

pid: 10361 -- getcmd buf ======= --> line1

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
EOF !!!

And I expect to see this:

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line1

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

EOF

A compilable version for reference:

#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <zconf.h>
#include <unistd.h>
#include <memory.h>

int
getcmd(char *buf, int nbuf, pid_t pid)
{
  memset(buf, 0, nbuf);
  if (fgets(buf, nbuf, stdin) == NULL) {
    fprintf(stderr, "EOF !!!\n");
    return -1;
  }
  fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf);
  return 0;
}

int main() {

  char buf[200];
  int r;
  pid_t pid = 0;

  while(getcmd(buf, 200, pid) >= 0) {
    fprintf(stderr, "current pid: %d\n", getpid());
    pid = fork();
    // Without forking the fgets() reads all lines normally
    if(pid == 0)
      exit(0);

    wait(&r);
  }

  return 0;
}

Thanks!

2 Answers

I found a solution for using fgets() from this thread that talks about the same problem, tldr:

exit flushes the stdio buffers in the child. ... For more details here is the link corresponding to the POSIX reference, chapter 2.5.1:

http://pubs.opengroup.org/onlinepubs/007904875/functions/xsh_chap02_05.html

The behaviour is therefore undefined, and thus is allowed to change between glibc 2.19 and 2.24.

The fix:

As written the above link, two solutions are possible to fix the code:

if(fork() == 0) { fclose(fd); exit(1); }

or

if(fork() == 0) { _exit(1); }

Related