File & I/O · Section 2
openat(2)
Open a file relative to a directory file descriptor — the modern replacement for open().
Signature
#include <fcntl.h>
int openat(int dirfd, const char * pathname, int flags, mode_t mode);- dirfd
- Directory descriptor that anchors pathname when pathname is relative. AT_FDCWD means "use the process's current working directory". An absolute pathname makes dirfd irrelevant (but it is still validated; pass AT_FDCWD when in doubt).
- pathname
- Path of the file to open. If absolute, dirfd is ignored. If relative, dirfd anchors it.
- flags
- Same as open(): access mode ORed with creation/status flags (O_CREAT, O_CLOEXEC, etc.).
- mode
- File-permission bits applied only with O_CREAT or O_TMPFILE.
Description
openat() opens the file at pathname, but interprets pathname relative to the directory referred to by dirfd instead of the calling process's current working directory. With dirfd == AT_FDCWD, openat() behaves exactly like open(). The other arguments — flags, mode — are identical to open(). Modern glibc implements open() via openat() with AT_FDCWD; on aarch64 the legacy open() syscall doesn't exist at all, only openat() and openat2(). Beyond convenience, openat() is the building block of race-free filesystem operations: holding dirfd over an O_DIRECTORY open lets you traverse a tree without recomposing absolute paths between hops, immune to symlink swaps and rename races at higher directory levels.
Architecture mapping
| Architecture | Number | ABI | Entry point |
|---|---|---|---|
| x86 (i386) | 295 | i386 | sys_openat |
| x64 (x86_64) | 257 | common | sys_openat |
| ARM64 (aarch64) | 56 | — | sys_openat |
Kernel history
Introduced in Linux 2.6.16.
2.6.16
openat() was added in 2.6.16 alongside the broader *at family (fstatat, fchmodat, unlinkat, etc.) to enable race-free traversals and per-thread working directories independent of chdir().
5.6
openat2() (Linux 5.6) extends openat() with an open_how struct carrying resolve flags such as RESOLVE_NO_SYMLINKS, RESOLVE_NO_MAGICLINKS, RESOLVE_BENEATH, and RESOLVE_IN_ROOT — letting servers safely open paths under a fixed directory without chroot.
seccomp & containers
Docker default profile
Allowed
Podman default profile
Allowed
openat() is on every default profile. Blocking it kills the process — modern libc cannot open any file without it. Argument filtering on dirfd is awkward because file descriptors are dynamic; the practical lever is filtering openat2() resolve flags or pairing openat() restrictions with Landlock for path-level confinement.
libseccomp
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat2), 0);strace example
$ strace -e openat ls /etc
openat(AT_FDCWD, "/etc", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3Most code paths reach openat() rather than open() because glibc rewrites the call. strace prints the dirfd as AT_FDCWD when applicable, and pretty-prints flags. -y resolves dirfd to its directory path; -e trace=openat,open,openat2 filters the family together.
Security & observability
openat() is the modern observation point for every file open on Linux. eBPF tracepoint sys_enter_openat (and the LSM hook file_open) captures the dirfd, full pathname, and flags. Rootkits commonly hook openat() to hide files matching their patterns from ls / find — the kernel-level audit trail diverges from the userspace view, which is the canonical detection. The *at family also enables tighter sandboxing: a process can pre-open a directory descriptor, drop other privileges, and continue to access only paths under that directory via openat() — a poor-man's chroot.
Errors
- EACCES
- Search permission denied on a component, or the file is not readable/writable as requested.
- EBADF
- dirfd is neither AT_FDCWD nor a valid descriptor.
- EEXIST
- —
- ENOENT
- A component of pathname does not exist, or O_CREAT was not set and the file is missing.
- ENOTDIR
- pathname is relative and dirfd refers to a non-directory.
- EISDIR
- —
- ELOOP
- —
- ENAMETOOLONG
- —
- EMFILE
- —
- ENFILE
- —
Flags
- AT_FDCWD
- -100
- Sentinel value for dirfd meaning "current working directory". Defined as -100 in <fcntl.h>.
- AT_EMPTY_PATH
- 0x1000
- When set (Linux 2.6.39+), an empty pathname operates on dirfd itself — useful with O_PATH descriptors for fstatat/linkat. Requires CAP_DAC_READ_SEARCH outside the process.
- AT_SYMLINK_NOFOLLOW
- 0x100
- Don't dereference the final component if it is a symlink.