]> Sergey Matveev's repositories - bfs.git/blob - src/stat.c
Skip mtab
[bfs.git] / src / stat.c
1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
3
4 #include "stat.h"
5 #include "atomic.h"
6 #include "bfstd.h"
7 #include "config.h"
8 #include "diag.h"
9 #include "sanity.h"
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15
16 #if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30)
17 #  define BFS_HAS_LIBC_STATX true
18 #elif __linux__
19 #  include <linux/stat.h>
20 #  include <sys/syscall.h>
21 #  include <unistd.h>
22 #endif
23
24 #ifndef BFS_USE_STATX
25 #  if BFS_HAS_LIBC_STATX || defined(SYS_statx)
26 #    define BFS_USE_STATX true
27 #  endif
28 #endif
29
30 const char *bfs_stat_field_name(enum bfs_stat_field field) {
31         switch (field) {
32         case BFS_STAT_DEV:
33                 return "device number";
34         case BFS_STAT_INO:
35                 return "inode nunmber";
36         case BFS_STAT_TYPE:
37                 return "type";
38         case BFS_STAT_MODE:
39                 return "mode";
40         case BFS_STAT_NLINK:
41                 return "link count";
42         case BFS_STAT_GID:
43                 return "group ID";
44         case BFS_STAT_UID:
45                 return "user ID";
46         case BFS_STAT_SIZE:
47                 return "size";
48         case BFS_STAT_BLOCKS:
49                 return "block count";
50         case BFS_STAT_RDEV:
51                 return "underlying device";
52         case BFS_STAT_ATTRS:
53                 return "attributes";
54         case BFS_STAT_ATIME:
55                 return "access time";
56         case BFS_STAT_BTIME:
57                 return "birth time";
58         case BFS_STAT_CTIME:
59                 return "change time";
60         case BFS_STAT_MTIME:
61                 return "modification time";
62         }
63
64         bfs_bug("Unrecognized stat field");
65         return "???";
66 }
67
68 /**
69  * Convert a struct stat to a struct bfs_stat.
70  */
71 static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) {
72         buf->mask = 0;
73
74         buf->dev = statbuf->st_dev;
75         buf->mask |= BFS_STAT_DEV;
76
77         buf->ino = statbuf->st_ino;
78         buf->mask |= BFS_STAT_INO;
79
80         buf->mode = statbuf->st_mode;
81         buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE;
82
83         buf->nlink = statbuf->st_nlink;
84         buf->mask |= BFS_STAT_NLINK;
85
86         buf->gid = statbuf->st_gid;
87         buf->mask |= BFS_STAT_GID;
88
89         buf->uid = statbuf->st_uid;
90         buf->mask |= BFS_STAT_UID;
91
92         buf->size = statbuf->st_size;
93         buf->mask |= BFS_STAT_SIZE;
94
95         buf->blocks = statbuf->st_blocks;
96         buf->mask |= BFS_STAT_BLOCKS;
97
98         buf->rdev = statbuf->st_rdev;
99         buf->mask |= BFS_STAT_RDEV;
100
101 #if BSD
102         buf->attrs = statbuf->st_flags;
103         buf->mask |= BFS_STAT_ATTRS;
104 #endif
105
106         buf->atime = statbuf->st_atim;
107         buf->mask |= BFS_STAT_ATIME;
108
109         buf->ctime = statbuf->st_ctim;
110         buf->mask |= BFS_STAT_CTIME;
111
112         buf->mtime = statbuf->st_mtim;
113         buf->mask |= BFS_STAT_MTIME;
114
115 #if __APPLE__ || __FreeBSD__ || __NetBSD__
116         buf->btime = statbuf->st_birthtim;
117         buf->mask |= BFS_STAT_BTIME;
118 #endif
119 }
120
121 /**
122  * bfs_stat() implementation backed by stat().
123  */
124 static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) {
125         struct stat statbuf;
126         int ret = fstatat(at_fd, at_path, &statbuf, at_flags);
127         if (ret == 0) {
128                 bfs_stat_convert(&statbuf, buf);
129         }
130         return ret;
131 }
132
133 #if BFS_USE_STATX
134
135 /**
136  * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28.
137  */
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);
141 #else
142         int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf);
143 #endif
144
145         if (ret == 0) {
146                 // -fsanitize=memory doesn't know about statx()
147                 sanitize_init(buf);
148         }
149
150         return ret;
151 }
152
153 /**
154  * bfs_stat() implementation backed by statx().
155  */
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;
158         struct statx xbuf;
159         int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf);
160         if (ret != 0) {
161                 return ret;
162         }
163
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) {
167                 errno = ENOTSUP;
168                 return -1;
169         }
170
171         buf->mask = 0;
172
173         buf->dev = xmakedev(xbuf.stx_dev_major, xbuf.stx_dev_minor);
174         buf->mask |= BFS_STAT_DEV;
175
176         buf->ino = xbuf.stx_ino;
177         buf->mask |= BFS_STAT_INO;
178
179         buf->mode = xbuf.stx_mode;
180         buf->mask |= BFS_STAT_TYPE;
181         buf->mask |= BFS_STAT_MODE;
182
183         buf->nlink = xbuf.stx_nlink;
184         buf->mask |= BFS_STAT_NLINK;
185
186         buf->gid = xbuf.stx_gid;
187         buf->mask |= BFS_STAT_GID;
188
189         buf->uid = xbuf.stx_uid;
190         buf->mask |= BFS_STAT_UID;
191
192         buf->size = xbuf.stx_size;
193         buf->mask |= BFS_STAT_SIZE;
194
195         buf->blocks = xbuf.stx_blocks;
196         buf->mask |= BFS_STAT_BLOCKS;
197
198         buf->rdev = xmakedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor);
199         buf->mask |= BFS_STAT_RDEV;
200
201         buf->attrs = xbuf.stx_attributes;
202         buf->mask |= BFS_STAT_ATTRS;
203
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;
208         }
209
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;
214         }
215
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;
220         }
221
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;
226         }
227
228         return ret;
229 }
230
231 #endif // BFS_USE_STATX
232
233 /**
234  * Calls the stat() implementation with explicit flags.
235  */
236 static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) {
237 #if BFS_USE_STATX
238         static atomic bool has_statx = true;
239
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
243                 // not allow statx()
244                 if (ret != 0 && (errno == ENOSYS || errno == EPERM)) {
245                         store(&has_statx, false, relaxed);
246                 } else {
247                         return ret;
248                 }
249         }
250 #endif
251
252         return bfs_stat_impl(at_fd, at_path, at_flags, buf);
253 }
254
255 /**
256  * Implements the BFS_STAT_TRYFOLLOW retry logic.
257  */
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);
260
261         if (ret != 0
262             && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW
263             && is_nonexistence_error(errno))
264         {
265                 at_flags |= AT_SYMLINK_NOFOLLOW;
266                 ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf);
267         }
268
269         return ret;
270 }
271
272 int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf) {
273         int at_flags = 0;
274         if (flags & BFS_STAT_NOFOLLOW) {
275                 at_flags |= AT_SYMLINK_NOFOLLOW;
276         }
277
278 #if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35))
279         at_flags |= AT_NO_AUTOMOUNT;
280 #endif
281
282         int x_flags = 0;
283 #ifdef AT_STATX_DONT_SYNC
284         if (flags & BFS_STAT_NOSYNC) {
285                 x_flags |= AT_STATX_DONT_SYNC;
286         }
287 #endif
288
289         if (at_path) {
290                 return bfs_stat_tryfollow(at_fd, at_path, at_flags, x_flags, flags, buf);
291         }
292
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);
301                 } else {
302                         return ret;
303                 }
304         }
305 #endif
306
307         struct stat statbuf;
308         if (fstat(at_fd, &statbuf) == 0) {
309                 bfs_stat_convert(&statbuf, buf);
310                 return 0;
311         } else {
312                 return -1;
313         }
314 }
315
316 const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) {
317         if (!(buf->mask & field)) {
318                 errno = ENOTSUP;
319                 return NULL;
320         }
321
322         switch (field) {
323         case BFS_STAT_ATIME:
324                 return &buf->atime;
325         case BFS_STAT_BTIME:
326                 return &buf->btime;
327         case BFS_STAT_CTIME:
328                 return &buf->ctime;
329         case BFS_STAT_MTIME:
330                 return &buf->mtime;
331         default:
332                 bfs_bug("Invalid stat field for time");
333                 errno = EINVAL;
334                 return NULL;
335         }
336 }
337
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));
341 }