diff options
Diffstat (limited to 'src/stat.c')
-rw-r--r-- | src/stat.c | 368 |
1 files changed, 172 insertions, 196 deletions
@@ -1,52 +1,32 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes <tavianator@tavianator.com> * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "stat.h" +#include "atomic.h" #include "bfstd.h" -#include "config.h" -#include <assert.h> +#include "diag.h" +#include "sanity.h" #include <errno.h> #include <fcntl.h> -#include <stdbool.h> #include <string.h> -#include <sys/types.h> #include <sys/stat.h> +#include <sys/types.h> -#if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_LIBC_STATX true -#elif __linux__ -# include <linux/stat.h> -# include <sys/syscall.h> -# include <unistd.h> -#endif - -#if BFS_LIBC_STATX || defined(__NR_statx) -# define BFS_STATX true +#if BFS_USE_STATX && !BFS_HAS_STATX +# include <linux/stat.h> +# include <sys/syscall.h> +# include <unistd.h> #endif const char *bfs_stat_field_name(enum bfs_stat_field field) { switch (field) { + case BFS_STAT_MODE: + return "mode"; case BFS_STAT_DEV: return "device number"; case BFS_STAT_INO: return "inode nunmber"; - case BFS_STAT_TYPE: - return "type"; - case BFS_STAT_MODE: - return "mode"; case BFS_STAT_NLINK: return "link count"; case BFS_STAT_GID: @@ -71,60 +51,74 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "modification time"; } - assert(!"Unrecognized stat field"); + bfs_bug("Unrecognized stat field"); return "???"; } -/** - * Convert a struct stat to a struct bfs_stat. - */ -static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { - buf->mask = 0; +int bfs_fstatat_flags(enum bfs_stat_flags flags) { + int ret = 0; - buf->dev = statbuf->st_dev; - buf->mask |= BFS_STAT_DEV; + if (flags & BFS_STAT_NOFOLLOW) { + ret |= AT_SYMLINK_NOFOLLOW; + } + +#ifdef AT_NO_AUTOMOUNT + ret |= AT_NO_AUTOMOUNT; +#endif + + return ret; +} + +void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { + dest->mask = 0; + + dest->mode = src->st_mode; + dest->mask |= BFS_STAT_MODE; - buf->ino = statbuf->st_ino; - buf->mask |= BFS_STAT_INO; + dest->dev = src->st_dev; + dest->mask |= BFS_STAT_DEV; - buf->mode = statbuf->st_mode; - buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; + dest->ino = src->st_ino; + dest->mask |= BFS_STAT_INO; - buf->nlink = statbuf->st_nlink; - buf->mask |= BFS_STAT_NLINK; + dest->nlink = src->st_nlink; + dest->mask |= BFS_STAT_NLINK; - buf->gid = statbuf->st_gid; - buf->mask |= BFS_STAT_GID; + dest->gid = src->st_gid; + dest->mask |= BFS_STAT_GID; - buf->uid = statbuf->st_uid; - buf->mask |= BFS_STAT_UID; + dest->uid = src->st_uid; + dest->mask |= BFS_STAT_UID; - buf->size = statbuf->st_size; - buf->mask |= BFS_STAT_SIZE; + dest->size = src->st_size; + dest->mask |= BFS_STAT_SIZE; - buf->blocks = statbuf->st_blocks; - buf->mask |= BFS_STAT_BLOCKS; + dest->blocks = src->st_blocks; + dest->mask |= BFS_STAT_BLOCKS; - buf->rdev = statbuf->st_rdev; - buf->mask |= BFS_STAT_RDEV; + dest->rdev = src->st_rdev; + dest->mask |= BFS_STAT_RDEV; -#if BSD - buf->attrs = statbuf->st_flags; - buf->mask |= BFS_STAT_ATTRS; +#if BFS_HAS_ST_FLAGS + dest->attrs = src->st_flags; + dest->mask |= BFS_STAT_ATTRS; #endif - buf->atime = statbuf->st_atim; - buf->mask |= BFS_STAT_ATIME; + dest->atime = ST_ATIM(*src); + dest->mask |= BFS_STAT_ATIME; - buf->ctime = statbuf->st_ctim; - buf->mask |= BFS_STAT_CTIME; + dest->ctime = ST_CTIM(*src); + dest->mask |= BFS_STAT_CTIME; - buf->mtime = statbuf->st_mtim; - buf->mask |= BFS_STAT_MTIME; + dest->mtime = ST_MTIM(*src); + dest->mask |= BFS_STAT_MTIME; -#if __APPLE__ || __FreeBSD__ || __NetBSD__ - buf->btime = statbuf->st_birthtim; - buf->mask |= BFS_STAT_BTIME; +#if BFS_HAS_ST_BIRTHTIM + dest->btime = src->st_birthtim; + dest->mask |= BFS_STAT_BTIME; +#elif BFS_HAS_ST_BIRTHTIMESPEC + dest->btime = src->st_birthtimespec; + dest->mask |= BFS_STAT_BTIME; #endif } @@ -135,143 +129,141 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf struct stat statbuf; int ret = fstatat(at_fd, at_path, &statbuf, at_flags); if (ret == 0) { - bfs_stat_convert(&statbuf, buf); + bfs_stat_convert(buf, &statbuf); } return ret; } -#if BFS_STATX +#if BFS_USE_STATX /** * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { -#if __has_feature(memory_sanitizer) - // -fsanitize=memory doesn't know about statx(), so tell it the memory - // got initialized - memset(buf, 0, sizeof(*buf)); -#endif - -#if BFS_LIBC_STATX - return statx(at_fd, at_path, at_flags, mask, buf); +#if BFS_HAS_STATX + int ret = statx(at_fd, at_path, at_flags, mask, buf); #else - return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); + int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); #endif + + if (ret == 0) { + // -fsanitize=memory doesn't know about statx() + sanitize_init(buf); + } + + return ret; } -/** - * bfs_stat() implementation backed by statx(). - */ -static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { - unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; - struct statx xbuf; - int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); - if (ret != 0) { - return ret; +int bfs_statx_flags(enum bfs_stat_flags flags) { + int ret = bfs_fstatat_flags(flags); + + if (flags & BFS_STAT_NOSYNC) { + ret |= AT_STATX_DONT_SYNC; } + return ret; +} + +int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { // Callers shouldn't have to check anything except the times - const unsigned int guaranteed = STATX_BASIC_STATS ^ (STATX_ATIME | STATX_CTIME | STATX_MTIME); - if ((xbuf.stx_mask & guaranteed) != guaranteed) { + const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME); + if ((src->stx_mask & guaranteed) != guaranteed) { errno = ENOTSUP; return -1; } - buf->mask = 0; + dest->mask = 0; - buf->dev = xmakedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); - buf->mask |= BFS_STAT_DEV; + dest->mode = src->stx_mode; + dest->mask |= BFS_STAT_MODE; - if (xbuf.stx_mask & STATX_INO) { - buf->ino = xbuf.stx_ino; - buf->mask |= BFS_STAT_INO; - } + dest->dev = xmakedev(src->stx_dev_major, src->stx_dev_minor); + dest->mask |= BFS_STAT_DEV; - buf->mode = xbuf.stx_mode; - if (xbuf.stx_mask & STATX_TYPE) { - buf->mask |= BFS_STAT_TYPE; - } - if (xbuf.stx_mask & STATX_MODE) { - buf->mask |= BFS_STAT_MODE; - } + dest->ino = src->stx_ino; + dest->mask |= BFS_STAT_INO; - if (xbuf.stx_mask & STATX_NLINK) { - buf->nlink = xbuf.stx_nlink; - buf->mask |= BFS_STAT_NLINK; - } + dest->nlink = src->stx_nlink; + dest->mask |= BFS_STAT_NLINK; - if (xbuf.stx_mask & STATX_GID) { - buf->gid = xbuf.stx_gid; - buf->mask |= BFS_STAT_GID; - } + dest->gid = src->stx_gid; + dest->mask |= BFS_STAT_GID; - if (xbuf.stx_mask & STATX_UID) { - buf->uid = xbuf.stx_uid; - buf->mask |= BFS_STAT_UID; - } + dest->uid = src->stx_uid; + dest->mask |= BFS_STAT_UID; - if (xbuf.stx_mask & STATX_SIZE) { - buf->size = xbuf.stx_size; - buf->mask |= BFS_STAT_SIZE; - } + dest->size = src->stx_size; + dest->mask |= BFS_STAT_SIZE; - if (xbuf.stx_mask & STATX_BLOCKS) { - buf->blocks = xbuf.stx_blocks; - buf->mask |= BFS_STAT_BLOCKS; - } + dest->blocks = src->stx_blocks; + dest->mask |= BFS_STAT_BLOCKS; - buf->rdev = xmakedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); - buf->mask |= BFS_STAT_RDEV; + dest->rdev = xmakedev(src->stx_rdev_major, src->stx_rdev_minor); + dest->mask |= BFS_STAT_RDEV; - buf->attrs = xbuf.stx_attributes; - buf->mask |= BFS_STAT_ATTRS; + dest->attrs = src->stx_attributes; + dest->mask |= BFS_STAT_ATTRS; - if (xbuf.stx_mask & STATX_ATIME) { - buf->atime.tv_sec = xbuf.stx_atime.tv_sec; - buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec; - buf->mask |= BFS_STAT_ATIME; + if (src->stx_mask & STATX_ATIME) { + dest->atime.tv_sec = src->stx_atime.tv_sec; + dest->atime.tv_nsec = src->stx_atime.tv_nsec; + dest->mask |= BFS_STAT_ATIME; } - if (xbuf.stx_mask & STATX_BTIME) { - buf->btime.tv_sec = xbuf.stx_btime.tv_sec; - buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec; - buf->mask |= BFS_STAT_BTIME; + if (src->stx_mask & STATX_BTIME) { + dest->btime.tv_sec = src->stx_btime.tv_sec; + dest->btime.tv_nsec = src->stx_btime.tv_nsec; + dest->mask |= BFS_STAT_BTIME; } - if (xbuf.stx_mask & STATX_CTIME) { - buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec; - buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec; - buf->mask |= BFS_STAT_CTIME; + if (src->stx_mask & STATX_CTIME) { + dest->ctime.tv_sec = src->stx_ctime.tv_sec; + dest->ctime.tv_nsec = src->stx_ctime.tv_nsec; + dest->mask |= BFS_STAT_CTIME; } - if (xbuf.stx_mask & STATX_MTIME) { - buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec; - buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec; - buf->mask |= BFS_STAT_MTIME; + if (src->stx_mask & STATX_MTIME) { + dest->mtime.tv_sec = src->stx_mtime.tv_sec; + dest->mtime.tv_nsec = src->stx_mtime.tv_nsec; + dest->mask |= BFS_STAT_MTIME; } - return ret; + return 0; +} + +/** + * bfs_stat() implementation backed by statx(). + */ +static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + struct statx xbuf; + int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); + if (ret != 0) { + return ret; + } + + return bfs_statx_convert(buf, &xbuf); } -#endif // BFS_STATX +#endif // BFS_USE_STATX /** * Calls the stat() implementation with explicit flags. */ -static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) { -#if BFS_STATX - static bool has_statx = true; - - if (has_statx) { - int ret = bfs_statx_impl(at_fd, at_path, at_flags | x_flags, buf); - // EPERM is commonly returned in a seccomp() sandbox that does - // not allow statx() - if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { - has_statx = false; +static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { +#if BFS_USE_STATX + static atomic bool has_statx = true; + + if (load(&has_statx, relaxed)) { + int ret = bfs_statx_impl(at_fd, at_path, at_flags, buf); + if (ret != 0 && errno_is_like(ENOSYS)) { + store(&has_statx, false, relaxed); } else { return ret; } } + + at_flags &= ~AT_STATX_DONT_SYNC; #endif return bfs_stat_impl(at_fd, at_path, at_flags, buf); @@ -280,62 +272,46 @@ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x /** * Implements the BFS_STAT_TRYFOLLOW retry logic. */ -static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, int x_flags, enum bfs_stat_flags bfs_flags, struct bfs_stat *buf) { - int ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); +static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flags bfs_flags, struct bfs_stat *buf) { + int ret = bfs_stat_explicit(at_fd, at_path, at_flags, buf); if (ret != 0 && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW - && is_nonexistence_error(errno)) + && errno_is_like(ENOENT)) { at_flags |= AT_SYMLINK_NOFOLLOW; - ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); + ret = bfs_stat_explicit(at_fd, at_path, at_flags, buf); } return ret; } int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf) { - int at_flags = 0; - if (flags & BFS_STAT_NOFOLLOW) { - at_flags |= AT_SYMLINK_NOFOLLOW; - } - -#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35)) - at_flags |= AT_NO_AUTOMOUNT; -#endif - - int x_flags = 0; -#ifdef AT_STATX_DONT_SYNC - if (flags & BFS_STAT_NOSYNC) { - x_flags |= AT_STATX_DONT_SYNC; - } +#if BFS_USE_STATX + int at_flags = bfs_statx_flags(flags); +#else + int at_flags = bfs_fstatat_flags(flags); #endif if (at_path) { - return bfs_stat_tryfollow(at_fd, at_path, at_flags, x_flags, flags, buf); + return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf); } - // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html -#if defined(AT_EMPTY_PATH) && !__GNU__ - static bool has_at_ep = true; - if (has_at_ep) { - at_flags |= AT_EMPTY_PATH; - int ret = bfs_stat_explicit(at_fd, "", at_flags, x_flags, buf); - if (ret != 0 && errno == EINVAL) { - has_at_ep = false; - } else { - return ret; - } - } -#endif - - struct stat statbuf; - if (fstat(at_fd, &statbuf) == 0) { - bfs_stat_convert(&statbuf, buf); - return 0; - } else { +#if BFS_USE_STATX + // If we have statx(), use it with AT_EMPTY_PATH for its extra features + at_flags |= AT_EMPTY_PATH; + return bfs_stat_explicit(at_fd, "", at_flags, buf); +#else + // Otherwise, just use fstat() rather than fstatat(at_fd, ""), to save + // the kernel the trouble of copying in the empty string + struct stat sb; + if (fstat(at_fd, &sb) != 0) { return -1; } + + bfs_stat_convert(buf, &sb); + return 0; +#endif } const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) { @@ -354,7 +330,7 @@ const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_f case BFS_STAT_MTIME: return &buf->mtime; default: - assert(!"Invalid stat field for time"); + bfs_bug("Invalid stat field for time"); errno = EINVAL; return NULL; } |