How the wrong code is described
The stdio library buffers data, allocating memory to store buffered data. The GNU C library dynamically allocates file structures (some libraries, especially on Solaris, use pointers to statically distributed file structures, but the buffer is still dynamically allocated unless you set up buffering otherwise).
If your thread is working with a copy of the pointer to the global file pointer (since you passed the file pointer to the function as an argument), then it is quite possible that the code will continue to access the data structure that was originally (even if it was freed by closing) and will read data from a buffer that was already present. It will only be when you exit the function or read outside the contents of the buffer that everything starts to go wrong - or the space that was previously allocated for the file structure is redistributed for new use.
FILE *global_fp; void somefunc(FILE *fp, ...) { ... while (fgets(buffer, sizeof(buffer), fp) != 0) ... } void another_function(...) { ... somefunc(global_fp, ...); ... }
Proof of concept code
Tested on MacOS X 10.5.8 (Leopard) with GCC 4.0.1:
#include <stdio.h> #include <stdlib.h> FILE *global_fp; const char etc_passwd[] = "/etc/passwd"; static void error(const char *fmt, const char *str) { fprintf(stderr, fmt, str); exit(1); } static void abuse(FILE *fp, const char *filename) { char buffer1[1024]; char buffer2[1024]; if (fgets(buffer1, sizeof(buffer1), fp) == 0) error("Failed to read buffer1 from %s\n", filename); printf("buffer1: %s", buffer1); /* Dangerous!!! */ fclose(global_fp); if ((global_fp = fopen(etc_passwd, "r")) == 0) error("Failed to open file %s\n", etc_passwd); if (fgets(buffer2, sizeof(buffer2), fp) == 0) error("Failed to read buffer2 from %s\n", filename); printf("buffer2: %s", buffer2); } int main(int argc, char **argv) { if (argc != 2) error("Usage: %s file\n", argv[0]); if ((global_fp = fopen(argv[1], "r")) == 0) error("Failed to open file %s\n", argv[1]); abuse(global_fp, argv[1]); return(0); }
When run on its own source code, the output was:
Osiris JL: ./xx xx.c buffer1: #include <stdio.h> buffer2: ## Osiris JL:
So, empirical evidence that on some systems the scenario that I have outlined can happen.
How to fix the code
Code correction is well discussed in other answers. If you avoid the problem that I illustrated (for example, avoiding global file pointers), this is easiest. Assuming this is not possible, it might be sufficient to compile with the appropriate flags (on many Unix-like systems, the compiler flag -D_REENTRANT does the job), and you will end up using thread-safe versions of the base standard I / O functions. Otherwise, you may need to establish transparent flow control policies around access to file pointers; mutex or something similar (and modify the code to make sure that threads use the mutex before using the appropriate file pointer).