File Sharing
of 3
Code Examples
File Descriptors Are Duplicated During fork()
When fork() is called, the child gets its own copy of the file descriptor table. But here’s the important part: these copies still refer to the same underlying open file descriptions in the kernel. This means the parent and child share things like the file offset and open file status flags for any files that were open before the fork.
Linux file I/O has three layers. Understanding them is the key to understanding what is shared and what is separate after fork():
offset, flags, ref_count
Both share: file offset • O_APPEND flag • access mode
| Property | Shared? | Effect |
|---|---|---|
| File offset | ✓ YES | If child seeks to position 1000, parent sees offset=1000 too |
| Open file status flags (O_APPEND etc.) | ✓ YES | If child sets O_APPEND, parent’s fd also has O_APPEND |
| Access mode (O_RDONLY/O_RDWR) | ✓ YES | Read-only files stay read-only for both |
| File descriptor flags (close-on-exec) | ✗ NO | Each process has its own close-on-exec flag per fd |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(void)
{
int fd, flags;
char template[] = "/tmp/testXXXXXX";
/* Disable buffering so printf output appears immediately */
setbuf(stdout, NULL);
/* Create a temporary file */
fd = mkstemp(template);
if (fd == -1) { perror("mkstemp"); exit(1); }
printf("File offset before fork(): %lld\n",
(long long) lseek(fd, 0, SEEK_CUR));
flags = fcntl(fd, F_GETFL);
printf("O_APPEND flag before fork(): %s\n",
(flags & O_APPEND) ? "on" : "off");
switch (fork()) {
case -1:
perror("fork"); exit(1);
case 0:
/* CHILD: change file offset and set O_APPEND */
if (lseek(fd, 1000, SEEK_SET) == -1) {
perror("lseek"); exit(1);
}
flags = fcntl(fd, F_GETFL);
flags |= O_APPEND;
if (fcntl(fd, F_SETFL, flags) == -1) {
perror("fcntl F_SETFL"); exit(1);
}
_exit(EXIT_SUCCESS);
default:
/* PARENT: wait, then check if it sees child's changes */
if (wait(NULL) == -1) { perror("wait"); exit(1); }
printf("Child has exited\n");
/* Offset should now be 1000 */
printf("File offset in parent: %lld\n",
(long long) lseek(fd, 0, SEEK_CUR));
/* O_APPEND should now be on */
flags = fcntl(fd, F_GETFL);
printf("O_APPEND flag in parent: %s\n",
(flags & O_APPEND) ? "on" : "off");
exit(EXIT_SUCCESS);
}
}
File offset before fork(): 0O_APPEND flag before fork(): offChild has exitedFile offset in parent: 1000O_APPEND flag in parent: on#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main(void)
{
int fd;
char parent_msg[] = "Parent line\n";
char child_msg[] = "Child line\n";
/* Open file BEFORE fork: both share the offset */
fd = open("/tmp/shared_output.txt",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); exit(1); }
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child writes first */
write(fd, child_msg, strlen(child_msg));
close(fd);
_exit(0);
}
/* Parent waits then writes */
wait(NULL);
write(fd, parent_msg, strlen(parent_msg));
close(fd);
/* Show the file contents */
printf("Contents of /tmp/shared_output.txt:\n");
system("cat /tmp/shared_output.txt");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
/* IMPORTANT: disable stdio buffering so writes are immediate */
setbuf(stdout, NULL);
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child: stdout fd is inherited and shared */
printf("[Child ] Writing to stdout (fd=%d)\n",
STDOUT_FILENO);
write(STDOUT_FILENO, "[Child ] Raw write\n", 19);
_exit(0);
}
wait(NULL);
printf("[Parent] Writing to stdout (fd=%d)\n",
STDOUT_FILENO);
write(STDOUT_FILENO, "[Parent] Raw write\n", 19);
return 0;
}
setbuf(stdout, NULL)) in programs with fork() to avoid output being mixed up.A file descriptor is an integer index (per-process) into the file descriptor table. An open file description is a kernel structure (in the open file table) that stores the file offset, status flags, and access mode. Multiple file descriptors (in same or different processes) can refer to the same open file description.
Yes. The file offset is stored in the open file description (kernel level), which is shared between parent and child. Any lseek() or read/write that advances the offset in the child is immediately visible to the parent through its file descriptor.
stdio uses buffering. If stdout is line-buffered or block-buffered, data sitting in the buffer gets copied to the child’s memory during fork(). Both parent and child may flush that same data to the terminal, causing duplicate output. Disabling buffering ensures each write goes directly to the kernel and is not duplicated.
The close-on-exec flag (FD_CLOEXEC) causes a file descriptor to be automatically closed when the process calls exec(). It is NOT shared via the open file description — each process has its own flag per fd. You set it with fcntl(fd, F_SETFD, FD_CLOEXEC) or via O_CLOEXEC in open().
If parent and child both have the same fd pointing to the same open file description, their writes advance the shared file offset atomically. Each write() is atomic for small sizes (under PIPE_BUF or for O_APPEND). With O_APPEND, both processes always write to the current end of file, preventing overwriting each other’s data.
