openat2(2)
๐ openat2(2) โ System Calls Manual ๐
okay bestie, welcome to openat2, the extended little sibling of openat(2) that said "let me be โจextraโจ". this is the openat2(2) manual page and it's long so buckle up ๐
โจ NAME โจ
openat2 โ open and possibly create a file (extended) ๐
๐ LIBRARY ๐
Standard C library (libc, -lc)
๐ SYNOPSIS ๐
#include <fcntl.h> /* Definition of O_* and S_* constants */
#include <linux/openat2.h> /* Definition of RESOLVE_* constants */
#include <sys/syscall.h> /* Definition of SYS_* constants */
#include <unistd.h>
long syscall(SYS_openat2, int dirfd, const char *path,
struct open_how *how, size_t size);
heads up, bestie: glibc provides no wrapper foropenat2(), so you literally have to go throughsyscall(2). no cap. ๐ซถ
๐ DESCRIPTION ๐
so openat2() is the glow-up of openat(2) โ it's an extension of it and gives you a superset of its functionality. it's giving main character energy fr fr.
openat2() opens the file specified by path. and if that file doesn't exist? it may optionally be created โ but only if O_CREAT is specified in how.flags. (:sparkles:)
just like with openat(2): if path is relative, it gets interpreted relative to the directory that the file descriptor dirfd refers to โ or, if dirfd is the special value AT_FDCWD, relative to the calling process's current working directory. and if path is absolute, then dirfd is ignoredโฆ unless how.resolve contains RESOLVE_IN_ROOT, in which case path gets resolved relative to dirfd instead. iykyk ๐
and here's the slay part: instead of taking a single flags argument, it takes an extensible structure (how) so it can grow new features later. the size argument must be specified as sizeof(struct open_how). no substitutions, bestie.
๐ธ The open_how structure ๐ธ
the how argument is the moment. it specifies how path should be opened, and it acts as a superset of the flags and mode arguments to openat(2). it's a pointer to an open_how structure, which is described over in open_how(2type). ๐
any future extensions to openat2() will show up as new fields appended to the open_how structure, and a zero value in a new field means the kernel behaves as though that extension field wasn't even there. so therefore โ and this is important, girlies โ the caller MUST zero-fill this structure on initialization. (there's more about why this is necessary in the "Extensibility" section of the NOTES. check it later!)
the fields of the open_how structure are, in order:
๐ฆ flags
this field specifies the file creation and file status flags to use when opening the file. all of the O_* flags defined for openat(2) are valid openat2() flag values. โ
now here's where it gets spicy ๐ถ๏ธ โ whereas openat(2) ignores unknown bits in its flags argument, openat2() returns an error if unknown or conflicting flags are specified in how.flags. it does not let things slide.
openat2() also supports this additional flag:
OPENAT2_REGULAR(since Linux 7.2) โ open the file specified bypathonly if it is a regular file. ๐
๐ mode
this field specifies the mode for the new file, with identical semantics to the mode argument of openat(2).
but again, the glow-up: whereas openat(2) ignores bits other than those in the range 07777 in its mode argument, openat2() returns an error if how.mode contains bits other than 07777. and similarly, an error is returned if openat2() is called with a nonzero how.mode and how.flags does not contain O_CREAT or O_TMPFILE. so don't be messy with your bits. ๐
๐ resolve
this one is the moment. it's a bit-mask of flags that modify the way all components of path will be resolved. (for background, go peek at path_resolution(7).)
the primary use case for these flags is letting trusted programs restrict how untrusted paths (or paths inside untrusted directories) are resolved. ๐ the full list of resolve flags is:
RESOLVE_BENEATHโ do not permit the path resolution to succeed if any component of the resolution is not a descendant of the directory indicated bydirfd. this causes absolute symbolic links (and absolute values ofpath) to be rejected. ๐ซ
currently, this flag also disables magic-link resolution (see below). however, this may change in the future. therefore, to ensure that magic links are not resolved, the caller should explicitly specify RESOLVE_NO_MAGICLINKS. (future-proofing, bestie, it's a vibe ๐
)
RESOLVE_IN_ROOTโ treat the directory referred to bydirfdas the root directory while resolvingpath. absolute symbolic links get interpreted relative todirfd. if a prefix component ofpathequates todirfd, then an immediately following..component likewise equates todirfd(just as/..is traditionally equivalent to/). and ifpathis absolute, it is also interpreted relative todirfd.
the effect of this flag is as though the calling process had used chroot(2) to (temporarily) modify its root directory โ specifically to the directory referred to by dirfd. but โ and this is the key difference โ unlike chroot(2) (which changes the filesystem root permanently for a process), RESOLVE_IN_ROOT lets a program efficiently restrict path resolution on a per-open basis. iconic behavior honestly ๐
currently, this flag also disables magic-link resolution. however, this may change in the future. therefore, to ensure that magic links are not resolved, the caller should explicitly specify RESOLVE_NO_MAGICLINKS. (yes this is the same warning as above, it's important, don't skip it ๐ธ)
RESOLVE_NO_MAGICLINKSโ disallow all magic-link resolution during path resolution.
magic links are symbolic-link-like objects most notably found in proc(5); examples include /proc/pid/exe and /proc/pid/fd/*. (see symlink(7) for more deets.)
unknowingly opening magic links can be risky for some applications. examples of such risks include:
- if the process opening a pathname is a controlling process that currently has no controlling terminal (see
credentials(7)), then opening a magic link inside/proc/pid/fdthat happens to refer to a terminal would cause the process to acquire a controlling terminal. ๐ณ - in a containerized environment, a magic link inside
/procmay refer to an object outside the container, and thus may provide a means to escape from the container. ๐
because of such risks, an application may prefer to disable magic link resolution using the RESOLVE_NO_MAGICLINKS flag. smart girl behavior.
and one more thing: if the trailing component (i.e., basename) of path is a magic link, how.resolve contains RESOLVE_NO_MAGICLINKS, and how.flags contains both O_PATH and O_NOFOLLOW, then an O_PATH file descriptor referencing the magic link will be returned. โจ
RESOLVE_NO_SYMLINKSโ disallow resolution of symbolic links during path resolution. this option impliesRESOLVE_NO_MAGICLINKS. two birds, one flag ๐ฆ
if the trailing component (i.e., basename) of path is a symbolic link, how.resolve contains RESOLVE_NO_SYMLINKS, and how.flags contains both O_PATH and O_NOFOLLOW, then an O_PATH file descriptor referencing the symbolic link will be returned.
note that the effect of the RESOLVE_NO_SYMLINKS flag โ which affects the treatment of symbolic links in all of the components of path โ differs from the effect of the O_NOFOLLOW file creation flag (in how.flags), which affects the handling of symbolic links only in the final component of path. yes, they are different, do not confuse them, bestie. ๐
applications that employ the RESOLVE_NO_SYMLINKS flag are encouraged to make its use configurable (unless it's used for a specific security purpose), because symbolic links are very widely used by end-users. setting this flag indiscriminately โ i.e., for purposes not specifically related to security โ for all uses of openat2() may result in spurious errors on previously functional systems. ๐ญ this may occur if, for example, a system pathname that is used by an application is modified (e.g., in a new distribution release) so that a pathname component (now) contains a symbolic link. so likeโฆ don't just slap it everywhere, okay?
RESOLVE_NO_XDEVโ disallow traversal of mount points during path resolution (including all bind mounts). consequently,pathmust either be on the same mount as the directory referred to bydirfd, or on the same mount as the current working directory ifdirfdis specified asAT_FDCWD.
applications that employ the RESOLVE_NO_XDEV flag are encouraged to make its use configurable (unless it's used for a specific security purpose), as bind mounts are widely used by end-users. setting this flag indiscriminately โ i.e., for purposes not specifically related to security โ for all uses of openat2() may result in spurious errors on previously functional systems. ๐ญ this may occur if, for example, a system pathname that is used by an application is modified (e.g., in a new distribution release) so that a pathname component (now) contains a bind mount.
RESOLVE_CACHED(since Linux 5.12) โ make the open operation fail unless all path components are already present in the kernel's lookup cache. if any kind of revalidation or I/O is needed to satisfy the lookup,openat2()fails with the errorEAGAIN. ๐ฅ this is useful in providing a fast-path open that can be performed without resorting to thread offload, or other mechanisms that an application might use to offload slower operations. speed, bestie ๐จ
and finally, the plot twist: if any bits other than those listed above are set in how.resolve, an error is returned. ๐จ
๐ฅ RETURN VALUE ๐ฅ
on success, a new file descriptor is returned. ๐
on error, -1 is returned, and errno is set to indicate the error. ๐ญ
โฆand that's just part 1, bestie, there's more coming ๐
๐ฅ ERRORS ๐ฅ
okay bestie, so openat2() throws everything that its little sibling openat(2) throws โ no cap โ plus these extra ones below. take notes, this is the good part ๐โจ
E2BIGโ someone passed an extension inhowthat this kernel straight up does not support. (peep the Extensibility section down in NOTES for the whole saga of how extensions get handled ๐)EAGAINโhow.resolvehas eitherRESOLVE_IN_ROOTorRESOLVE_BENEATHset, and the kernel could not guarantee that a".."component didn't just escape (either a race condition happened or somebody's being shady, lowkey an attack). the caller gets to pick: retry theopenat2()call, it's giving second-chance energy ๐EAGAINโRESOLVE_CACHEDwas set, but nope, this open operation cannot be done using only cached info. caller should retry withRESOLVE_CACHEDnot set inhow.resolve. rip ๐EINVALโ an unknown flag or an invalid value got dropped intohow.EINVALโmodeis nonzero, buthow.flagsdoesn't containO_CREATorO_TMPFILE. the audacity ๐EINVALโsizewas smaller than any known version ofstruct open_how.ELOOPโhow.resolvecontainsRESOLVE_NO_SYMLINKS, and one of the path components was a symbolic link (or a magic link). scandalous โจELOOPโhow.resolvecontainsRESOLVE_NO_MAGICLINKS, and one of the path components was a magic link. double scandal ๐ฆEXDEVโhow.resolvecontains eitherRESOLVE_IN_ROOTorRESOLVE_BENEATH, and an escape from the root during path resolution was detected. caught in 4k ๐ธEXDEVโhow.resolvecontainsRESOLVE_NO_XDEV, and a path component crosses a mount point.EFTYPEโhow.flagscontains theOPENAT2_REGULARflag and the path is not a regular file. bestie it's simply not that girl ๐
๐ STANDARDS ๐
Linux. that's it. that's the standard. periodt ๐
๐ฐ๏ธ HISTORY ๐ฐ๏ธ
dropped in Linux 5.6 โ vintage, iconic, we love her ๐
the semantics of RESOLVE_BENEATH were modeled after FreeBSD's O_BENEATH, but โ and this is the tea โ โ Linux avoided a well-known correctness bug in FreeBSD's implementation that made it effectively insecure. later, FreeBSD 13 introduced O_RESOLVE_BENEATH to replace the insecure O_BENEATH. FreeBSD's O_RESOLVE_BENEATH semantics are based on Linux's RESOLVE_BENEATH and the two are now functionally equivalent. the glow-up is real ๐ฆโจ
๐ NOTES ๐
๐งฉ Extensibility ๐งฉ
so, to allow for future extensibility and keep things cute, openat2() makes the user-space application specify the size of the open_how structure it's passing in. by handing over that info, openat2() gets both forwards- and backwards-compatibility, with size acting like an implicit version number. (because new extension fields always get appended, the structure size will always increase โ it only goes up, it's giving growth mindset ๐ชด). this extensibility design is very similar to other system calls like sched_setattr(2), perf_event_open(2), and clone3(2).
okay so if we let usize be the size of the structure as specified by the user-space application, and ksize be the size of the structure that the kernel supports, there are like three cases to think about:
- if
ksizeequalsusize, then there's no version mismatch andhowcan be used verbatim. slay ๐ - if
ksizeis larger thanusize, then there are some extension fields the kernel supports that the user-space application doesn't even know about. because a zero value in any added extension field means no-op, the kernel treats every extension field the user-space app didn't provide as having zero values. this provides backwards-compatibility. - if
ksizeis smaller thanusize, then there are some extension fields the user-space application is aware of but the kernel does not support. because any extension field must have its zero values mean no-op, the kernel can safely ignore the unsupported extension fields if they're all-zero. but if any unsupported extension field is nonzero โ boom ๐ฅ โ-1is returned anderrnois set toE2BIG. this provides forwards-compatibility.
because the definition of struct open_how may change in the future (new fields getting added whenever system headers are updated), user-space applications should zero-fill struct open_how to make sure that recompiling the program with new headers won't result in spurious errors at run time. ๐ญ the simplest way is a designated initializer:
struct open_how how = { .flags = O_RDWR,
.resolve = RESOLVE_IN_ROOT };
or you can do it explicitly with memset(3) or similar:
struct open_how how;
memset(&how, 0, sizeof(how));
how.flags = O_RDWR;
how.resolve = RESOLVE_IN_ROOT;
and if a user-space application wants to find out which extensions the running kernel supports? so easy bestie โ do a binary search on size with a structure that has every byte nonzero (the goal is finding the largest value that doesn't produce an error of E2BIG). detective work, but make it โจalgorithmicโจ ๐
๐ SEE ALSO ๐
openat(2), open_how(2type), path_resolution(7), symlink(7)
go read those, they're all bangers, iykyk ๐ซถ
and that's a wrap on openat2(2) bestie!! this page came to you from Linux man-pages 6.20, dated 2026-07-02 โ go forth and resolve some paths safely, you absolute main character ๐๐๐
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: Linux-man-pages-copyleft
- Copyright 2019, Aleksa Sarai <cyphar@cyphar.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms