diff options
Diffstat (limited to 'src/stat.c')
-rw-r--r-- | src/stat.c | 325 |
1 files changed, 180 insertions, 145 deletions
@@ -2,41 +2,33 @@ // SPDX-License-Identifier: 0BSD #include "stat.h" + #include "atomic.h" +#include "bfs.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" + #include <errno.h> #include <fcntl.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_HAS_LIBC_STATX true -#elif __linux__ +#if BFS_USE_STATX && !BFS_HAS_STATX # include <linux/stat.h> # include <sys/syscall.h> # include <unistd.h> #endif -#ifndef BFS_USE_STATX -# if BFS_HAS_LIBC_STATX || defined(SYS_statx) -# define BFS_USE_STATX true -# endif -#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: @@ -59,62 +51,85 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "change time"; case BFS_STAT_MTIME: return "modification time"; + case BFS_STAT_MNT_ID: + return "mount ID"; } - bfs_bug("Unrecognized stat field"); + bfs_bug("Unrecognized stat field %d", (int)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; + + if (flags & BFS_STAT_NOFOLLOW) { + ret |= AT_SYMLINK_NOFOLLOW; + } + +#ifdef AT_NO_AUTOMOUNT + ret |= AT_NO_AUTOMOUNT; +#endif + + return ret; +} - buf->dev = statbuf->st_dev; - buf->mask |= BFS_STAT_DEV; +void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { + dest->mask = 0; - buf->ino = statbuf->st_ino; - buf->mask |= BFS_STAT_INO; + dest->mode = src->st_mode; + dest->mask |= BFS_STAT_MODE; - buf->mode = statbuf->st_mode; - buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; + dest->dev = src->st_dev; + dest->mask |= BFS_STAT_DEV; - buf->nlink = statbuf->st_nlink; - buf->mask |= BFS_STAT_NLINK; + dest->ino = src->st_ino; + dest->mask |= BFS_STAT_INO; - buf->gid = statbuf->st_gid; - buf->mask |= BFS_STAT_GID; + dest->nlink = src->st_nlink; + dest->mask |= BFS_STAT_NLINK; - buf->uid = statbuf->st_uid; - buf->mask |= BFS_STAT_UID; + dest->gid = src->st_gid; + dest->mask |= BFS_STAT_GID; - buf->size = statbuf->st_size; - buf->mask |= BFS_STAT_SIZE; + dest->uid = src->st_uid; + dest->mask |= BFS_STAT_UID; - buf->blocks = statbuf->st_blocks; - buf->mask |= BFS_STAT_BLOCKS; + dest->size = src->st_size; + dest->mask |= BFS_STAT_SIZE; - buf->rdev = statbuf->st_rdev; - buf->mask |= BFS_STAT_RDEV; + dest->blocks = src->st_blocks; + dest->mask |= BFS_STAT_BLOCKS; -#if BSD - buf->attrs = statbuf->st_flags; - buf->mask |= BFS_STAT_ATTRS; + dest->rdev = src->st_rdev; + dest->mask |= BFS_STAT_RDEV; + + // No mount IDs in regular stat(), so use the dev_t as an approximation + dest->mnt_id = dest->dev; + dest->mask |= BFS_STAT_MNT_ID; + +#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_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 } @@ -125,7 +140,7 @@ 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; } @@ -136,7 +151,7 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf * 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 BFS_HAS_LIBC_STATX +#if BFS_HAS_STATX int ret = statx(at_fd, at_path, at_flags, mask, buf); #else int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); @@ -150,82 +165,118 @@ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int 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; +} + +unsigned int bfs_statx_mask(void) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; +#ifdef STATX_MNT_ID + mask |= STATX_MNT_ID; +#endif +#ifdef STATX_MNT_ID_UNIQUE + mask |= STATX_MNT_ID_UNIQUE; +#endif + return mask; +} + +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) { + 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; - 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; - buf->mask |= BFS_STAT_TYPE; - buf->mask |= BFS_STAT_MODE; + dest->ino = src->stx_ino; + dest->mask |= BFS_STAT_INO; - buf->nlink = xbuf.stx_nlink; - buf->mask |= BFS_STAT_NLINK; + dest->nlink = src->stx_nlink; + dest->mask |= BFS_STAT_NLINK; - buf->gid = xbuf.stx_gid; - buf->mask |= BFS_STAT_GID; + dest->gid = src->stx_gid; + dest->mask |= BFS_STAT_GID; - buf->uid = xbuf.stx_uid; - buf->mask |= BFS_STAT_UID; + dest->uid = src->stx_uid; + dest->mask |= BFS_STAT_UID; - buf->size = xbuf.stx_size; - buf->mask |= BFS_STAT_SIZE; + dest->size = src->stx_size; + dest->mask |= BFS_STAT_SIZE; - 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; + dest->mnt_id = dest->dev; +#ifdef STATX_MNT_ID + unsigned int mnt_mask = STATX_MNT_ID; +# ifdef STATX_MNT_ID_UNIQUE + mnt_mask |= STATX_MNT_ID_UNIQUE; +# endif + if (src->stx_mask & mnt_mask) { + dest->mnt_id = src->stx_mnt_id; } +#endif + dest->mask |= BFS_STAT_MNT_ID; - 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_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_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_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_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_CTIME) { + dest->ctime.tv_sec = src->stx_ctime.tv_sec; + dest->ctime.tv_nsec = src->stx_ctime.tv_nsec; + dest->mask |= BFS_STAT_CTIME; } - return ret; + 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 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 = bfs_statx_mask(); + 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_USE_STATX @@ -233,20 +284,20 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b /** * 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) { +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 | x_flags, buf); - // EPERM is commonly returned in a seccomp() sandbox that does - // not allow statx() - if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { + 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); @@ -255,62 +306,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); - } - - // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html -#if defined(AT_EMPTY_PATH) && !__GNU__ - static atomic bool has_at_ep = true; - if (load(&has_at_ep, relaxed)) { - at_flags |= AT_EMPTY_PATH; - int ret = bfs_stat_explicit(at_fd, "", at_flags, x_flags, buf); - if (ret != 0 && errno == EINVAL) { - store(&has_at_ep, false, relaxed); - } else { - return ret; - } + return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf); } -#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) { |