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!