1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
14 #include <sys/types.h>
16 #if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30)
17 # define BFS_HAS_LIBC_STATX true
19 # include <linux/stat.h>
20 # include <sys/syscall.h>
25 # if BFS_HAS_LIBC_STATX || defined(SYS_statx)
26 # define BFS_USE_STATX true
30 const char *bfs_stat_field_name(enum bfs_stat_field field) {
33 return "device number";
35 return "inode nunmber";
51 return "underlying device";
61 return "modification time";
64 bfs_bug("Unrecognized stat field");
69 * Convert a struct stat to a struct bfs_stat.
71 static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) {
74 buf->dev = statbuf->st_dev;
75 buf->mask |= BFS_STAT_DEV;
77 buf->ino = statbuf->st_ino;
78 buf->mask |= BFS_STAT_INO;
80 buf->mode = statbuf->st_mode;
81 buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE;
83 buf->nlink = statbuf->st_nlink;
84 buf->mask |= BFS_STAT_NLINK;
86 buf->gid = statbuf->st_gid;
87 buf->mask |= BFS_STAT_GID;
89 buf->uid = statbuf->st_uid;
90 buf->mask |= BFS_STAT_UID;
92 buf->size = statbuf->st_size;
93 buf->mask |= BFS_STAT_SIZE;
95 buf->blocks = statbuf->st_blocks;
96 buf->mask |= BFS_STAT_BLOCKS;
98 buf->rdev = statbuf->st_rdev;
99 buf->mask |= BFS_STAT_RDEV;
102 buf->attrs = statbuf->st_flags;
103 buf->mask |= BFS_STAT_ATTRS;
106 buf->atime = statbuf->st_atim;
107 buf->mask |= BFS_STAT_ATIME;
109 buf->ctime = statbuf->st_ctim;
110 buf->mask |= BFS_STAT_CTIME;
112 buf->mtime = statbuf->st_mtim;
113 buf->mask |= BFS_STAT_MTIME;
115 #if __APPLE__ || __FreeBSD__ || __NetBSD__
116 buf->btime = statbuf->st_birthtim;
117 buf->mask |= BFS_STAT_BTIME;
122 * bfs_stat() implementation backed by stat().
124 static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) {
126 int ret = fstatat(at_fd, at_path, &statbuf, at_flags);
128 bfs_stat_convert(&statbuf, buf);
136 * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28.
138 static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) {
139 #if BFS_HAS_LIBC_STATX
140 int ret = statx(at_fd, at_path, at_flags, mask, buf);
142 int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf);
146 // -fsanitize=memory doesn't know about statx()
154 * bfs_stat() implementation backed by statx().
156 static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) {
157 unsigned int mask = STATX_BASIC_STATS | STATX_BTIME;
159 int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf);
164 // Callers shouldn't have to check anything except the times
165 const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME);
166 if ((xbuf.stx_mask & guaranteed) != guaranteed) {
173 buf->dev = xmakedev(xbuf.stx_dev_major, xbuf.stx_dev_minor);
174 buf->mask |= BFS_STAT_DEV;
176 buf->ino = xbuf.stx_ino;
177 buf->mask |= BFS_STAT_INO;
179 buf->mode = xbuf.stx_mode;
180 buf->mask |= BFS_STAT_TYPE;
181 buf->mask |= BFS_STAT_MODE;
183 buf->nlink = xbuf.stx_nlink;
184 buf->mask |= BFS_STAT_NLINK;
186 buf->gid = xbuf.stx_gid;
187 buf->mask |= BFS_STAT_GID;
189 buf->uid = xbuf.stx_uid;
190 buf->mask |= BFS_STAT_UID;
192 buf->size = xbuf.stx_size;
193 buf->mask |= BFS_STAT_SIZE;
195 buf->blocks = xbuf.stx_blocks;
196 buf->mask |= BFS_STAT_BLOCKS;
198 buf->rdev = xmakedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor);
199 buf->mask |= BFS_STAT_RDEV;
201 buf->attrs = xbuf.stx_attributes;
202 buf->mask |= BFS_STAT_ATTRS;
204 if (xbuf.stx_mask & STATX_ATIME) {
205 buf->atime.tv_sec = xbuf.stx_atime.tv_sec;
206 buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec;
207 buf->mask |= BFS_STAT_ATIME;
210 if (xbuf.stx_mask & STATX_BTIME) {
211 buf->btime.tv_sec = xbuf.stx_btime.tv_sec;
212 buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec;
213 buf->mask |= BFS_STAT_BTIME;
216 if (xbuf.stx_mask & STATX_CTIME) {
217 buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec;
218 buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec;
219 buf->mask |= BFS_STAT_CTIME;
222 if (xbuf.stx_mask & STATX_MTIME) {
223 buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec;
224 buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec;
225 buf->mask |= BFS_STAT_MTIME;
231 #endif // BFS_USE_STATX
234 * Calls the stat() implementation with explicit flags.
236 static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) {
238 static atomic bool has_statx = true;
240 if (load(&has_statx, relaxed)) {
241 int ret = bfs_statx_impl(at_fd, at_path, at_flags | x_flags, buf);
242 // EPERM is commonly returned in a seccomp() sandbox that does
244 if (ret != 0 && (errno == ENOSYS || errno == EPERM)) {
245 store(&has_statx, false, relaxed);
252 return bfs_stat_impl(at_fd, at_path, at_flags, buf);
256 * Implements the BFS_STAT_TRYFOLLOW retry logic.
258 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) {
259 int ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf);
262 && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW
263 && is_nonexistence_error(errno))
265 at_flags |= AT_SYMLINK_NOFOLLOW;
266 ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf);
272 int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf) {
274 if (flags & BFS_STAT_NOFOLLOW) {
275 at_flags |= AT_SYMLINK_NOFOLLOW;
278 #if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35))
279 at_flags |= AT_NO_AUTOMOUNT;
283 #ifdef AT_STATX_DONT_SYNC
284 if (flags & BFS_STAT_NOSYNC) {
285 x_flags |= AT_STATX_DONT_SYNC;
290 return bfs_stat_tryfollow(at_fd, at_path, at_flags, x_flags, flags, buf);
293 // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html
294 #if defined(AT_EMPTY_PATH) && !__GNU__
295 static atomic bool has_at_ep = true;
296 if (load(&has_at_ep, relaxed)) {
297 at_flags |= AT_EMPTY_PATH;
298 int ret = bfs_stat_explicit(at_fd, "", at_flags, x_flags, buf);
299 if (ret != 0 && errno == EINVAL) {
300 store(&has_at_ep, false, relaxed);
308 if (fstat(at_fd, &statbuf) == 0) {
309 bfs_stat_convert(&statbuf, buf);
316 const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) {
317 if (!(buf->mask & field)) {
332 bfs_bug("Invalid stat field for time");
338 void bfs_stat_id(const struct bfs_stat *buf, bfs_file_id *id) {
339 memcpy(*id, &buf->dev, sizeof(buf->dev));
340 memcpy(*id + sizeof(buf->dev), &buf->ino, sizeof(buf->ino));