Serenity Operating System
at master 293 lines 8.1 kB view raw
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}