open, read, write, close, lseek
What This Concept Is
These are the five raw I/O syscalls that every higher-level C I/O facility (FILE *, fopen, fgets, fread) is built on.
int open(const char *path, int flags, mode_t mode)-- ask the kernel to openpath; return a fd or-1.flagsincludeO_RDONLY,O_WRONLY,O_RDWR,O_CREAT,O_TRUNC,O_APPEND,O_CLOEXEC,O_NONBLOCK.ssize_t read(int fd, void *buf, size_t n)-- try to read up tonbytes intobuf; return the number actually read,0for end-of-file, or-1witherrno.ssize_t write(int fd, const void *buf, size_t n)-- try to write up tonbytes frombuf; return the number actually written, or-1witherrno.int close(int fd)-- release the fd. After close, the number may be reused by the nextopen/pipe/dup.off_t lseek(int fd, off_t off, int whence)-- reposition the file offset.whenceisSEEK_SET,SEEK_CUR, orSEEK_END.
The model is byte-oriented. There is no notion of "lines" or "records"; that is a convention layered on top in user code.
Why It Matters Here
This is the smallest complete I/O interface in UNIX. Everything else -- stdio, mmap, sockets, pipes -- is either a wrapper over these five (plus a couple of relatives like pread, pwrite, readv, writev) or an alternative to them that still uses the fd abstraction.
Understanding the failure modes is the point. read can return fewer bytes than you asked for. write can too. The loops that handle partial transfers are the distinguishing feature of systems code.
Concrete Example
A minimal cat-like program:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
static int cat_fd(int in) {
char buf[4096];
for (;;) {
ssize_t n = read(in, buf, sizeof buf);
if (n == 0) return 0;
if (n < 0) {
if (errno == EINTR) continue;
return -1;
}
char *p = buf;
size_t left = (size_t)n;
while (left > 0) {
ssize_t w = write(1, p, left);
if (w < 0) {
if (errno == EINTR) continue;
return -1;
}
p += w; left -= (size_t)w;
}
}
}
int main(int argc, char **argv) {
if (argc < 2) return cat_fd(0) < 0;
for (int i = 1; i < argc; i++) {
int fd = open(argv[i], O_RDONLY);
if (fd < 0) { perror(argv[i]); return 1; }
if (cat_fd(fd) < 0) { perror("cat"); return 1; }
close(fd);
}
return 0;
}
Two things worth memorizing. First, both loops (read and write) handle EINTR by retrying. Second, write is inside a nested loop -- it can return fewer bytes than requested, so you advance the pointer and try again.
Common Confusion / Misconception
"read(fd, buf, 100) reads 100 bytes." Only if 100 bytes happen to be immediately available. Pipes, sockets, and slow disks routinely return short counts. Code that assumes "full" reads works in testing (small files come back in one chunk) and fails in production.
"Short write is impossible because I am writing to a local file." For regular files this is usually true on Linux, but write may be interrupted by a signal or run out of disk space mid-write. Production code loops.
"lseek(fd, 0, SEEK_SET) rewinds the file." Yes -- but note that lseek is a no-op on non-seekable fds (pipes, sockets, terminals) and returns -1 with errno == ESPIPE. You cannot rewind a pipe.
Another trap: forgetting the third argument to open when O_CREAT is set. Without a mode_t, permissions are undefined; the kernel may refuse the call. Use open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644).
How To Use It
For every raw I/O call:
- Check the return value against
-1for error, and branch onerrno(EINTR-> retry;EAGAIN-> maybe retry later;ENOENT-> user-visible message). - Loop short transfers. Never treat
readorwriteas "all or nothing." closeevery fd youopen, on every exit path. (goto cleanupis idiomatic here, even though it is otherwise frowned on.)
Check Yourself
- Why must you loop
read? - What happens if you call
lseekon a pipe? - What is the minimum mode-safe form of
openwhen creating a file?
Mini Drill or Application
Do all four:
- Implement the
cat_fdloop above from memory. Test it on/etc/hostnameand on standard input. - Extend it: write
wc -l(count newlines) using onlyread,write, andlseek-- no stdio. Compare its output with systemwc -lon at least three files. - Add
O_APPENDto an open and write from two processes to the same file. Verify output is not interleaved inside a singlewrite. - Explain, in one sentence, why
O_APPENDgives a different guarantee thanlseek(fd, 0, SEEK_END); write(fd, ...);.
Read This Only If Stuck
- K&R 8.2: Low-Level I/O -- Read and Write
- K&R 8.3:
open,creat,close,unlink - K&R 8.5: Implementation of fopen and getc
- Man page:
man 2 open - Man page:
man 2 read - Man page:
man 2 write - Man page:
man 2 lseek