1 // Copyright (C) 2025 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 // Implementation file (dirent.cpp)
15 #include "dirent.h"
16
17 #include <errno.h>
18 #include <windows.h>
19
20 #include <algorithm>
21 #include <codecvt>
22 #include <locale>
23 #include <memory>
24 #include <string>
25
26 namespace {
27
28 using file_index_t = uint64_t;
29
30 using DereferencedHandle = std::remove_pointer_t<HANDLE>;
31 struct HandleCloser {
operator ()__anonf0b3b8120111::HandleCloser32 void operator()(HANDLE h) const {
33 if (h != INVALID_HANDLE_VALUE) {
34 ::CloseHandle(h);
35 }
36 }
37 };
38
39 using UniqueHandle = std::unique_ptr<DereferencedHandle, HandleCloser>;
40
41 // Translates Windows error codes to errno values
translate_windows_error_to_errno(DWORD errorCode)42 int translate_windows_error_to_errno(DWORD errorCode) {
43 switch (errorCode) {
44 case ERROR_SUCCESS:
45 return 0;
46 case ERROR_FILE_NOT_FOUND:
47 case ERROR_PATH_NOT_FOUND:
48 return ENOENT;
49 case ERROR_ACCESS_DENIED:
50 return EACCES;
51 case ERROR_ALREADY_EXISTS:
52 case ERROR_FILE_EXISTS:
53 return EEXIST;
54 case ERROR_INVALID_PARAMETER:
55 case ERROR_INVALID_NAME:
56 return EINVAL;
57 case ERROR_NOT_ENOUGH_MEMORY:
58 case ERROR_OUTOFMEMORY:
59 return ENOMEM;
60 case ERROR_WRITE_PROTECT:
61 return EROFS;
62 case ERROR_HANDLE_EOF:
63 return EPIPE;
64 case ERROR_HANDLE_DISK_FULL:
65 case ERROR_DISK_FULL:
66 return ENOSPC;
67 case ERROR_NOT_SUPPORTED:
68 return ENOTSUP;
69 case ERROR_DIRECTORY:
70 return ENOTDIR;
71 case ERROR_DIR_NOT_EMPTY:
72 return ENOTEMPTY;
73 case ERROR_BAD_PATHNAME:
74 return ENOENT;
75 case ERROR_OPERATION_ABORTED:
76 return EINTR;
77 case ERROR_INVALID_HANDLE:
78 return EBADF;
79 case ERROR_FILENAME_EXCED_RANGE:
80 case ERROR_CANT_RESOLVE_FILENAME:
81 return ENAMETOOLONG;
82 case ERROR_DEV_NOT_EXIST:
83 return ENODEV;
84 case ERROR_TOO_MANY_OPEN_FILES:
85 return EMFILE;
86 default:
87 return EIO;
88 }
89 }
90
91 // Get file index information
get_file_index(const std::wstring & path)92 file_index_t get_file_index(const std::wstring& path) {
93 UniqueHandle file(CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES,
94 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
95 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
96
97 if (file.get() == INVALID_HANDLE_VALUE) {
98 return 0;
99 }
100
101 BY_HANDLE_FILE_INFORMATION info;
102 if (!GetFileInformationByHandle(file.get(), &info)) {
103 return 0;
104 }
105
106 return (static_cast<file_index_t>(info.nFileIndexHigh) << 32) | info.nFileIndexLow;
107 }
108
109 // Convert UTF-8 to wide string
utf8_to_wide(const std::string & input)110 std::wstring utf8_to_wide(const std::string& input) {
111 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
112 return converter.from_bytes(input);
113 }
114
115 // Convert wide string to UTF-8
wide_to_utf8(const std::wstring & input)116 std::string wide_to_utf8(const std::wstring& input) {
117 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
118 return converter.to_bytes(input);
119 }
120
121 // Prepare directory path for Windows API
prepare_dir_path(const std::wstring & path)122 std::wstring prepare_dir_path(const std::wstring& path) {
123 // Check if path already has extended-length prefix
124 if (path.rfind(L"\\\\?\\", 0) == 0) {
125 return path;
126 }
127
128 // Add extended-length prefix
129 return L"\\\\?\\" + path;
130 }
131
132 // Create search path with wildcard
create_search_path(const std::wstring & dir_path)133 std::wstring create_search_path(const std::wstring& dir_path) {
134 std::wstring search_path = dir_path;
135 if (!search_path.empty() && search_path.back() != L'\\') {
136 search_path += L"\\";
137 }
138 search_path += L"*";
139 return search_path;
140 }
141
142 } // namespace
143
144 // Internal DIR structure (hidden from users)
145 struct InternalDir {
146 HANDLE handle;
147 WIN32_FIND_DATAW find_data;
148 dirent entry;
149 std::wstring path; // Original path (wide)
150 std::wstring search_path; // Search path with pattern
151 bool first;
152 bool end_reached;
153 long current_position; // Current position in the directory
154
155 // Constructor
InternalDirInternalDir156 InternalDir()
157 : handle(INVALID_HANDLE_VALUE), first(true), end_reached(false), current_position(0) {
158 memset(&entry, 0, sizeof(dirent));
159 }
160
161 // Destructor
~InternalDirInternalDir162 ~InternalDir() {
163 if (handle != INVALID_HANDLE_VALUE) {
164 FindClose(handle);
165 }
166 }
167
168 private:
169 // Prevent copying and assignment to maintain unique ownership
170 InternalDir(const InternalDir&) = delete;
171 InternalDir& operator=(const InternalDir&) = delete;
172 };
173
174 // Opaque DIR type (declared in header)
175 struct DIR {
176 std::unique_ptr<InternalDir> pImpl; // std::unique_ptr to hold the internal structure
177
DIRDIR178 DIR() : pImpl(std::make_unique<InternalDir>()) {}
179
180 private:
181 // Prevent copying and assignment to maintain unique ownership
182 DIR(const DIR&) = delete;
183 DIR& operator=(const DIR&) = delete;
184 };
185
opendir(const char * name)186 DIR* opendir(const char* name) {
187 if (!name) {
188 errno = EINVAL;
189 return nullptr;
190 }
191
192 // Convert to wide string
193 std::wstring wide_path = utf8_to_wide(name);
194 if (wide_path.empty() && !std::string(name).empty()) {
195 errno = EINVAL;
196 return nullptr;
197 }
198
199 // Check if path exists and is a directory
200 DWORD attrs = GetFileAttributesW(wide_path.c_str());
201 if (attrs == INVALID_FILE_ATTRIBUTES) {
202 errno = translate_windows_error_to_errno(GetLastError());
203 return nullptr;
204 }
205
206 if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
207 errno = ENOTDIR;
208 return nullptr;
209 }
210
211 // Prepare directory path
212 std::wstring dir_path = prepare_dir_path(wide_path);
213
214 // Create search path
215 std::wstring search_path = create_search_path(dir_path);
216
217 // Allocate and initialize DIR structure using unique_ptr
218 std::unique_ptr<DIR> dir = std::make_unique<DIR>();
219 if (!dir) {
220 errno = ENOMEM;
221 return nullptr;
222 }
223
224 // Initialize InternalDir structure
225 dir->pImpl->handle = FindFirstFileW(search_path.c_str(), &dir->pImpl->find_data);
226 if (dir->pImpl->handle == INVALID_HANDLE_VALUE) {
227 errno = translate_windows_error_to_errno(GetLastError());
228 return nullptr;
229 }
230
231 dir->pImpl->path = dir_path;
232 dir->pImpl->search_path = search_path;
233 dir->pImpl->first = true;
234 dir->pImpl->end_reached = false;
235
236 return dir.release(); // Release ownership to the caller
237 }
238
readdir(DIR * dirp)239 struct dirent* readdir(DIR* dirp) {
240 if (!dirp) {
241 errno = EBADF;
242 return nullptr;
243 }
244
245 if (dirp->pImpl->end_reached) {
246 return nullptr;
247 }
248
249 while (true) {
250 if (!dirp->pImpl->first && !FindNextFileW(dirp->pImpl->handle, &dirp->pImpl->find_data)) {
251 DWORD lastError = GetLastError();
252 if (lastError == ERROR_NO_MORE_FILES) {
253 dirp->pImpl->end_reached = true;
254 return nullptr;
255 } else {
256 errno = translate_windows_error_to_errno(lastError);
257 return nullptr;
258 }
259 }
260 dirp->pImpl->first = false;
261
262 // Skip "." and ".." entries
263 if (wcscmp(dirp->pImpl->find_data.cFileName, L".") == 0 ||
264 wcscmp(dirp->pImpl->find_data.cFileName, L"..") == 0) {
265 continue;
266 }
267
268 // Convert filename to UTF-8
269 std::string utf8_filename = wide_to_utf8(dirp->pImpl->find_data.cFileName);
270 if (utf8_filename.empty() && !std::wstring(dirp->pImpl->find_data.cFileName).empty()) {
271 errno = ENAMETOOLONG;
272 return nullptr;
273 }
274
275 // Copy filename to dirent structure, with bounds checking
276 if (utf8_filename.length() >= sizeof(dirp->pImpl->entry.d_name)) {
277 errno = ENAMETOOLONG;
278 return nullptr;
279 }
280 strcpy(dirp->pImpl->entry.d_name, utf8_filename.c_str());
281
282 // Get full path for the current file
283 std::wstring fullPath = dirp->pImpl->path + L"\\" + dirp->pImpl->find_data.cFileName;
284
285 // Get file index information
286 dirp->pImpl->entry.d_ino = get_file_index(fullPath);
287
288 // Increment position after successfully reading an entry
289 dirp->pImpl->current_position++;
290
291 return &dirp->pImpl->entry;
292 }
293 }
294
closedir(DIR * dirp)295 int closedir(DIR* dirp) {
296 if (!dirp) {
297 errno = EBADF;
298 return -1;
299 }
300
301 // Destructor of unique_ptr<InternalDir> will be called automatically,
302 // releasing resources held by InternalDir.
303
304 delete dirp; // Release memory held by DIR
305 return 0;
306 }
307
rewinddir(DIR * dirp)308 void rewinddir(DIR* dirp) {
309 if (!dirp) {
310 errno = EBADF;
311 return;
312 }
313
314 if (dirp->pImpl->handle != INVALID_HANDLE_VALUE) {
315 FindClose(dirp->pImpl->handle);
316 }
317
318 dirp->pImpl->handle = FindFirstFileW(dirp->pImpl->search_path.c_str(), &dirp->pImpl->find_data);
319 if (dirp->pImpl->handle == INVALID_HANDLE_VALUE) {
320 errno = translate_windows_error_to_errno(GetLastError());
321 return;
322 }
323 dirp->pImpl->first = true;
324 dirp->pImpl->end_reached = false;
325 dirp->pImpl->current_position = 0; // Reset position
326 }
327
telldir(DIR * dirp)328 long telldir(DIR* dirp) {
329 if (!dirp) {
330 errno = EBADF;
331 return -1;
332 }
333 return dirp->pImpl->end_reached ? -1 : dirp->pImpl->current_position;
334 }
335
seekdir(DIR * dirp,long loc)336 void seekdir(DIR* dirp, long loc) {
337 if (!dirp) {
338 errno = EBADF;
339 return;
340 }
341
342 if (loc == 0) {
343 rewinddir(dirp);
344 } else if (loc == -1) {
345 // Seeking to the end is equivalent to reading until the end
346 while (readdir(dirp) != nullptr);
347 } else if (loc > 0) {
348 // Seek forward to a specific position
349 rewinddir(dirp); // Start from the beginning
350 for (long i = 0; i < loc; ++i) {
351 if (readdir(dirp) == nullptr) {
352 // Reached the end before the desired position
353 errno = EINVAL;
354 return;
355 }
356 }
357 } else {
358 errno = EINVAL; // Negative positions other than -1 are not supported
359 return;
360 }
361 }