Linux File Operations: Buffered and Unbuffered I/O

Linux provides two distinct approaches for file I/O operations: library functions (buffered/standard I/O) and system calls (unbuffered I/O). Understanding the differences between these approaches is essential for writing efficeint file handling code.

Buffered I/O (Standard Library Functions)

Buffered I/O functions are part of the C standard library and provide user-space buffering, which reduces the number of system calls and improves performance.

Opening Files: fopen

Header: #include <stdio.h>

Function prototypes:

FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);

Parameters:

  • pathname: Path to the file
  • fd: File descriptor (for fdopen)
  • mode: File open mode (r, w, a, r+, w+, a+)
  • Returns: FILE pointer on success, NULL on failure

Reading Files: fread

Header: #include <stdio.h>

size_t fread(void *buffer, size_t element_size, size_t count, FILE *stream);

Parameters:

  • buffer: Memory buffer to store read data
  • element_size: Size of each element in bytes
  • count: Number of elements to read
  • stream: File stream pointer
  • Returns: Number of elements successfully read (may be less than count on error/EOF)

Writing Files: fwrite

Header: #include <stdio.h>

size_t fwrite(const void *buffer, size_t element_size, size_t count, FILE *stream);

Parameters:

  • buffer: Memory buffer containing data to write
  • element_size: Size of each element in bytes
  • count: Number of elements to write
  • stream: File stream pointer
  • Returns: Number of elements successfully written

Closing Files: fclose

Header: #include <stdio.h>

int fclose(FILE *stream);

Parameters:

  • stream: File stream pointer to close
  • Returns: 0 on success, EOF on error

Example: File Copy Using Buffered I/O

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

int main(int argc, char *argv[]) {
    int option;
    char src_path[256] = {0}, dst_path[256] = {0};
    FILE *src_file = NULL, *dst_file = NULL;

    while ((option = getopt(argc, argv, "s:d:")) != -1) {
        switch (option) {
            case 's':
                strncpy(src_path, optarg, sizeof(src_path) - 1);
                break;
            case 'd':
                strncpy(dst_path, optarg, sizeof(dst_path) - 1);
                break;
            default:
                fprintf(stderr, "Usage: %s -s source_file -d destination_file\n", argv[0]);
                return 1;
        }
    }

    if (strlen(src_path) == 0 || strlen(dst_path) == 0) {
        fprintf(stderr, "Usage: %s -s source_file -d destination_file\n", argv[0]);
        return 1;
    }

    src_file = fopen(src_path, "r");
    if (src_file == NULL) {
        perror("fopen source");
        return 1;
    }

    dst_file = fopen(dst_path, "w");
    if (dst_file == NULL) {
        perror("fopen destination");
        fclose(src_file);
        return 1;
    }

    char data_buffer[4096] = {0};
    size_t bytes_read;

    while ((bytes_read = fread(data_buffer, 1, sizeof(data_buffer), src_file)) > 0) {
        if (fwrite(data_buffer, 1, bytes_read, dst_file) != bytes_read) {
            perror("fwrite error");
            break;
        }
    }

    fclose(src_file);
    fclose(dst_file);
    return 0;
}

Unbuffered I/O (System Calls)

System calls provide direct interface to the kernel without user-space buffering. These operations interact directly with the operating system.

Opening Files: open

Headers:

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

Function prototypes:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Parameters:

  • pathname: Path to the file
  • flags: Bitwise OR of access mode and optional flags:
    • O_RDONLY: Read-only
    • O_WRONLY: Write-only
    • O_RDWR: Read-write
    • O_CREAT: Create file if it doesn't exist (requires mode parameter)
    • O_EXCL: Fail if file exists (used with O_CREAT)
    • O_TRUNC: Truncate file to zero length
    • O_APPEND: Append to end of file
  • mode: File permissions (e.g., 0644, S_IRUSR|S_IWUSR)
  • Returns: File descriptor on success, -1 on failure

Reading Files: read

Header: #include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

Parameters:

  • fd: File descriptor
  • buf: Buffer to store read data
  • count: Number of bytes to read
  • Returns: Number of bytes read, 0 on EOF, -1 on error

Writing Files: write

Header: #include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

Parameters:

  • fd: File descriptor
  • buf: Buffer containing data to write
  • count: Number of bytes to write
  • Returns: Number of bytes written, -1 on errer

Closing Files: cloce

Header: #include <unistd.h>

int close(int fd);

Parameters:

  • fd: File descriptor to close
  • Returns: 0 on success, -1 on error

Important notes:

  • close() releases the file descriptor association with the file
  • When a process terminates, the kernel automatically closes all open file descriptors
  • The returned file descriptor is always the lowest numbered unused descriptor

