Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Assertions.h>
8#include <AK/ScopeGuard.h>
9#include <AK/StdLibExtras.h>
10#include <AK/Vector.h>
11#include <dirent.h>
12#include <errno.h>
13#include <fcntl.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/stat.h>
18#include <syscall.h>
19#include <unistd.h>
20
21extern "C" {
22
23// https://pubs.opengroup.org/onlinepubs/9699919799/functions/opendir.html
24DIR* opendir(char const* name)
25{
26 int fd = open(name, O_RDONLY | O_DIRECTORY);
27 if (fd == -1)
28 return nullptr;
29 return fdopendir(fd);
30}
31
32// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html
33DIR* fdopendir(int fd)
34{
35 if (fd == -1)
36 return nullptr;
37 DIR* dirp = (DIR*)malloc(sizeof(DIR));
38 dirp->fd = fd;
39 dirp->buffer = nullptr;
40 dirp->buffer_size = 0;
41 dirp->nextptr = nullptr;
42 return dirp;
43}
44
45// https://pubs.opengroup.org/onlinepubs/9699919799/functions/closedir.html
46int closedir(DIR* dirp)
47{
48 if (!dirp || dirp->fd == -1)
49 return -EBADF;
50 free(dirp->buffer);
51 int rc = close(dirp->fd);
52 if (rc == 0)
53 dirp->fd = -1;
54 free(dirp);
55 return rc;
56}
57
58// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rewinddir.html
59void rewinddir(DIR* dirp)
60{
61 free(dirp->buffer);
62 dirp->buffer = nullptr;
63 dirp->buffer_size = 0;
64 dirp->nextptr = nullptr;
65 lseek(dirp->fd, 0, SEEK_SET);
66}
67
68struct [[gnu::packed]] sys_dirent {
69 ino_t ino;
70 u8 file_type;
71 u32 namelen;
72 char name[];
73 size_t total_size()
74 {
75 return sizeof(ino_t) + sizeof(u8) + sizeof(u32) + sizeof(char) * namelen;
76 }
77};
78
79static void create_struct_dirent(sys_dirent* sys_ent, struct dirent* str_ent)
80{
81 str_ent->d_ino = sys_ent->ino;
82 str_ent->d_type = sys_ent->file_type;
83 str_ent->d_off = 0;
84 str_ent->d_reclen = sizeof(struct dirent);
85
86 VERIFY(sizeof(str_ent->d_name) > sys_ent->namelen);
87
88 // Note: We can't use any normal string function as sys_ent->name is
89 // not null terminated. All string copy functions will attempt to read
90 // the non-existent null terminator past the end of the source string.
91 memcpy(str_ent->d_name, sys_ent->name, sys_ent->namelen);
92 str_ent->d_name[sys_ent->namelen] = '\0';
93}
94
95static int allocate_dirp_buffer(DIR* dirp)
96{
97 if (dirp->buffer) {
98 return 0;
99 }
100
101 struct stat st;
102 // preserve errno since this could be a reentrant call
103 int old_errno = errno;
104 int rc = fstat(dirp->fd, &st);
105 if (rc < 0) {
106 int new_errno = errno;
107 errno = old_errno;
108 return new_errno;
109 }
110 size_t size_to_allocate = max(st.st_size, static_cast<off_t>(4096));
111 dirp->buffer = (char*)malloc(size_to_allocate);
112 if (!dirp->buffer)
113 return ENOMEM;
114 for (;;) {
115 ssize_t nread = syscall(SC_get_dir_entries, dirp->fd, dirp->buffer, size_to_allocate);
116 if (nread < 0) {
117 if (nread == -EINVAL) {
118 size_to_allocate *= 2;
119 char* new_buffer = (char*)realloc(dirp->buffer, size_to_allocate);
120 if (new_buffer) {
121 dirp->buffer = new_buffer;
122 continue;
123 } else {
124 nread = -ENOMEM;
125 }
126 }
127 // uh-oh, the syscall returned an error
128 free(dirp->buffer);
129 dirp->buffer = nullptr;
130 return -nread;
131 }
132 dirp->buffer_size = nread;
133 dirp->nextptr = dirp->buffer;
134 break;
135 }
136 return 0;
137}
138
139// https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
140dirent* readdir(DIR* dirp)
141{
142 if (!dirp)
143 return nullptr;
144 if (dirp->fd == -1)
145 return nullptr;
146
147 if (int new_errno = allocate_dirp_buffer(dirp)) {
148 // readdir is allowed to mutate errno
149 errno = new_errno;
150 return nullptr;
151 }
152
153 if (dirp->nextptr >= (dirp->buffer + dirp->buffer_size))
154 return nullptr;
155
156 auto* sys_ent = (sys_dirent*)dirp->nextptr;
157 create_struct_dirent(sys_ent, &dirp->cur_ent);
158
159 dirp->nextptr += sys_ent->total_size();
160 return &dirp->cur_ent;
161}
162
163static bool compare_sys_struct_dirent(sys_dirent* sys_ent, struct dirent* str_ent)
164{
165 size_t namelen = min((size_t)256, sys_ent->namelen);
166 // These fields are guaranteed by create_struct_dirent to be the same
167 return sys_ent->ino == str_ent->d_ino
168 && sys_ent->file_type == str_ent->d_type
169 && sys_ent->total_size() == str_ent->d_reclen
170 && strncmp(sys_ent->name, str_ent->d_name, namelen) == 0;
171}
172
173// https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir_r.html
174int readdir_r(DIR* dirp, struct dirent* entry, struct dirent** result)
175{
176 if (!dirp || dirp->fd == -1) {
177 *result = nullptr;
178 return EBADF;
179 }
180
181 if (int new_errno = allocate_dirp_buffer(dirp)) {
182 *result = nullptr;
183 return new_errno;
184 }
185
186 // This doesn't care about dirp state; seek until we find the entry.
187 // Unfortunately, we can't just compare struct dirent to sys_dirent, so
188 // manually compare the fields. This seems a bit risky, but could work.
189 auto* buffer = dirp->buffer;
190 auto* sys_ent = (sys_dirent*)buffer;
191 bool found = false;
192 while (!(found || buffer >= dirp->buffer + dirp->buffer_size)) {
193 found = compare_sys_struct_dirent(sys_ent, entry);
194
195 // Make sure if we found one, it's the one after (end of buffer or not)
196 buffer += sys_ent->total_size();
197 sys_ent = (sys_dirent*)buffer;
198 }
199
200 // If we found one, but hit end of buffer, then EOD
201 if (found && buffer >= dirp->buffer + dirp->buffer_size) {
202 *result = nullptr;
203 return 0;
204 }
205 // If we never found a match for entry in buffer, start from the beginning
206 else if (!found) {
207 buffer = dirp->buffer;
208 sys_ent = (sys_dirent*)buffer;
209 }
210
211 *result = entry;
212 create_struct_dirent(sys_ent, entry);
213
214 return 0;
215}
216
217// https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html
218int dirfd(DIR* dirp)
219{
220 VERIFY(dirp);
221 return dirp->fd;
222}
223
224// https://pubs.opengroup.org/onlinepubs/9699919799/functions/alphasort.html
225int alphasort(const struct dirent** d1, const struct dirent** d2)
226{
227 return strcoll((*d1)->d_name, (*d2)->d_name);
228}
229
230// https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html
231int scandir(char const* dir_name,
232 struct dirent*** namelist,
233 int (*select)(const struct dirent*),
234 int (*compare)(const struct dirent**, const struct dirent**))
235{
236 auto dir = opendir(dir_name);
237 if (dir == nullptr)
238 return -1;
239 ScopeGuard guard = [&] {
240 closedir(dir);
241 };
242
243 Vector<struct dirent*> tmp_names;
244 ScopeGuard names_guard = [&] {
245 tmp_names.remove_all_matching([&](auto& entry) {
246 free(entry);
247 return true;
248 });
249 };
250
251 while (true) {
252 errno = 0;
253 auto entry = readdir(dir);
254 if (!entry)
255 break;
256
257 // Omit entries the caller chooses to ignore.
258 if (select && !select(entry))
259 continue;
260
261 auto entry_copy = (struct dirent*)malloc(entry->d_reclen);
262 if (!entry_copy)
263 break;
264 memcpy(entry_copy, entry, entry->d_reclen);
265 tmp_names.append(entry_copy);
266 }
267
268 // Propagate any errors encountered while accumulating back to the user.
269 if (errno) {
270 return -1;
271 }
272
273 // Sort the entries if the user provided a comparator.
274 if (compare) {
275 qsort(tmp_names.data(), tmp_names.size(), sizeof(struct dirent*), (int (*)(void const*, void const*))compare);
276 }
277
278 int const size = tmp_names.size();
279 auto** names = static_cast<struct dirent**>(kmalloc_array(size, sizeof(struct dirent*)));
280 if (names == nullptr) {
281 return -1;
282 }
283 for (auto i = 0; i < size; i++) {
284 names[i] = tmp_names[i];
285 }
286
287 // Disable the scope guard which free's names on error.
288 tmp_names.clear();
289
290 *namelist = names;
291 return size;
292}
293}