Shared File Offset & Status Flags

Shared File Offset & Status Flags
Topic 3 → Subtopic 2  |  How lseek, O_APPEND and fcntl interact with fork()
Topic 3
Subtopic 2
Shared
State
3
Code Examples

Why Shared State Matters

The shared file offset and status flags between parent and child are not just a curiosity — they are intentional and useful. When both processes write to the same file, sharing the offset prevents them from overwriting each other. But if you don’t want sharing, you need to close and re-open files in each process independently.

Keywords:

lseek() SEEK_SET SEEK_CUR O_APPEND F_GETFL F_SETFL fcntl() atomic write

📄 File Offset Sharing — Step by Step

Step
Action
File Offset
1
Parent opens file
0
2
Parent calls fork()
0 (shared)
3
Child: lseek(fd, 1000, SEEK_SET)
1000 (both see it)
4
Parent checks lseek(fd,0,SEEK_CUR)
1000 !

💻 Code Example 1: Using O_APPEND for Safe Multi-Process Logging

When multiple processes write to a log file, O_APPEND ensures each write goes to the end atomically — no overwriting:

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

#define LOG_FILE "/tmp/process_log.txt"

void write_log(int fd, const char *who, int count)
{
    char buf[128];
    for (int i = 0; i < count; i++) {
        int len = snprintf(buf, sizeof(buf),
                           "[%s PID=%d] Log entry #%d\n",
                           who, getpid(), i+1);
        write(fd, buf, len);
        /* Small sleep to interleave with other process */
        usleep(10000);
    }
}

int main(void)
{
    /* Open with O_APPEND: writes always go to end, atomically */
    int fd = open(LOG_FILE,
                  O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
                  0644);
    if (fd == -1) { perror("open"); exit(1); }

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        write_log(fd, "Child ", 5);
        close(fd);
        _exit(0);
    }

    write_log(fd, "Parent", 5);
    close(fd);
    wait(NULL);

    printf("Log file contents (%s):\n", LOG_FILE);
    system("cat " LOG_FILE);
    return 0;
}
O_APPEND guarantee: Each write() with O_APPEND is an atomic seek-to-end + write operation. Even with multiple processes writing concurrently, lines won’t corrupt each other (as long as each write() is smaller than PIPE_BUF ≈ 4096 bytes).

💻 Code Example 2: Adding/Removing Flags with fcntl(F_SETFL)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

void print_flags(int fd, const char *label)
{
    int flags = fcntl(fd, F_GETFL);
    printf("[%s] O_APPEND=%s  O_NONBLOCK=%s\n",
           label,
           (flags & O_APPEND)   ? "ON" : "off",
           (flags & O_NONBLOCK) ? "ON" : "off");
}

int main(void)
{
    int fd = open("/tmp/flags_test.txt",
                  O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) { perror("open"); exit(1); }

    setbuf(stdout, NULL);
    print_flags(fd, "Before fork");

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        /* Child adds O_APPEND and O_NONBLOCK */
        int flags = fcntl(fd, F_GETFL);
        flags |= O_APPEND | O_NONBLOCK;
        fcntl(fd, F_SETFL, flags);
        print_flags(fd, "Child after setting");
        _exit(0);
    }

    wait(NULL);
    /* Parent sees flags set by child (shared OFT entry!) */
    print_flags(fd, "Parent after child exits");

    /* Parent removes O_APPEND */
    int flags = fcntl(fd, F_GETFL);
    flags &= ~O_APPEND;
    fcntl(fd, F_SETFL, flags);
    print_flags(fd, "Parent after removing O_APPEND");

    close(fd);
    return 0;
}

💻 Code Example 3: Getting Independent File Positions (Re-open After fork)

If you DON’T want shared offsets, open the file independently in each process:

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

int main(void)
{
    setbuf(stdout, NULL);

    /* Write test content */
    int setup_fd = open("/tmp/read_test.txt",
                        O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(setup_fd, "ABCDEFGHIJ", 10);
    close(setup_fd);

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        /* Child opens its OWN file descriptor independently */
        int fd = open("/tmp/read_test.txt", O_RDONLY);
        if (fd == -1) { perror("open child"); exit(1); }

        lseek(fd, 5, SEEK_SET);  /* child seeks to position 5 */
        char buf[4] = {0};
        read(fd, buf, 3);
        printf("[Child ] Read from pos 5: '%s'\n", buf);
        close(fd);
        _exit(0);
    }

    /* Parent also opens its OWN file descriptor */
    int fd = open("/tmp/read_test.txt", O_RDONLY);
    if (fd == -1) { perror("open parent"); exit(1); }

    /* Parent reads from position 0 regardless of what child does */
    char buf[4] = {0};
    read(fd, buf, 3);
    printf("[Parent] Read from pos 0: '%s'\n", buf);

    wait(NULL);
    close(fd);
    return 0;
}
Output:
[Parent] Read from pos 0: 'ABC'
[Child ] Read from pos 5: 'FGH'
Each process has its own offset because they opened the file independently after fork.

🅾 Interview Questions
Q1: Two processes share a file descriptor and both write to a file. How can you prevent their output from interleaving?

Use O_APPEND flag when opening. Writes with O_APPEND are atomic: the OS does a seek-to-end + write in a single atomic operation. You can also use file locking (fcntl F_SETLKW) to get exclusive access before writing, or use a pipe/socket to serialize writes through a single process.

Q2: How do you get an independent file offset in the child after fork()?

Close the inherited file descriptor in the child and re-open the file with a new open() call. The new open() creates a fresh open file description with its own offset starting at 0 (or wherever you lseek to). The parent’s offset is unaffected.

Q3: What is the O_APPEND flag and how does it work atomically?

O_APPEND means every write() automatically seeks to the current end-of-file before writing, as a single atomic operation. This prevents two processes from overwriting each other even when writing concurrently. The kernel holds a lock internally during the seek+write sequence.

Series Navigation
Topic 3 → Subtopic 2 of 3

← Previous Next: Closing Unused Descriptors → Index

Leave a Reply

Your email address will not be published. Required fields are marked *