Creating Files: creat

Headers:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);

Parameters:

  • pathname: Path to the file
  • mode: File permissions
  • Returns: File descriptor on success, -1 on failure

Example: Reading File Contents

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
        return 1;
    }

    int file_descriptor = open(argv[1], O_RDONLY);
    if (file_descriptor == -1) {
        perror("open failed");
        fprintf(stderr, "Error code: %d\n", errno);
        return 1;
    }

    char read_buffer[1024] = {0};
    ssize_t bytes_received;

    while ((bytes_received = read(file_descriptor, read_buffer, sizeof(read_buffer))) > 0) {
        if (write(STDOUT_FILENO, read_buffer, bytes_received) != bytes_received) {
            perror("write failed");
            break;
        }
    }

    if (bytes_received == -1) {
        perror("read failed");
    }

    close(file_descriptor);
    return 0;
}

Example: File Copy Using System Calls

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    int input_fd, output_fd;

    if (argc < 3) {
        fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
        return 1;
    }

    input_fd = open(argv[1], O_RDONLY);
    if (input_fd == -1) {
        perror("open input");
        return 1;
    }

    output_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (output_fd == -1) {
        perror("open output");
        close(input_fd);
        return 1;
    }

    char transfer_buffer[2048] = {0};
    ssize_t bytes_in, bytes_out;

    while ((bytes_in = read(input_fd, transfer_buffer, sizeof(transfer_buffer))) > 0) {
        bytes_out = write(output_fd, transfer_buffer, bytes_in);
        if (bytes_out != bytes_in) {
            perror("write error");
            break;
        }
    }

    if (bytes_in == -1) {
        perror("read error");
    }

    close(input_fd);
    close(output_fd);
    return 0;
}

Additional File Operations

File Descriptor Limits

  • View limits: ulimit -a shows open files limit (default 1024)
  • Modify limit: ulimit -n 4096 increases limit

Obtaining File Information: stat

The stat structure provides comprehensive file metadata:

struct stat {
    dev_t     st_dev;         /* Device ID containing file */
    ino_t     st_ino;         /* Inode number (unique file identifier) */
    mode_t    st_mode;        /* File type and permissions */
    nlink_t   st_nlink;       /* Number of hard links */
    uid_t     st_uid;         /* Owner user ID */
    gid_t     st_gid;         /* Owner group ID */
    off_t     st_size;       /* File size in bytes */
    blksize_t st_blksize;     /* Preferred block size for I/O */
    blkcnt_t  st_blocks;      /* Number of 512-byte blocks allocated */
    struct timespec st_atim; /* Last access time */
    struct timespec st_mtim; /* Last modification time */
    struct timespec st_ctim; /* Last status change time */
};

Example: Displaying File Information

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char *argv[]) {
    const char *target_path = (argc > 1) ? argv[1] : ".";
    struct stat file_info;

    if (lstat(target_path, &file_info) == -1) {
        perror("lstat");
        return 1;
    }

    /* Method 1: Using S_IS* macros */
    if (S_ISREG(file_info.st_mode)) {
        printf("Regular file\n");
    } else if (S_ISDIR(file_info.st_mode)) {
        printf("Directory\n");
    } else if (S_ISLNK(file_info.st_mode)) {
        printf("Symbolic link\n");
    } else if (S_ISFIFO(file_info.st_mode)) {
        printf("FIFO/pipe\n");
    }

    /* Method 2: Using bitwise AND with S_IFMT */
    switch (file_info.st_mode & S_IFMT) {
        case S_IFBLK:  printf("Block device\n");       break;
        case S_IFCHR:  printf("Character device\n");  break;
        case S_IFDIR:  printf("Directory\n");         break;
        case S_IFIFO:  printf("FIFO/pipe\n");          break;
        case S_IFLNK:  printf("Symbolic link\n");     break;
        case S_IFREG:  printf("Regular file\n");       break;
        case S_IFSOCK: printf("Socket\n");            break;
        default:       printf("Unknown type\n");       break;
    }

    printf("Inode: %lu\n", (unsigned long)file_info.st_ino);
    printf("Size: %ld bytes\n", (long)file_info.st_size);
    printf("Hard links: %lu\n", (unsigned long)file_info.st_nlink);

    return 0;
}

Modifying File Properties: fcntl

The fcntl function modifies properties of already open files without reopening them.

Headers:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int command, ... /* arg */);

Example: Setting and Getting File Flags

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

