1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
21 # include <sys/syscall.h>
24 /** getdents() syscall wrapper. */
25 static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
26 sanitize_uninit(buf, size);
28 #if (__linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)) || __ANDROID__
29 ssize_t ret = syscall(SYS_getdents64, fd, buf, size);
31 ssize_t ret = getdents64(fd, buf, size);
33 ssize_t ret = getdents(fd, buf, size);
37 sanitize_init(buf, ret);
43 #endif // BFS_USE_GETDENTS
45 #if BFS_USE_GETDENTS && __linux__
46 /** Directory entry type for bfs_getdents() */
47 typedef struct dirent64 sys_dirent;
49 typedef struct dirent sys_dirent;
52 enum bfs_type bfs_mode_to_type(mode_t mode) {
53 switch (mode & S_IFMT) {
101 * Private directory flags.
104 /** We've reached the end of the directory. */
105 BFS_DIR_EOF = BFS_DIR_PRIVATE << 0,
106 /** This directory is a union mount we need to dedup manually. */
107 BFS_DIR_UNION = BFS_DIR_PRIVATE << 1,
120 alignas(sys_dirent) char buf[];
128 # define DIR_SIZE (64 << 10)
129 # define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir))
131 # define DIR_SIZE sizeof(struct bfs_dir)
134 struct bfs_dir *bfs_allocdir(void) {
135 return malloc(DIR_SIZE);
138 void bfs_dir_arena(struct arena *arena) {
139 arena_init(arena, alignof(struct bfs_dir), DIR_SIZE);
142 int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags) {
145 fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
149 } else if (at_fd >= 0) {
163 # if __FreeBSD__ && defined(F_ISUNIONSTACK)
164 if (fcntl(fd, F_ISUNIONSTACK) > 0) {
165 dir->flags |= BFS_DIR_UNION;
166 trie_init(&dir->trie);
169 #else // !BFS_USE_GETDENTS
170 dir->dir = fdopendir(fd);
183 int bfs_dirfd(const struct bfs_dir *dir) {
187 return dirfd(dir->dir);
191 int bfs_polldir(struct bfs_dir *dir) {
193 if (dir->pos < dir->size) {
195 } else if (dir->flags & BFS_DIR_EOF) {
199 char *buf = (char *)(dir + 1);
200 ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE);
202 dir->flags |= BFS_DIR_EOF;
204 } else if (size < 0) {
211 // Like read(), getdents() doesn't indicate EOF until another call returns zero.
212 // Check that eagerly here to hopefully avoid a syscall in the last bfs_readdir().
213 size_t rest = BUF_SIZE - size;
214 if (rest >= sizeof(sys_dirent)) {
215 size = bfs_getdents(dir->fd, buf + size, rest);
218 } else if (size == 0) {
219 dir->flags |= BFS_DIR_EOF;
224 #else // !BFS_USE_GETDENTS
227 } else if (dir->flags & BFS_DIR_EOF) {
232 dir->de = readdir(dir->dir);
235 } else if (errno == 0) {
236 dir->flags |= BFS_DIR_EOF;
244 /** Read a single directory entry. */
245 static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) {
246 int ret = bfs_polldir(dir);
249 char *buf = (char *)(dir + 1);
250 *de = (const sys_dirent *)(buf + dir->pos);
251 dir->pos += (*de)->d_reclen;
260 /** Skip ".", "..", and deleted/empty dirents. */
261 static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) {
264 // Union mounts on FreeBSD have to be de-duplicated in userspace
265 if (dir->flags & BFS_DIR_UNION) {
266 struct trie_leaf *leaf = trie_insert_str(&dir->trie, de->d_name);
269 } else if (leaf->value) {
276 // NFS mounts on FreeBSD can return empty dirents with inode number 0
277 if (de->d_ino == 0) {
283 if (de->d_type == DT_WHT && !(dir->flags & BFS_DIR_WHITEOUTS)) {
287 #endif // BFS_USE_GETDENTS
289 const char *name = de->d_name;
290 return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
293 /** Convert de->d_type to a bfs_type, if it exists. */
294 static enum bfs_type bfs_d_type(const sys_dirent *de) {
296 return bfs_mode_to_type(DTTOIF(de->d_type));
302 int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) {
304 const sys_dirent *sysde;
305 int ret = bfs_getdent(dir, &sysde);
310 int skip = bfs_skipdent(dir, sysde);
318 de->type = bfs_d_type(sysde);
319 de->name = sysde->d_name;
326 static void bfs_destroydir(struct bfs_dir *dir) {
327 #if BFS_USE_GETDENTS && __FreeBSD__
328 if (dir->flags & BFS_DIR_UNION) {
329 trie_destroy(&dir->trie);
333 sanitize_uninit(dir, DIR_SIZE);
336 int bfs_closedir(struct bfs_dir *dir) {
338 int ret = xclose(dir->fd);
340 int ret = closedir(dir->dir);
342 bfs_verify(errno != EBADF);
350 #if BFS_USE_UNWRAPDIR
351 int bfs_unwrapdir(struct bfs_dir *dir) {
355 int ret = fdclosedir(dir->dir);