| /* Open a file, with more flags than openat | |
| Copyright 2025-2026 Free Software Foundation, Inc. | |
| This file is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of the | |
| License, or (at your option) any later version. | |
| This file is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| /* written by Paul Eggert */ | |
| #include <config.h> | |
| #include <fcntl.h> | |
| #include "eloop-threshold.h" | |
| #include "filename.h" | |
| #include "ialloc.h" | |
| #include "idx.h" | |
| #include "verify.h" | |
| #include <errno.h> | |
| #include <limits.h> | |
| #include <stdckdint.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #if defined __linux__ || defined __ANDROID__ | |
| # include <sys/syscall.h> | |
| # include <linux/version.h> | |
| # if HAVE_SYS_VFS_H && HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE | |
| # include <sys/vfs.h> | |
| /* Linux-specific constant from coreutils src/fs.h. */ | |
| # define S_MAGIC_PROC 0x9FA0 | |
| # endif | |
| #endif | |
| /* FSTAT_O_PATH_BUG is true if fstat fails on O_PATH file descriptors. | |
| Although it can be dicey to use static checks for Linux kernel versions, | |
| due to the dubious practice of building on newer kernels for older ones, | |
| do it here anyway as the buggy kernels are rare (all EOLed by 2016) | |
| and builders for them are unlikely to use the dubious practice. | |
| Circa 2030 we should remove the old-kernel workarounds entirely. */ | |
| #ifdef LINUX_VERSION_CODE | |
| # define FSTAT_O_PATH_BUG (KERNEL_VERSION (2, 6, 39) <= LINUX_VERSION_CODE \ | |
| && LINUX_VERSION_CODE < KERNEL_VERSION (3, 6, 0)) | |
| #else | |
| # define FSTAT_O_PATH_BUG false | |
| #endif | |
| #ifndef E2BIG | |
| # define E2BIG EINVAL | |
| #endif | |
| #ifndef ENOTCAPABLE /* A FreeBSD error number. */ | |
| # define ENOTCAPABLE 0 | |
| #endif | |
| #ifndef PATH_MAX | |
| # define PATH_MAX IDX_MAX | |
| #endif | |
| #ifndef O_ASYNC | |
| # define O_ASYNC 0 | |
| #endif | |
| #ifndef O_CLOFORK | |
| # define O_CLOFORK 0 | |
| #endif | |
| #ifndef O_LARGEFILE | |
| # define O_LARGEFILE 0 | |
| #endif | |
| #ifndef O_NOCLOBBER | |
| # define O_NOCLOBBER 0 | |
| #endif | |
| #ifndef O_PATH | |
| # define O_PATH 0 | |
| #endif | |
| #ifndef O_RESOLVE_BENEATH /* A FreeBSD flag. */ | |
| # define O_RESOLVE_BENEATH 0 | |
| #endif | |
| #ifndef O_TMPFILE | |
| # define O_TMPFILE 0 | |
| #endif | |
| #if O_PATH | |
| enum { O_PATHSEARCH = O_PATH }; | |
| #else | |
| enum { O_PATHSEARCH = O_SEARCH }; | |
| #endif | |
| /* Return true if the memory region at S of size N contains only zeros. */ | |
| static bool | |
| memiszero (void const *s, idx_t n) | |
| { | |
| /* Keep it simple, as N is typically zero. */ | |
| char const *p = s; | |
| for (idx_t i = 0; i < n; i++) | |
| if (p[i]) | |
| return false; | |
| return true; | |
| } | |
| /* Return the negative of errno, helping the compiler about its sign. */ | |
| static int | |
| negative_errno (void) | |
| { | |
| int err = -errno; | |
| assume (err < 0); | |
| return err; | |
| } | |
| /* Make *BUF, which is of size BUFSIZE and which is heap-allocated | |
| if not equal to STACKBUF, large enough to hold an object of NGROW + NTAIL. | |
| Keep the last NTAIL bytes of *BUF; the rest of *BUF becomes uninitialized. | |
| NTAIL must not exceed BUFSIZE. | |
| Return the resulting buffer size, or a negative errno value | |
| if the buffer could not be grown. */ | |
| static ptrdiff_t | |
| maybe_grow (char **buf, idx_t bufsize, char *stackbuf, | |
| idx_t ngrow, idx_t ntail) | |
| { | |
| if (ngrow <= bufsize - ntail) | |
| return bufsize; | |
| idx_t needed; | |
| if (ckd_add (&needed, ngrow, ntail)) | |
| return -ENOMEM; | |
| idx_t s = ckd_add (&s, needed, needed >> 1) ? needed : s; | |
| char *newbuf = imalloc (s); | |
| if (!newbuf) | |
| return negative_errno (); | |
| char *oldbuf = *buf; | |
| memcpy (newbuf + s - ntail, oldbuf + bufsize - ntail, ntail); | |
| if (oldbuf != stackbuf) | |
| free (oldbuf); | |
| *buf = newbuf; | |
| return s; | |
| } | |
| /* Store DIRFD's file status into *ST. | |
| DIRFD is either AT_FDCWD or a nonnegative file descriptor. | |
| Return 0 on success, -1 (setting errno) on failure. */ | |
| static int | |
| dirstat (int dirfd, struct stat *st) | |
| { | |
| /* Use fstatat only if fstat is buggy. fstatat is a bit slower, | |
| and using it only on buggy hosts means openat2 need not depend on | |
| Gnulib's fstatat module, as all systems with the fstat bug have | |
| an fstatat that works well enough. */ | |
| #if FSTAT_O_PATH_BUG | |
| return fstatat (dirfd, ".", st); | |
| #else | |
| return dirfd < 0 ? stat (".", st) : fstat (dirfd, st); | |
| #endif | |
| } | |
| /* Like openat2 (*FD, FILENAME, h, sizeof h) where h is | |
| (struct open_how) { .flags = FLAGS, .resolve = RESOLVE, .mode = MODE }, | |
| except trust h's contents, advance *FD as we go, | |
| use and update *BUF (originally pointing to a buffer of size BUFSIZE, | |
| though it may be changed to point to a freshly allocated heap buffer), | |
| and return the negative of the errno value on failure. | |
| *FD and *BUF can be updated even on failure. | |
| BUFSIZE must be at least 2. */ | |
| static int | |
| do_openat2 (int *fd, char const *filename, | |
| int flags, char resolve, mode_t mode, | |
| char **buf, idx_t bufsize) | |
| { | |
| /* RESOLVED_CACHED cannot be implemented properly in user space, | |
| so pretend nothing is cached. */ | |
| if (resolve & RESOLVE_CACHED) | |
| return -EAGAIN; | |
| /* Put the file name being processed (including trailing NUL) at buffer end, | |
| to simplify symlink resolution. */ | |
| idx_t filenamelen = strlen (filename); | |
| if (!filenamelen) | |
| return -ENOENT; | |
| idx_t filenamesize = filenamelen + 1; | |
| if (PATH_MAX < filenamesize) | |
| return -ENAMETOOLONG; | |
| char *stackbuf = *buf; | |
| bufsize = maybe_grow (buf, bufsize, stackbuf, filenamesize, 0); | |
| if (bufsize < 0) | |
| return bufsize; | |
| /* Pointer to buffer end. A common access is E[-I] where I is a | |
| negative index relative to buffer end. */ | |
| char *e = *buf + bufsize; | |
| memcpy (&e[-filenamesize], filename, filenamesize); | |
| /* Directory depth below DFD. This is -1 if ".." ascended above | |
| DFD at any point in the past, which can happen only if | |
| neither RESOLVE_BENEATH nor RESOLVE_IN_ROOT is in effect. */ | |
| ptrdiff_t depth = 0; | |
| /* DFD's device. UNKNOWN_DDEV if not acquired yet. If the actual | |
| device number equals UNKNOWN_DDEV the code still works, | |
| albeit more slowly. */ | |
| dev_t const UNKNOWN_DEV = -1; | |
| dev_t ddev = UNKNOWN_DEV; | |
| long int maxlinks = resolve & RESOLVE_NO_SYMLINKS ? 0 : __eloop_threshold (); | |
| int dfd = *fd; | |
| /* Iterates through file name components, possibly expanded by | |
| symlink contents. */ | |
| while (true) | |
| { | |
| /* Make progress in interpreting &E[-FILENAMESIZE] as a file name. | |
| If relative, it is relative to *FD. | |
| FILENAMESIZE is positive. | |
| Start by computing sizes of strings at the file name's end. | |
| Use negations of sizes to index into E. | |
| Here is an example file name and sizes of the trailing strings: | |
| ///usr//bin/.////cat | |
| F G H 1 | |
| As the '1' indicates, all sizes are positive | |
| and include the trailing NUL at E[-1]. | |
| If there are file name components (the typical case), | |
| -F <= -G < -H <= -1 and the first component | |
| starts at E[-G] and ends just before E[-H]. | |
| Otherwise if the file name is nonempty, | |
| -F < -G = -H = -1 and &E[-F] is a file system root. | |
| Otherwise it is Solaris and we resolved an empty final symlink, and | |
| -F = -G = -H = -1 and the empty file name is equivalent to ".". | |
| F = G means the file name is relative to *FD; | |
| otherwise the file name is not relative. | |
| F (i.e., FILENAMESIZE) is the size of the file name. &E[-F] is what | |
| is typically passed next to openat (with E[-H] set to NUL). | |
| G is the size of the file name's suffix that starts with the name's | |
| first component; &E[-G] addresses the first component. | |
| H is the size of the suffix after the first component, i.e., | |
| E[-H] is the slash or NUL after the first component. | |
| If there is no component, G and H are both 1. */ | |
| idx_t f = filenamesize; | |
| idx_t g = f - FILE_SYSTEM_PREFIX_LEN (&e[-f]); | |
| while (ISSLASH (e[-g])) | |
| g--; | |
| if (f != g) | |
| { | |
| /* The file name is not relative. */ | |
| if (resolve & RESOLVE_BENEATH) | |
| return -EXDEV; | |
| if (resolve & RESOLVE_IN_ROOT) | |
| f = g; | |
| if (*fd != dfd) | |
| { | |
| /* A non-relative symlink had been resolved at positive depth. | |
| Forget its parent directory. */ | |
| close (*fd); | |
| *fd = dfd; | |
| } | |
| } | |
| idx_t h = g; | |
| while (h > 1) | |
| { | |
| h--; | |
| if (ISSLASH (e[-h])) | |
| break; | |
| } | |
| /* Properties of the file name through the first component's end, | |
| or to file name end if there is no component. */ | |
| bool leading_dot = e[-f] == '.'; | |
| bool dot_or_empty = f - h == leading_dot; | |
| bool dotdot = leading_dot & (f - h == 2) & (e[-h - 1] == '.'); | |
| bool dotdot_as_dot = dotdot & !depth & !!(resolve & RESOLVE_IN_ROOT); | |
| bool dotlike = dot_or_empty | dotdot_as_dot; | |
| if (dotdot & !depth & !!(resolve & RESOLVE_BENEATH)) | |
| return -EXDEV; | |
| /* NEXTF is the value of FILENAMESIZE next time through the loop, | |
| unless a symlink intervenes. */ | |
| idx_t nextf = h; | |
| while (ISSLASH (e[-nextf])) | |
| nextf--; | |
| /* FINAL means this is the last time through the loop, | |
| unless a symlink intervenes. */ | |
| bool final = nextf == 1; | |
| if (!final & dotlike) | |
| { | |
| /* A non-final component that acts like "."; skip it. */ | |
| filenamesize = nextf; | |
| } | |
| else if (!final & dotdot & (depth == 1)) | |
| { | |
| /* A non-final ".." in a name like "x/../y/z" when "x" is an | |
| existing non-symlink directory. As an optimization, | |
| resolve it like "y/z". */ | |
| close (*fd); | |
| *fd = dfd; | |
| depth = 0; | |
| filenamesize = nextf; | |
| } | |
| else | |
| { | |
| if (dotlike) | |
| { | |
| /* This is empty or the last component, and acts like ".". | |
| Use "." regardless of whether it was "" or "." or "..". */ | |
| f = sizeof "."; | |
| e[-f] = '.'; | |
| } | |
| /* Open the current component, as either an internal directory or | |
| the final open. Do not follow symlinks. */ | |
| char eh = e[-h]; | |
| int subflags = ((!final | |
| ? O_PATHSEARCH | O_CLOEXEC | O_CLOFORK | |
| : flags) | |
| | O_NOFOLLOW | (eh ? O_DIRECTORY : 0)); | |
| e[-h] = '\0'; | |
| int subfd = openat (*fd, &e[-f], subflags, mode); | |
| if (subfd < 0) | |
| { | |
| int openerr = negative_errno (); | |
| if (! ((openerr == -_GL_OPENAT_ESYMLINK) | |
| | (!!(subflags & O_DIRECTORY) & (openerr == -ENOTDIR)))) | |
| return openerr; | |
| /* Likely a symlink. | |
| Try to read symlink contents into buffer start. | |
| But if the root prefix might be needed, | |
| leave room for it at buffer start. */ | |
| idx_t rootlen = f - g; | |
| char *slink; | |
| ssize_t slinklen; | |
| for (idx_t more = rootlen + 1; ; more = bufsize - f + 1) | |
| { | |
| bufsize = maybe_grow (buf, bufsize, stackbuf, more, f); | |
| if (bufsize < 0) | |
| return bufsize; | |
| e = *buf + bufsize; | |
| slink = *buf + rootlen; | |
| idx_t slinksize = bufsize - f - rootlen; | |
| slinklen = readlinkat (*fd, &e[-f], slink, slinksize); | |
| if (slinklen < 0) | |
| { | |
| int readlinkerr = negative_errno (); | |
| return readlinkerr == -EINVAL ? -ENOTDIR : readlinkerr; | |
| } | |
| if (slinklen < slinksize) | |
| break; | |
| } | |
| if (maxlinks <= 0) | |
| return -_GL_OPENAT_ESYMLINK; | |
| maxlinks--; | |
| /* A symlink and the symlink loop count is not exhausted. | |
| Fail now if magic and if RESOLVE_NO_MAGIC_LINKS. */ | |
| #ifdef S_MAGIC_PROC | |
| if (resolve & RESOLVE_NO_MAGICLINKS) | |
| { | |
| bool relative = IS_RELATIVE_FILE_NAME (&e[-f]); | |
| struct statfs st; | |
| int r; | |
| if (relative) | |
| r = *fd < 0 ? statfs (".", &st) : fstatfs (*fd, &st); | |
| else | |
| { | |
| char eg = e[-g]; | |
| e[-g] = '\0'; | |
| r = statfs (&e[-f], &st); | |
| e[-g] = eg; | |
| } | |
| if (r < 0) | |
| return negative_errno (); | |
| if (st.f_type == S_MAGIC_PROC) | |
| return -_GL_OPENAT_ESYMLINK; | |
| } | |
| #endif | |
| /* Compute KEPT, the number of trailing bytes in the file | |
| name that will be appended to the symlink contents. */ | |
| idx_t kept; | |
| if (slinklen == 0) | |
| { | |
| /* On Solaris empty symlinks act like ".". | |
| On other platforms that allow them, | |
| they fail with ENOENT. */ | |
| #ifdef __sun | |
| slink[slinklen] = '\0'; /* For IS_RELATIVE_FILE_NAME. */ | |
| kept = nextf; | |
| #else | |
| return -ENOENT; | |
| #endif | |
| } | |
| else if (ISSLASH (slink[slinklen - 1])) | |
| { | |
| /* Skip any leading slashes in the kept bytes. | |
| This can matter if the symlink contains only slashes, | |
| because "//" and "/" can be distinct directories. */ | |
| kept = nextf; | |
| } | |
| else | |
| { | |
| e[-h] = eh; | |
| kept = h; | |
| } | |
| if (ISSLASH ('\\')) | |
| slink[slinklen] = '\0'; /* For IS_RELATIVE_FILE_NAME. */ | |
| /* Compute the new file name by concatenating: | |
| - Any old root prefix if the symlink contents are relative. | |
| - The symlink contents. | |
| - The last KEPT bytes of the old file name. | |
| The KEPT part is already in place. */ | |
| char const *prefix; /* [old root prefix +] symlink contents */ | |
| idx_t prefixlen; | |
| if (IS_RELATIVE_FILE_NAME (slink)) | |
| { | |
| prefix = memmove (*buf, &e[-f], rootlen); | |
| prefixlen = rootlen + slinklen; | |
| } | |
| else | |
| { | |
| if (*fd != dfd) | |
| { | |
| close (*fd); | |
| *fd = dfd; | |
| } | |
| prefix = slink; | |
| prefixlen = slinklen; | |
| } | |
| filenamesize = prefixlen + kept; | |
| if (PATH_MAX < filenamesize) | |
| return -ENAMETOOLONG; | |
| memmove (&e[-filenamesize], prefix, prefixlen); | |
| } | |
| else | |
| { | |
| if (*fd != dfd) | |
| close (*fd); | |
| *fd = subfd; | |
| /* SUBFD is open to the file named by the current component. | |
| If requested, require it to be in the same file system. */ | |
| if (resolve & RESOLVE_NO_XDEV) | |
| { | |
| struct stat st; | |
| if (ddev == UNKNOWN_DEV) | |
| { | |
| if (dirstat (dfd, &st) < 0) | |
| return negative_errno (); | |
| ddev = st.st_dev; | |
| } | |
| if (dirstat (subfd, &st) < 0) | |
| return negative_errno (); | |
| if (st.st_dev != ddev) | |
| return -EXDEV; | |
| } | |
| if (final) | |
| { | |
| *fd = dfd; | |
| return subfd; | |
| } | |
| /* The component cannot be dotlike here, so if the depth is | |
| nonnegative adjust it by +1 or -1. */ | |
| if (0 <= depth) | |
| depth += dotdot ? -1 : 1; | |
| filenamesize = nextf; | |
| } | |
| } | |
| } | |
| } | |
| /* Like openat (DFD, FILENAME, HOW->flags, HOW->mode), | |
| but with extra flags in *HOW, which is of size HOWSIZE. */ | |
| int | |
| openat2 (int dfd, char const *filename, | |
| struct open_how const *how, size_t howsize) | |
| { | |
| #ifdef SYS_openat2 | |
| { | |
| int r = syscall (SYS_openat2, dfd, filename, how, howsize); | |
| if (! (r < 0 && errno == ENOSYS)) | |
| return r; | |
| } | |
| /* Keep going, to support the dubious practice of compiling for an | |
| older kernel. The openat2 syscall was introduced in Linux 5.6. | |
| Linux 5.4 LTS is EOLed at the end of 2025, so perhaps after that | |
| we can simply return the syscall result instead of continuing. */ | |
| #endif | |
| int r; | |
| /* Check for invalid arguments. Once the size test has succeeded, | |
| *HOW's members are safe to access, so use & and | as there is | |
| little point to using && or || when invalid arguments are rare. | |
| (Other parts of this file also use & and | for similar reasons.) | |
| These checks mimic those of the Linux kernel: when the Linux | |
| kernel is overly generous, these checks are too. */ | |
| if (howsize < sizeof *how) | |
| r = -EINVAL; | |
| else if (!memiszero (how + 1, howsize - sizeof *how)) | |
| r = -E2BIG; | |
| else if ((how->flags | |
| & ~ (O_CLOFORK | O_CLOEXEC | O_DIRECTORY | |
| | O_NOFOLLOW | O_PATH | |
| | (how->flags & O_PATH | |
| ? 0 | |
| : (O_ACCMODE | O_APPEND | O_ASYNC | O_BINARY | |
| | O_CREAT | O_DIRECT | O_DSYNC | O_EXCL | |
| | O_IGNORE_CTTY | O_LARGEFILE | O_NDELAY | |
| | O_NOATIME | O_NOCLOBBER | O_NOCTTY | |
| | O_NOLINK | O_NOLINKS | O_NONBLOCK | O_NOTRANS | |
| | O_RSYNC | O_SYNC | |
| | O_TEXT | O_TMPFILE | O_TRUNC | O_TTY_INIT)))) | |
| | (!!(how->flags & O_CREAT) | |
| & !!(how->flags & (O_DIRECTORY | O_TMPFILE))) | |
| | (!!(how->flags & O_TMPFILE & ~O_DIRECTORY) | |
| & ((how->flags & (O_ACCMODE | O_PATH)) != O_WRONLY) | |
| & ((how->flags & (O_ACCMODE | O_PATH)) != O_RDWR)) | |
| | (how->mode | |
| & ~ (how->flags & (O_CREAT | (O_TMPFILE & ~O_DIRECTORY)) | |
| ? (S_ISUID | S_ISGID | S_ISVTX | |
| | S_IRWXU | S_IRWXG | S_IRWXO) | |
| : 0)) | |
| | (how->resolve | |
| & ~ (RESOLVE_BENEATH | RESOLVE_CACHED | RESOLVE_IN_ROOT | |
| | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | |
| | RESOLVE_NO_XDEV)) | |
| | ((how->resolve & (RESOLVE_BENEATH | RESOLVE_IN_ROOT)) | |
| == (RESOLVE_BENEATH | RESOLVE_IN_ROOT))) | |
| r = -EINVAL; | |
| else | |
| { | |
| /* Args are valid so it is safe to use narrower types. */ | |
| int flags = how->flags; | |
| char resolve = how->resolve; | |
| mode_t mode = how->mode; | |
| /* For speed use a single openat if it suffices. */ | |
| if (O_RESOLVE_BENEATH ? !(resolve & ~RESOLVE_BENEATH) : !resolve) | |
| { | |
| int fd = openat (dfd, filename, | |
| flags | (resolve ? O_RESOLVE_BENEATH : 0), mode); | |
| /* Return FD now unless openat failed with an errno that might | |
| be an O_RESOLVE_BENEATH failure. On platforms with | |
| ENOTCAPABLE that is the errno; otherwise, this is macOS 15 | |
| where EACCES is the errno. */ | |
| if (! (fd < 0 && resolve | |
| && errno == (ENOTCAPABLE ? ENOTCAPABLE : EACCES))) | |
| return fd; | |
| } | |
| int fd = dfd; | |
| char stackbuf[256]; | |
| char *buf = stackbuf; | |
| r = do_openat2 (&fd, filename, flags, resolve, mode, | |
| &buf, sizeof stackbuf); | |
| if (fd != dfd) | |
| close (fd); | |
| if (buf != stackbuf) | |
| free (buf); | |
| } | |
| if (r < 0) | |
| { | |
| errno = -r; | |
| return -1; | |
| } | |
| return r; | |
| } |