funwithlinux blog

When Can read(2) Return a Short Read on Linux Filesystems? (Non-EOF Scenarios Explained)

The read(2) system call is a cornerstone of input/output (I/O) operations in Linux, allowing programs to read data from file descriptors. Many developers assume read(2) will either fill the requested buffer, return 0 (end-of-file/EOF), or -1 (error). However, this is not always the case: read(2) can sometimes return fewer bytes than requested without hitting EOF. These "short reads" are critical to understand for writing robust, bug-free code—especially in systems programming, where mishandling them can lead to data corruption, incomplete reads, or application crashes.

In this blog, we’ll demystify non-EOF short reads on Linux. We’ll explore the scenarios where they occur, why they happen, and how to handle them gracefully. Whether you’re working with terminals, pipes, network filesystems, or special devices, this guide will help you avoid common pitfalls.

2026-01

Table of Contents#

  1. Understanding read(2) Basics
  2. What Is a "Short Read"?
  3. Non-EOF Scenarios for Short Reads
  4. How to Handle Short Reads in Code
  5. Conclusion
  6. References

Understanding read(2) Basics#

Before diving into short reads, let’s recap how read(2) works. The system call has the following signature:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • fd: The file descriptor to read from (e.g., STDIN_FILENO for standard input, or a handle to a file/device).
  • buf: A pointer to the buffer where data will be stored.
  • count: The maximum number of bytes to read.

Return Value:

  • On success: The number of bytes read (≥ 0).
  • 0: End-of-file (EOF) was reached (no more data to read).
  • -1: An error occurred (check errno for details, e.g., EINTR, EAGAIN).

What Is a "Short Read"?#

A "short read" occurs when read(2) returns a positive value less than count without reaching EOF. For example, if you request 1024 bytes but read(2) returns 500, that’s a short read.

Crucially, short reads are not errors—they’re a normal part of how certain file types behave in Linux. The key distinction is that EOF returns 0, while short reads return a positive value < count.

Non-EOF Scenarios for Short Reads#

Short reads are rare for regular files on local filesystems (e.g., ext4, XFS), where read(2) will typically return the requested count bytes (or 0 at EOF). However, they are common for special files, network-backed filesystems, and file-like objects (e.g., pipes, terminals). Below are the most common scenarios:

1. Signal Interruption (EINTR)#

If a signal is delivered to the process while read(2) is blocked waiting for data, the system call may be interrupted. By default, read(2) will return the number of bytes already read (if any) and set errno to EINTR (interrupted system call).

Why It Happens:#

Linux allows signals to interrupt blocking system calls. For example, if your program is blocked on read(2) from a slow device (e.g., a terminal), and the user sends SIGINT (Ctrl+C), the read(2) call may exit early with a partial read.

Example:#

Suppose you run this code and press Ctrl+C while read(2) is waiting:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
 
int main() {
    char buf[1024];
    ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
    if (n == -1) {
        perror("read"); // May print "read: Interrupted system call" (EINTR)
        return 1;
    }
    printf("Read %zd bytes\n", n); // Could be < 1024 if interrupted mid-read
    return 0;
}

If the signal arrives after some data has been read (e.g., the user typed "hello" before Ctrl+C), n will be 5 (the length of "hello"), and errno will not be set (since partial data was read). Only if no data was read before the signal does errno become EINTR.

2. Non-Blocking I/O (O_NONBLOCK)#

When a file descriptor is opened with the O_NONBLOCK flag, read(2) will never block. If data is available but less than count, it returns the available bytes (a short read). If no data is available, it returns -1 with errno set to EAGAIN or EWOULDBLOCK (instead of blocking).

Why It Happens:#

Non-blocking I/O is used for responsive applications (e.g., servers) that can’t wait for data. For example, a chat server might use non-blocking sockets to check for new messages without stalling.

Example:#

Opening a pipe in non-blocking mode and reading before data is available:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
 
int main() {
    int pipefd[2];
    pipe(pipefd); // Create a pipe
 
    // Set read end to non-blocking
    int flags = fcntl(pipefd[0], F_GETFL);
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
 
    char buf[1024];
    ssize_t n = read(pipefd[0], buf, sizeof(buf));
    if (n == -1) {
        if (errno == EAGAIN) {
            printf("No data available (non-blocking)\n"); // Expected here
        } else {
            perror("read");
        }
    } else {
        printf("Read %zd bytes (short read)\n", n); // If some data was available
    }
    return 0;
}

If another process writes 500 bytes to the pipe before this read, n will be 500 (a short read, since count was 1024).

3. Terminal Devices (TTY/PTY) in Canonical Mode#

Terminals (TTYs) and pseudo-terminals (PTYs) default to canonical mode (line-buffered input). In this mode, read(2) returns when a newline (\n) is received—even if the buffer isn’t full. This is a classic source of short reads.

Why It Happens:#

Canonical mode is user-friendly: it lets users edit input (backspace, Ctrl+W) before pressing Enter. The kernel buffers input until \n is received, then returns the entire line to read(2).

Example:#

