Skip to content
/linux-syscalls

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

ArchitectureNumberABIEntry point
x86 (i386)295i386sys_openat
x64 (x86_64)257commonsys_openat
ARM64 (aarch64)56sys_openat

Kernel history

Introduced in Linux 2.6.16.

  1. 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().

  2. 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) = 3

Most 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.

Related syscalls