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