int main(int argc, char *argv[]) {
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    /* Set non-blocking mode */
    int flags;
    flags = fcntl(fd, F_GETFL);
    if (flags < 0) {
        perror("fcntl F_GETFL");
        close(fd);
        return 1;
    }

    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    /* Retrieve and display access mode */
    flags = fcntl(fd, F_GETFL);
    switch (flags & O_ACCMODE) {
        case O_RDONLY:  printf("Read-only\n");    break;
        case O_WRONLY:  printf("Write-only\n");   break;
        case O_RDWR:    printf("Read-write\n");   break;
    }

    if (flags & O_NONBLOCK) {
        printf("Non-blocking mode enabled\n");
    }

    /* Remove non-blocking flag */
    fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);

    close(fd);
    return 0;
}

File Positioning: lseek

Headers:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

Parameters:

  • fd: File descriptor
  • offset: Offset from reference position
  • whence: Reference position (SEEK_SET, SEEK_CUR, SEEK_END)
  • Returns: New file position on success, -1 on error

Example: File Positioning Operations

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    int fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        perror("open");
        fprintf(stderr, "errno = %d\n", errno);
        return 1;
    }

    /* Get current position */
    printf("Initial position: %ld\n", lseek(fd, 0, SEEK_CUR));

    /* Read two bytes and advance position */
    char ch;
    read(fd, &ch, 1);
    read(fd, &ch, 1);
    printf("After reading 2 bytes: %ld\n", lseek(fd, 0, SEEK_CUR));

    /* Skip 3 bytes forward */
    lseek(fd, 3, SEEK_CUR);
    printf("After skipping 3 bytes: %ld\n", lseek(fd, 0, SEEK_CUR));

    /* Get file size */
    off_t file_size = lseek(fd, 0, SEEK_END);
    printf("File size: %ld bytes\n", (long)file_size);

    /* Extend file by 100 bytes */
    off_t new_size = lseek(fd, 100, SEEK_CUR);
    printf("New file size after extension: %ld bytes\n", (long)new_size);

    /* Write to trigger actual allocation */
    if (write(fd, "x", 1) != 1) {
        perror("write");
    }

    close(fd);
    return 0;
}

Creating Hard Links: link

int link(const char *existing_path, const char *new_path);
  • Creates a new directory entry (hard link) for an existing file
  • Returns 0 on success, -1 on failure
  • Note: The mv command only modifies directory entries, not the file itself

Removing Links: unlink

int unlink(const char *pathname);
  • Removes a directory entry for a file
  • The file is actually deleted when the link count reaches zero
  • Returns 0 on success, -1 on failure

Linux removes files by decrementing the link count. When the count reaches zero and no process has the file open, the operating system reclaims the disk space.

Checking File Access: access

int access(const char *pathname, int mode);

mode values:

  • R_OK: Check read permission
  • W_OK: Check write permission
  • X_OK: Check execute permission
  • F_OK: Check file existence

Returns: 0 if permission exists or file exists, -1 otherwise

Changing Permissions: chmod

int chmod(const char *pathname, mode_t mode);
  • Modifies file access permissions
  • Returns 0 on success, -1 on failure

Truncating Files: truncate

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
  • Sets file size to specified length
  • Can extend or truncate files
  • Useful for extending files without writing data

Duplicating File Descriptors: dup/dup2

int dup(int oldfd);
int dup2(int oldfd, int newfd);

Example: Basic dup Usage

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

int main(int argc, char *argv[]) {
    int original = open(argv[1], O_RDONLY);  /* Typically fd 3 */
    int duplicate = dup(original);           /* Gets next available fd */
    printf("Duplicated fd: %d\n", duplicate);
    close(original);
    close(duplicate);
    return 0;
}

Example: dup2 for Redirection

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

int main(int argc, char *argv[]) {
    int file1 = open(argv[1], O_RDWR);
    int file2 = open(argv[2], O_RDWR);

    printf("Original fds: file1=%d, file2=%d\n", file1, file2);

    /* Redirect file2 to file1 */
    int result = dup2(file1, file2);
    printf("dup2 returned: %d\n", result);

    const char *msg = "written via redirected fd\n";
    write(file2, msg, strlen(msg));

    close(file1);
    close(file2);
    return 0;
}

Using fcntl for Duplication

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

int main(int argc, char *argv[]) {
    int original = open(argv[1], O_RDWR);
    printf("Original fd: %d\n", original);

    /* Duplicate to lowest available fd */
    int copy1 = fcntl(original, F_DUPFD, 0);
    printf("Copy1 (lowest available): %d\n", copy1);

    /* Duplicate to specific fd (100) */
    int copy2 = fcntl(original, F_DUPFD, 100);
    printf("Copy2 (fd 100): %d\n", copy2);

    close(original);
    close(copy1);
    close(copy2);
    return 0;
}

Tags: Linux File I/O System Programming c programming Buffered IO

Posted on Sat, 09 May 2026 19:38:16 +0000 by mogen