diff options
author | Patryk Niedźwiedziński <pniedzwiedzinski19@gmail.com> | 2021-04-17 10:28:11 +0200 |
---|---|---|
committer | Patryk Niedźwiedziński <pniedzwiedzinski19@gmail.com> | 2021-04-17 10:28:11 +0200 |
commit | b1ddaa81f28b8bbc62d6d50526e0935b173099eb (patch) | |
tree | e606c194f8eecdedf3f710ed097cadfe97d60122 /stdio/dirstream.c | |
download | cat-cosmopolitan-b1ddaa81f28b8bbc62d6d50526e0935b173099eb.tar.gz cat-cosmopolitan-b1ddaa81f28b8bbc62d6d50526e0935b173099eb.zip |
Diffstat (limited to 'stdio/dirstream.c')
-rw-r--r-- | stdio/dirstream.c | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/stdio/dirstream.c b/stdio/dirstream.c new file mode 100644 index 0000000..77940a0 --- /dev/null +++ b/stdio/dirstream.c @@ -0,0 +1,321 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ 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 "libc/bits/bits.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/dirent.h" +#include "libc/dce.h" +#include "libc/macros.internal.h" +#include "libc/mem/mem.h" +#include "libc/nt/enum/fileflagandattributes.h" +#include "libc/nt/enum/filetype.h" +#include "libc/nt/files.h" +#include "libc/nt/runtime.h" +#include "libc/nt/struct/win32finddata.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/dt.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/errfuns.h" + +/** + * @fileoverview Directory Streams for Linux+Mac+Windows+FreeBSD+OpenBSD. + * + * System interfaces for listing the contents of file system directories + * are famously incompatible across platforms. Most native projects that + * have been around a long time implement wrappers for this. Normally it + * will only be for DOS or Windows support. So this is the first time it + * has been done for five platforms, having a remarkably tiny footprint. + */ + +/** + * Directory stream object. + */ +struct dirstream { + int64_t fd; + int64_t tell; + struct dirent ent; + union { + struct { + unsigned buf_pos; + unsigned buf_end; + char buf[BUFSIZ]; + }; + struct { + bool isdone; + struct NtWin32FindData windata; + }; + }; +}; + +/** + * FreeBSD getdents() and XNU getdirentries() ABI. + */ +struct dirent_bsd { + uint32_t d_fileno; + uint16_t d_reclen; + uint8_t d_type; + uint8_t d_namlen; + char d_name[256]; +}; + +/** + * OpenBSD getdents() ABI. + */ +struct dirent_openbsd { + uint64_t d_fileno; + int64_t d_off; + uint16_t d_reclen; + uint8_t d_type; + uint8_t d_namlen; + uint8_t __zomg[4]; + char d_name[256]; +}; + +/** + * NetBSD getdents(). + */ +struct dirent_netbsd { + uint64_t d_fileno; + uint16_t d_reclen; + uint16_t d_namlen; + uint8_t d_type; + char d_name[512]; +}; + +static textwindows DIR *opendir_nt_impl(char16_t name[PATH_MAX], size_t len) { + DIR *res; + if (len + 2 + 1 <= PATH_MAX) { + if (name[len - 1] != u'\\') name[len++] = u'\\'; + name[len++] = u'*'; + name[len] = u'\0'; + if ((res = calloc(1, sizeof(DIR)))) { + if ((res->fd = FindFirstFile(name, &res->windata)) != -1) { + return res; + } else { + __winerr(); + } + free(res); + } + } else { + enametoolong(); + } + return NULL; +} + +static textwindows noinline DIR *opendir_nt(const char *path) { + int len; + char16_t name[PATH_MAX]; + if ((len = __mkntpath(path, name)) == -1) return NULL; + return opendir_nt_impl(name, len); +} + +static textwindows noinline DIR *fdopendir_nt(int fd) { + DIR *res; + char16_t name[PATH_MAX]; + if (__isfdkind(fd, kFdFile)) { + if ((res = opendir_nt_impl( + name, GetFinalPathNameByHandle( + g_fds.p[fd].handle, name, ARRAYLEN(name), + kNtFileNameNormalized | kNtVolumeNameDos)))) { + close(fd); + return res; + } + } else { + ebadf(); + } + return NULL; +} + +static textwindows noinline struct dirent *readdir_nt(DIR *dir) { + if (!dir->isdone) { + memset(&dir->ent, 0, sizeof(dir->ent)); + dir->ent.d_ino = 0; + dir->ent.d_off = dir->tell++; + dir->ent.d_reclen = sizeof(dir->ent) + + tprecode16to8(dir->ent.d_name, sizeof(dir->ent.d_name), + dir->windata.cFileName) + .ax + + 1; + switch (dir->windata.dwFileType) { + case kNtFileTypeDisk: + dir->ent.d_type = DT_BLK; + break; + case kNtFileTypeChar: + dir->ent.d_type = DT_CHR; + break; + case kNtFileTypePipe: + dir->ent.d_type = DT_FIFO; + break; + default: + if (dir->windata.dwFileAttributes & kNtFileAttributeDirectory) { + dir->ent.d_type = DT_DIR; + } else { + dir->ent.d_type = DT_REG; + } + break; + } + dir->isdone = !FindNextFile(dir->fd, &dir->windata); + return &dir->ent; + } else { + return NULL; + } +} + +/** + * Opens directory, e.g. + * + * DIR *d; + * struct dirent *e; + * CHECK((d = opendir(path))); + * while ((e = readdir(d))) { + * printf("%s/%s\n", path, e->d_name); + * } + * LOGIFNEG1(closedir(d)); + * + * @returns newly allocated DIR object, or NULL w/ errno + * @errors ENOENT, ENOTDIR, EACCES, EMFILE, ENFILE, ENOMEM + * @see glob() + */ +DIR *opendir(const char *name) { + int fd; + DIR *res; + if (!IsWindows()) { + res = NULL; + if ((fd = open(name, O_RDONLY | O_DIRECTORY | O_CLOEXEC)) != -1) { + if (!(res = fdopendir(fd))) close(fd); + } + return res; + } else { + return opendir_nt(name); + } +} + +/** + * Creates directory object for file descriptor. + * + * @param fd gets owned by this function, if it succeeds + * @return new directory object, which must be freed by closedir(), + * or NULL w/ errno + * @errors ENOMEM and fd is closed + */ +DIR *fdopendir(int fd) { + DIR *dir; + if (!IsWindows()) { + if (!(dir = calloc(1, sizeof(*dir)))) return NULL; + dir->fd = fd; + return dir; + } else { + return fdopendir_nt(fd); + } +} + +/** + * Reads next entry from directory stream. + * + * This API doesn't define any particular ordering. + * + * @param dir is the object opendir() or fdopendir() returned + * @return next entry or NULL on end or error, which can be + * differentiated by setting errno to 0 beforehand + */ +struct dirent *readdir(DIR *dir) { + int rc; + long basep; + struct dirent *ent; + struct dirent_bsd *bsd; + struct dirent_netbsd *nbsd; + struct dirent_openbsd *obsd; + if (!IsWindows()) { + if (dir->buf_pos >= dir->buf_end) { + basep = dir->tell; /* TODO(jart): what does xnu do */ + rc = getdents(dir->fd, dir->buf, sizeof(dir->buf) - 256, &basep); + if (!rc || rc == -1) return NULL; + dir->buf_pos = 0; + dir->buf_end = rc; + } + if (IsLinux()) { + ent = (struct dirent *)(dir->buf + dir->buf_pos); + dir->buf_pos += ent->d_reclen; + dir->tell = ent->d_off; + } else if (IsOpenbsd()) { + obsd = (struct dirent_openbsd *)(dir->buf + dir->buf_pos); + dir->buf_pos += obsd->d_reclen; + ent = &dir->ent; + ent->d_ino = obsd->d_fileno; + ent->d_off = obsd->d_off; + ent->d_reclen = obsd->d_reclen; + ent->d_type = obsd->d_type; + memcpy(ent->d_name, obsd->d_name, obsd->d_namlen + 1); + } else if (IsNetbsd()) { + nbsd = (struct dirent_netbsd *)(dir->buf + dir->buf_pos); + dir->buf_pos += nbsd->d_reclen; + ent = &dir->ent; + ent->d_ino = nbsd->d_fileno; + ent->d_off = dir->tell++; + ent->d_reclen = nbsd->d_reclen; + ent->d_type = nbsd->d_type; + memcpy(ent->d_name, nbsd->d_name, MAX(256, nbsd->d_namlen + 1)); + } else { + bsd = (struct dirent_bsd *)(dir->buf + dir->buf_pos); + dir->buf_pos += bsd->d_reclen; + ent = &dir->ent; + ent->d_ino = bsd->d_fileno; + ent->d_off = IsXnu() ? (dir->tell = basep) : dir->tell++; + ent->d_reclen = bsd->d_reclen; + ent->d_type = bsd->d_type; + memcpy(ent->d_name, bsd->d_name, bsd->d_namlen + 1); + } + return ent; + } else { + return readdir_nt(dir); + } +} + +/** + * Closes directory object returned by opendir(). + * @return 0 on success or -1 w/ errno + */ +int closedir(DIR *dir) { + int rc; + if (dir) { + if (!IsWindows()) { + rc = close(dir->fd); + } else { + rc = FindClose(dir->fd) ? 0 : __winerr(); + } + free(dir); + } else { + rc = 0; + } + return rc; +} + +/** + * Returns offset into directory data. + */ +long telldir(DIR *dir) { + return dir->tell; +} + +/** + * Returns file descriptor associated with DIR object. + */ +int dirfd(DIR *dir) { + if (IsWindows()) return eopnotsupp(); + return dir->fd; +} |