Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Understanding File I/O Operations in Linux Systems

Notes 1

In Linux systems, everything is treated as a file. Files consist of content and metadata, and can exist in either opened or unopened states. Unopened files reside on disk storage and are organized for efficient retrieval.

When a file is opened, it's loaded into memory by a process. The operating system manages numerous open files across multiple processes using data structures, typically implemented as arrays of file descriptors.

Standard Library File Operations

Opening Files with fopen

The fopen function provides a standard interface for opening files:

#include <stdio.h>

int main() {
    FILE* stream = fopen("data.log", "r");
    if (stream == NULL) {
        perror("fopen");
        return 1;
    }
    
    char line[64];
    for (int i = 0; i < 5; i++) {
        fgets(line, sizeof(line), stream);
        printf("%s", line);
    }
    fclose(stream);
    return 0;
}

Common modes include:

  • r: Read-only access
  • w: Write-only, truncates existing content
  • a: Append mode

Process Working Directory

Files opened without absolute paths are created relative to the process's current working directory:

#include <stdio.h>
#include <unistd.h>

int main() {
    chdir("/tmp/workspace");
    FILE* fp = fopen("output.txt", "w");
    
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }
    
    const char* content = "system message";
    fwrite(content, strlen(content), 1, fp);
    fclose(fp);
    return 0;
}

System Call Interface

Underlying all standard library functions are system calls that interact directly with the kernel.

The open Function

System calls provide lower-level file operations:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    umask(0);
    int descriptor = open("record.dat", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    
    if (descriptor < 0) {
        perror("open");
        return 1;
    }
    
    const char* message = "persistent data";
    write(descriptor, message, strlen(message));
    close(descriptor);
    return 0;
}

Key flags include:

  • O_RDONLY, O_WRONLY, O_RDWR: Access permissions
  • O_CREAT: Create file if nonexistent
  • O_TRUNC: Truncate existing content
  • O_APPEND: Write at file end

Bitwise Flag Combination

Flags use individual bits within an integer:

#include <stdio.h>

#define READ_MODE    0x1   // 00000001
#define WRITE_MODE   0x2   // 00000010
#define CREATE_MODE  0x4   // 00000100

void analyze_flags(int flags) {
    if (flags & READ_MODE)
        printf("Read enabled\n");
    if (flags & WRITE_MODE)
        printf("Write enabled\n");
    if (flags & CREATE_MODE)
        printf("Create enabled\n");
}

int main() {
    analyze_flags(READ_MODE | WRITE_MODE);
    return 0;
}

File Descriptors

Each open file receives a unique descriptor number:

#include <stdio.h>
#include <fcntl.h>

int main() {
    umask(0);
    int fd1 = open("first.txt", O_CREAT | O_RDONLY, 0666);
    int fd2 = open("second.txt", O_CREAT | O_RDONLY, 0666);
    int fd3 = open("third.txt", O_CREAT | O_RDONLY, 0666);
    
    printf("Descriptors: %d %d %d\n", fd1, fd2, fd3);
    // Typically outputs: 3 4 5
    
    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

File descriptors 0, 1, and 2 are reserved for stdin, stdout, and stderr respectively.

Reading and Writing Data

Write Operations

Writing data to files requires specifying the target descriptor:

#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main() {
    int fd = open("storage.bin", O_WRONLY | O_CREAT | O_APPEND, 0666);
    
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    const char* payload = "important information";
    ssize_t written = write(fd, payload, strlen(payload));
    
    if (written < 0) {
        perror("write");
        close(fd);
        return 1;
    }
    
    close(fd);
    return 0;
}

Read Operations

Reading handles various scenarios including partial reads:

#include <unistd.h>
#include <fcntl.h>

ssize_t full_read(int fd, void* buffer, size_t length) {
    ssize_t total = 0;
    ssize_t result;
    
    while (length > 0) {
        result = read(fd, buffer, length);
        
        if (result < 0) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        
        if (result == 0)
            break;
            
        total += result;
        buffer = (char*)buffer + result;
        length -= result;
    }
    
    return total;
}

int main() {
    int fd = open("source.dat", O_RDONLY);
    
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    char data[1024];
    ssize_t bytes = full_read(fd, data, sizeof(data));
    
    if (bytes < 0) {
        perror("read");
        close(fd);
        return 1;
    }
    
    write(STDOUT_FILENO, data, bytes);
    close(fd);
    return 0;
}

Error Handling

Proper error checking prevents data loss:

#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd = open("critical.db", O_WRONLY | O_CREAT, 0666);
    
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    const char* update = "new record";
    ssize_t status = write(fd, update, strlen(update));
    
    if (status < 0) {
        perror("write");
        close(fd);
        return 1;
    }
    
    if (close(fd) < 0) {
        perror("close");
        return 1;
    }
    
    return 0;
}

Always verify return values from file operations to ensure data integrity.

Tags: Linuxfile-io

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.