If you run read(STDIN_FILENO, buf, 1024) and type "hello\n", read(2) returns 6 (5 characters + \n), even though you requested 1024 bytes.

To disable canonical mode (e.g., for real-time input), use tcsetattr to set ICANON off. Even then, short reads can occur if using VMIN/VTIME (see man termios for details).

4. Pipes and FIFOs#

Pipes (unnamed) and FIFOs (named pipes) are inter-process communication (IPC) mechanisms that use in-memory buffers. read(2) on a pipe/FIFO returns immediately with whatever data is available—even if less than count.

Why It Happens:#

Pipes have a fixed buffer size (default 64KB on Linux, configurable via /proc/sys/fs/pipe-max-size). Writers block when the buffer is full, and readers return partial data when the buffer has less than count bytes.

Example:#

Parent writes 100 bytes to a pipe; child reads 200 bytes (short read):

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
int main() {
    int pipefd[2];
    pipe(pipefd); // Create pipe: pipefd[0] = read end, pipefd[1] = write end
 
    pid_t pid = fork();
    if (pid == 0) { // Child (reader)
        close(pipefd[1]); // Close unused write end
        char buf[200];
        ssize_t n = read(pipefd[0], buf, sizeof(buf)); // Request 200 bytes
        printf("Child read %zd bytes: %.*s\n", n, (int)n, buf); // Prints 100
        close(pipefd[0]);
        exit(0);
    } else { // Parent (writer)
        close(pipefd[0]); // Close unused read end
        const char *msg = "This is a 100-byte message!.................................................."; // 100 bytes
        write(pipefd[1], msg, strlen(msg)); // Write 100 bytes
        close(pipefd[1]);
        wait(NULL); // Wait for child
    }
    return 0;
}

The child will read 100 bytes (not 200), demonstrating a short read.

5. Network Filesystems (NFS, CIFS, etc.)#

Network filesystems like NFS (Network File System) or CIFS (Common Internet File System) retrieve data over a network. Due to network latency or partial packet delivery, read(2) may return fewer bytes than requested.

Why It Happens:#

Network filesystems rely on remote servers. If a read request is split across multiple network packets and only part of the data arrives before a timeout, the kernel may return the partial data instead of waiting indefinitely.

Example:#

Reading a large file over NFS with intermittent network issues could result in read(2) returning 1500 bytes (one Ethernet frame) instead of the requested 4096 bytes.

6. Pseudo-Filesystems and Special Files (e.g., /proc, /sys)#

Pseudo-filesystems like /proc (process information) and /sys (kernel parameters) contain virtual files that are generated on-the-fly. Reading these files can return short reads if the data changes mid-read or is dynamically sized.

Why It Happens:#

Files in /proc (e.g., /proc/[pid]/cmdline) or /sys (e.g., /sys/class/net/eth0/speed) are not stored on disk. Their content is generated when read, and if the underlying data changes during the read (e.g., a process exits while reading /proc/[pid]/status), read(2) may return partial data.

Example:#

Reading /proc/loadavg (which reports system load) may return fewer bytes than requested if the load average updates during the read.

How to Handle Short Reads in Code#

To write robust code, never assume read(2) fills the buffer. Instead, loop until you’ve read the desired number of bytes, handling errors and edge cases. Here’s a safe pattern:

ssize_t read_all(int fd, void *buf, size_t count) {
    char *buf_ptr = buf;
    size_t remaining = count;
    while (remaining > 0) {
        ssize_t n = read(fd, buf_ptr, remaining);
        if (n == -1) {
            if (errno == EINTR) continue; // Retry on signal interruption
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // For non-blocking: handle retry later (e.g., with select/poll)
                return -1; 
            }
            return -1; // Other errors (e.g., EBADF)
        }
        if (n == 0) break; // EOF reached (no more data)
        buf_ptr += n;
        remaining -= n;
    }
    return count - remaining; // Bytes read (may be < count if EOF)
}

Key Takeaways:

  • Retry on EINTR (interrupted system call).
  • Handle EAGAIN/EWOULDBLOCK for non-blocking I/O (use select/poll to wait for data).
  • Check for EOF (n == 0) to avoid infinite loops.

Conclusion#

Short reads are a normal part of Linux I/O, especially for non-regular files like terminals, pipes, and network-backed filesystems. Assuming read(2) always fills the buffer is a common pitfall that leads to bugs (e.g., truncated data, incomplete reads).

By understanding the scenarios above—signal interruption, non-blocking I/O, TTY canonical mode, pipes, network filesystems, and pseudo-filesystems—you can write code that gracefully handles partial reads. Always loop until you’ve read the required bytes, and check errno for edge cases like EINTR and EAGAIN.

References#

  • man 2 read: Linux read(2) system call documentation.
  • man 7 signal: Signal handling and EINTR.
  • man 7 pipe: Pipes and FIFOs behavior.
  • man 4 tty_ioctl: Terminal I/O (canonical mode, termios).
  • man 5 nfs: NFS filesystem documentation.
  • Linux Kernel Documentation: Pipes, TTY.
  • GNU C Library: Handling Interrupted System Calls.