]> git.proxmox.com Git - wasi-libc.git/blame - libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c
Remove __wasilibc_unmodified_upstream markers from libc-bottom-half.
[wasi-libc.git] / libc-bottom-half / cloudlibc / src / libc / dirent / scandirat.c
CommitLineData
320054e8
DG
1// Copyright (c) 2015-2016 Nuxi, https://nuxi.nl/
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
446cb3f1 5#include <wasi/api.h>
a94d2d04 6#include <wasi/libc.h>
320054e8
DG
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13
14#include "dirent_impl.h"
15
16static int sel_true(const struct dirent *de) {
17 return 1;
18}
19
20int scandirat(int dirfd, const char *dir, struct dirent ***namelist,
21 int (*sel)(const struct dirent *),
22 int (*compar)(const struct dirent **, const struct dirent **)) {
23 // Match all files if no select function is provided.
24 if (sel == NULL)
25 sel = sel_true;
26
27 // Open the directory.
a94d2d04 28 int fd = __wasilibc_openat_nomode(dirfd, dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY);
320054e8
DG
29 if (fd == -1)
30 return -1;
31
32 // Allocate a read buffer for the directory entries.
33 size_t buffer_size = DIRENT_DEFAULT_BUFFER_SIZE;
34 char *buffer = malloc(buffer_size);
35 if (buffer == NULL) {
36 close(fd);
37 return -1;
38 }
39 size_t buffer_processed = buffer_size;
40 size_t buffer_used = buffer_size;
41
42 // Space for the array to return to the caller.
43 struct dirent **dirents = NULL;
44 size_t dirents_size = 0;
45 size_t dirents_used = 0;
46
47 __wasi_dircookie_t cookie = __WASI_DIRCOOKIE_START;
48 for (;;) {
49 // Extract the next dirent header.
50 size_t buffer_left = buffer_used - buffer_processed;
51 if (buffer_left < sizeof(__wasi_dirent_t)) {
52 // End-of-file.
53 if (buffer_used < buffer_size)
54 break;
55 goto read_entries;
56 }
57 __wasi_dirent_t entry;
58 memcpy(&entry, buffer + buffer_processed, sizeof(entry));
59
60 size_t entry_size = sizeof(__wasi_dirent_t) + entry.d_namlen;
61 if (entry.d_namlen == 0) {
62 // Invalid pathname length. Skip the entry.
63 buffer_processed += entry_size;
64 continue;
65 }
66
67 // The entire entry must be present in buffer space. If not, read
68 // the entry another time. Ensure that the read buffer is large
69 // enough to fit at least this single entry.
70 if (buffer_left < entry_size) {
71 while (buffer_size < entry_size)
72 buffer_size *= 2;
73 char *new_buffer = realloc(buffer, buffer_size);
74 if (new_buffer == NULL)
75 goto bad;
76 buffer = new_buffer;
77 goto read_entries;
78 }
79
80 // Skip entries having null bytes in the filename.
81 const char *name = buffer + buffer_processed + sizeof(entry);
82 buffer_processed += entry_size;
83 if (memchr(name, '\0', entry.d_namlen) != NULL)
84 continue;
85
86 // Create the new directory entry.
87 struct dirent *dirent =
88 malloc(offsetof(struct dirent, d_name) + entry.d_namlen + 1);
89 if (dirent == NULL)
90 goto bad;
91 dirent->d_ino = entry.d_ino;
92 dirent->d_type = entry.d_type;
93 memcpy(dirent->d_name, name, entry.d_namlen);
94 dirent->d_name[entry.d_namlen] = '\0';
95 cookie = entry.d_next;
96
97 if (sel(dirent)) {
98 // Add the entry to the results.
99 if (dirents_used == dirents_size) {
100 dirents_size = dirents_size < 8 ? 8 : dirents_size * 2;
101 struct dirent **new_dirents =
102 realloc(dirents, dirents_size * sizeof(*dirents));
103 if (new_dirents == NULL) {
104 free(dirent);
105 goto bad;
106 }
107 dirents = new_dirents;
108 }
109 dirents[dirents_used++] = dirent;
110 } else {
111 // Discard the entry.
112 free(dirent);
113 }
114 continue;
115
116 read_entries:;
117 // Load more directory entries and continue.
446cb3f1
DG
118 // TODO: Remove the cast on `buffer` once the witx is updated with char8 support.
119 __wasi_errno_t error = __wasi_fd_readdir(fd, (uint8_t *)buffer, buffer_size,
320054e8
DG
120 cookie, &buffer_used);
121 if (error != 0) {
122 errno = error;
123 goto bad;
124 }
125 buffer_processed = 0;
126 }
127
128 // Sort results and return them.
129 free(buffer);
130 close(fd);
131 (qsort)(dirents, dirents_used, sizeof(*dirents),
132 (int (*)(const void *, const void *))compar);
133 *namelist = dirents;
134 return dirents_used;
135
136bad:
137 // Deallocate partially created results.
138 for (size_t i = 0; i < dirents_used; ++i)
139 free(dirents[i]);
140 free(dirents);
141 free(buffer);
142 close(fd);
143 return -1;
144}