How does the open() system call work, and what are the commonly used flags?
open() is the fundamental UNIX system call for accessing files. It returns a file descriptor (a small non-negative integer) that serves as an index into the process's file descriptor table.
Prototype:
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Access mode flags (exactly one required):
O_RDONLY-- open for reading onlyO_WRONLY-- open for writing onlyO_RDWR-- open for reading and writing
Common modifier flags (OR-ed with access mode):
O_CREAT-- create file if it does not exist (requiresmodeargument)O_TRUNC-- truncate to zero length if file existsO_APPEND-- append on each write (atomic seek-to-end + write)O_EXCL-- withO_CREAT, fail if file exists (atomic create-or-fail)O_NONBLOCK-- non-blocking mode for FIFOs and devicesO_CLOEXEC-- close fd on exec (avoids fd leak to child processes)O_SYNC-- synchronous writes (data + metadata flushed to disk)
The kernel always assigns the lowest available file descriptor number. The mode argument (e.g., 0644) is masked by the process's umask when creating files.
1. Call open(pathname, flags, mode)
2. Kernel checks permissions against effective UID/GID
3. Kernel allocates an entry in the open file table
4. Kernel adds an entry in the process fd table pointing to the open file entry
5. Returns lowest available fd number, or -1 on error (sets errno)
6. Common pattern: fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
/* Open for reading only */
int fd1 = open("/etc/passwd", O_RDONLY);
if (fd1 == -1) {
perror("open /etc/passwd");
return 1;
}
printf("fd1 = %d\n", fd1); /* likely 3 */
/* Create or truncate a file for writing */
int fd2 = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 == -1) {
perror("open output.txt");
close(fd1);
return 1;
}
printf("fd2 = %d\n", fd2); /* likely 4 */
/* Atomic create -- fail if file already exists */
int fd3 = open("lockfile", O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd3 == -1) {
if (errno == EEXIST)
fprintf(stderr, "lockfile already exists\n");
else
perror("open lockfile");
} else {
printf("fd3 = %d\n", fd3);
close(fd3);
}
close(fd1);
close(fd2);
return 0;
}
- •open() returns the lowest available file descriptor number
- •Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified
- •O_CREAT requires a third mode argument; actual permissions = mode & ~umask
- •O_CREAT | O_EXCL is atomic: create-or-fail with no race condition
- •O_APPEND guarantees atomic seek-to-end + write (even across processes)
- •Always check the return value; -1 means error and errno is set
- •Forgetting the mode argument with O_CREAT causes undefined permissions (garbage from the stack)
- •O_RDONLY is typically 0, so you cannot test for it with bitwise AND
- •O_TRUNC on a file opened O_RDONLY is undefined behavior on some systems
- •O_CLOEXEC should be used by default to prevent fd leaks across exec()
- •On Linux, open() can fail with EMFILE (per-process limit) or ENFILE (system-wide limit)