• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }