diff options
Diffstat (limited to 'src/dir.c')
-rw-r--r-- | src/dir.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/src/dir.c b/src/dir.c new file mode 100644 index 0000000..024e767 --- /dev/null +++ b/src/dir.c @@ -0,0 +1,303 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2021-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. * + ****************************************************************************/ + +#include "dir.h" +#include "util.h" +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#if __linux__ +# include <sys/syscall.h> +#endif // __linux__ + +enum bfs_type bfs_mode_to_type(mode_t mode) { + switch (mode & S_IFMT) { +#ifdef S_IFBLK + case S_IFBLK: + return BFS_BLK; +#endif +#ifdef S_IFCHR + case S_IFCHR: + return BFS_CHR; +#endif +#ifdef S_IFDIR + case S_IFDIR: + return BFS_DIR; +#endif +#ifdef S_IFDOOR + case S_IFDOOR: + return BFS_DOOR; +#endif +#ifdef S_IFIFO + case S_IFIFO: + return BFS_FIFO; +#endif +#ifdef S_IFLNK + case S_IFLNK: + return BFS_LNK; +#endif +#ifdef S_IFPORT + case S_IFPORT: + return BFS_PORT; +#endif +#ifdef S_IFREG + case S_IFREG: + return BFS_REG; +#endif +#ifdef S_IFSOCK + case S_IFSOCK: + return BFS_SOCK; +#endif +#ifdef S_IFWHT + case S_IFWHT: + return BFS_WHT; +#endif + + default: + return BFS_UNKNOWN; + } +} + +#if __linux__ +/** + * This is not defined in the kernel headers for some reason, callers have to + * define it themselves. + */ +struct linux_dirent64 { + ino64_t d_ino; + off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +// Make the whole allocation 64k +#define BUF_SIZE ((64 << 10) - 8) +#endif + +struct bfs_dir { +#if __linux__ + int fd; + unsigned short pos; + unsigned short size; +#else + DIR *dir; + struct dirent *de; +#endif +}; + +struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { +#if __linux__ + struct bfs_dir *dir = malloc(sizeof(*dir) + BUF_SIZE); +#else + struct bfs_dir *dir = malloc(sizeof(*dir)); +#endif + if (!dir) { + return NULL; + } + + int fd; + if (at_path) { + fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); + } else if (at_fd >= 0) { + fd = at_fd; + } else { + free(dir); + errno = EBADF; + return NULL; + } + + if (fd < 0) { + free(dir); + return NULL; + } + +#if __linux__ + dir->fd = fd; + dir->pos = 0; + dir->size = 0; +#else + dir->dir = fdopendir(fd); + if (!dir->dir) { + if (at_path) { + close_quietly(fd); + } + free(dir); + return NULL; + } + + dir->de = NULL; +#endif // __linux__ + + return dir; +} + +int bfs_dirfd(const struct bfs_dir *dir) { +#if __linux__ + return dir->fd; +#else + return dirfd(dir->dir); +#endif +} + +/** Convert a dirent type to a bfs_type. */ +static enum bfs_type translate_type(int d_type) { + switch (d_type) { +#ifdef DT_BLK + case DT_BLK: + return BFS_BLK; +#endif +#ifdef DT_CHR + case DT_CHR: + return BFS_CHR; +#endif +#ifdef DT_DIR + case DT_DIR: + return BFS_DIR; +#endif +#ifdef DT_DOOR + case DT_DOOR: + return BFS_DOOR; +#endif +#ifdef DT_FIFO + case DT_FIFO: + return BFS_FIFO; +#endif +#ifdef DT_LNK + case DT_LNK: + return BFS_LNK; +#endif +#ifdef DT_PORT + case DT_PORT: + return BFS_PORT; +#endif +#ifdef DT_REG + case DT_REG: + return BFS_REG; +#endif +#ifdef DT_SOCK + case DT_SOCK: + return BFS_SOCK; +#endif +#ifdef DT_WHT + case DT_WHT: + return BFS_WHT; +#endif + } + + return BFS_UNKNOWN; +} + +#if !__linux__ +/** Get the type from a struct dirent if it exists, and convert it. */ +static enum bfs_type dirent_type(const struct dirent *de) { +#if defined(_DIRENT_HAVE_D_TYPE) || defined(DT_UNKNOWN) + return translate_type(de->d_type); +#else + return BFS_UNKNOWN; +#endif +} +#endif + +/** Check if a name is . or .. */ +static bool is_dot(const char *name) { + return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { + while (true) { +#if __linux__ + char *buf = (char *)(dir + 1); + + if (dir->pos >= dir->size) { +#if BFS_HAS_FEATURE(memory_sanitizer, false) + // Make sure msan knows the buffer is initialized + memset(buf, 0, BUF_SIZE); +#endif + + ssize_t size = syscall(__NR_getdents64, dir->fd, buf, BUF_SIZE); + if (size <= 0) { + return size; + } + dir->pos = 0; + dir->size = size; + } + + const struct linux_dirent64 *lde = (void *)(buf + dir->pos); + dir->pos += lde->d_reclen; + + if (is_dot(lde->d_name)) { + continue; + } + + if (de) { + de->type = translate_type(lde->d_type); + de->name = lde->d_name; + } + + return 1; +#else // !__linux__ + errno = 0; + dir->de = readdir(dir->dir); + if (dir->de) { + if (is_dot(dir->de->d_name)) { + continue; + } + if (de) { + de->type = dirent_type(dir->de); + de->name = dir->de->d_name; + } + return 1; + } else if (errno != 0) { + return -1; + } else { + return 0; + } +#endif // !__linux__ + } +} + +int bfs_closedir(struct bfs_dir *dir) { +#if __linux__ + int ret = xclose(dir->fd); +#else + int ret = closedir(dir->dir); +#endif + free(dir); + return ret; +} + +int bfs_freedir(struct bfs_dir *dir) { +#if __linux__ + int ret = dir->fd; + free(dir); + return ret; +#elif __FreeBSD__ + int ret = fdclosedir(dir->dir); + free(dir); + return ret; +#else + int ret = dup_cloexec(dirfd(dir->dir)); + bfs_closedir(dir); + return ret; +#endif +} |