feat: Implement dirent API for Windows using WinAPI am: 97208f5344
Original change: https://android-review.googlesource.com/c/platform/hardware/google/aemu/+/3472741
Change-Id: Ib22927b197d19785fb07997db1e54a48fc6935b1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
GitOrigin-RevId: f9bf6abdc327cf21ebb854c1c30522d820a0be9f
diff --git a/windows/BUILD b/windows/BUILD
index db73f90..b3977d1 100644
--- a/windows/BUILD
+++ b/windows/BUILD
@@ -1,3 +1,5 @@
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+
cc_library(
name = "compat-hdrs",
hdrs = glob(["includes/**/*.h"]),
@@ -11,13 +13,13 @@
cc_library(
name = "compat",
- srcs =
- glob([
- "src/dirent/*.c",
- "src/*.c",
- "src/*.h",
- "src/*.cpp",
- ]),
+ srcs = [
+ "src/dirent/dirent.cpp",
+ ] + glob([
+ "src/*.c",
+ "src/*.h",
+ "src/*.cpp",
+ ]),
defines = [
"WIN32_LEAN_AND_MEAN",
],
@@ -35,3 +37,14 @@
visibility = ["//visibility:public"],
deps = [":compat-hdrs"],
)
+
+cc_test(
+ name = "dirent_test",
+ srcs = [
+ "tests/dirent_test.cpp",
+ ],
+ deps = [
+ ":compat",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/windows/includes/dirent/dirent.h b/windows/includes/dirent/dirent.h
index 81bad74..04e40a1 100644
--- a/windows/includes/dirent/dirent.h
+++ b/windows/includes/dirent/dirent.h
@@ -1,127 +1,191 @@
-/*
- * DIRENT.H (formerly DIRLIB.H)
- * This file has no copyright assigned and is placed in the Public Domain.
- * This file is a part of the mingw-runtime package.
- * No warranty is given; refer to the file DISCLAIMER within the package.
- *
- */
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
#ifndef _AEMU_DIRENT_H_
#define _AEMU_DIRENT_H_
-#include <stdio.h>
-#include <io.h>
-
-#ifndef RC_INVOKED
+#include <sys/types.h>
+#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
-struct dirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[FILENAME_MAX+1]; /* File name plus nul delimiter. */
+/**
+ * @file dirent.h
+ * @brief A POSIX-like dirent API implementation for Windows using the Windows API.
+ *
+ * This header provides a subset of the POSIX dirent API for Windows, allowing C and C++
+ * code to use familiar functions like opendir(), readdir(), closedir(), etc. to
+ * iterate through directory entries.
+ *
+ * @warning **Limitations:**
+ * - **`telldir()` and `seekdir()` are minimally implemented.** `seekdir()` only supports
+ * seeking to the beginning (loc = 0), the end (loc = -1), or forward to a specific entry
+ * by its index (loc > 0). Seeking to arbitrary positions is implemented by iterating
+ * through the entries, making it an **O(N)** operation in the worst case, where N is
+ * the desired position. `telldir()` returns the index of the last entry read by `readdir()`.
+ * - **`d_ino` is implemented using Windows file index.** It does not represent a
+ * true POSIX inode number but can be used to identify files uniquely.
+ * - **`d_reclen` is not supported.** The field is not present in this implementation.
+ * - **Thread safety:** This implementation is not inherently thread-safe. Using the
+ * same `DIR` pointer from multiple threads simultaneously can lead to undefined
+ * behavior.
+ *
+ * @note **Windows-Specific Behavior:**
+ * - Filenames are stored in `d_name` as **UTF-8** encoded strings.
+ * - Extended-length paths (longer than `MAX_PATH`) are supported using the `\\?\` prefix.
+ * - The implementation uses the Windows API (`FindFirstFileW`, `FindNextFileW`, etc.)
+ * internally.
+ * - The `DIR` type is an opaque pointer to an internal structure.
+ */
+
+/**
+ * @brief The maximum length of a file name, including the null terminator.
+ *
+ * This is set to `MAX_PATH` (260) for compatibility but internally the implementation
+ * supports extended-length paths using the `\\?\` prefix.
+ */
+#define FILENAME_MAX MAX_PATH
+
+/**
+ * @brief Represents a directory entry.
+ */
+struct dirent {
+ /**
+ * @brief File ID (from the Windows file index).
+ *
+ * This is not a true POSIX inode number but can be used as a unique file
+ * identifier on Windows. It is obtained using `GetFileInformationByHandle`
+ * and represents a file's unique ID within a volume.
+ * @warning This field might not be fully unique across different volumes or over time.
+ */
+ uint64_t d_ino;
+
+ /**
+ * @brief Null-terminated file name in UTF-8 encoding.
+ *
+ * @warning The maximum length of the filename (excluding the null terminator)
+ * that can be stored in this field is `FILENAME_MAX`. If a filename exceeds this
+ * limit, `readdir` will skip the entry and set `errno` to `ENAMETOOLONG`.
+ */
+ char d_name[FILENAME_MAX];
};
-#ifdef _WIN64
-#define INTPTR __int64
-#else
-#define INTPTR long
-#endif
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- * dd_stat field is now int (was short in older versions).
+/**
+ * @brief An opaque type representing a directory stream.
*/
-typedef struct
-{
- /* disk transfer area for this dir */
- struct _finddata_t dd_dta;
-
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct dirent dd_dir;
-
- /* _findnext handle */
- INTPTR dd_handle;
-
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
-
- /* given path for dir with search pattern (struct is extended) */
- char dd_name[1];
-} DIR;
-
-DIR* __cdecl opendir (const char*);
-struct dirent* __cdecl readdir (DIR*);
-int __cdecl closedir (DIR*);
-void __cdecl rewinddir (DIR*);
-long __cdecl telldir (DIR*);
-void __cdecl seekdir (DIR*, long);
+typedef struct DIR DIR;
-/* wide char versions */
-
-struct _wdirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- wchar_t d_name[FILENAME_MAX+1]; /* File name plus nul delimiter. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
+/**
+ * @brief Opens a directory stream for reading.
+ *
+ * @param name The path to the directory to open. This should be a UTF-8 encoded string.
+ *
+ * @return A pointer to a `DIR` structure representing the opened directory stream,
+ * or `nullptr` if an error occurred. If `nullptr` is returned, `errno` is set
+ * to indicate the error.
+ *
+ * @retval EACCES Search permission is denied for the directory.
+ * @retval EMFILE The maximum number of file descriptors are already open.
+ * @retval ENFILE The maximum number of files are already open in the system.
+ * @retval ENOENT The named directory does not exist or is an empty string.
+ * @retval ENOMEM Insufficient memory is available.
+ * @retval ENOTDIR A component of the path is not a directory.
+ * @retval EINVAL The `name` argument is invalid (e.g., contains invalid characters).
*/
-typedef struct
-{
- /* disk transfer area for this dir */
- struct _wfinddata_t dd_dta;
+DIR* opendir(const char* name);
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct _wdirent dd_dir;
+/**
+ * @brief Reads the next directory entry from a directory stream.
+ *
+ * @param dirp A pointer to a `DIR` structure returned by `opendir()`.
+ *
+ * @return A pointer to a `dirent` structure representing the next directory entry,
+ * or `nullptr` if the end of the directory stream is reached or an error
+ * occurred. If `nullptr` is returned and `errno` is not 0, an error occurred.
+ *
+ * @retval EBADF The `dirp` argument does not refer to an open directory stream.
+ * @retval ENOMEM Insufficient memory is available.
+ * @retval ENOENT No more directory entries.
+ * @retval EIO An I/O error occurred.
+ * @retval ENAMETOOLONG A filename exceeded `FILENAME_MAX`.
+ */
+struct dirent* readdir(DIR* dirp);
- /* _findnext handle */
- INTPTR dd_handle;
+/**
+ * @brief Closes a directory stream.
+ *
+ * @param dirp A pointer to a `DIR` structure returned by `opendir()`.
+ *
+ * @return 0 on success, -1 on failure. If -1 is returned, `errno` is set to
+ * indicate the error.
+ *
+ * @retval EBADF The `dirp` argument does not refer to an open directory stream.
+ */
+int closedir(DIR* dirp);
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
+/**
+ * @brief Resets the position of a directory stream to the beginning.
+ *
+ * @param dirp A pointer to a `DIR` structure returned by `opendir()`.
+ *
+ * @retval EBADF The `dirp` argument does not refer to an open directory stream.
+ * @retval EIO An I/O error occurred.
+ */
+void rewinddir(DIR* dirp);
+/**
+ * @brief Gets the current position of a directory stream.
+ *
+ * @param dirp A pointer to a `DIR` structure returned by `opendir()`.
+ *
+ * @return The current position of the directory stream. This is the index of the last
+ * entry read by `readdir()`. Returns -1 if at the end of the directory stream.
+ * If -1 is returned and `errno` is not 0, an error occurred.
+ *
+ * @retval EBADF The `dirp` argument does not refer to an open directory stream.
+ *
+ * @note The position returned by `telldir()` is an opaque value that should only be
+ * used in conjunction with `seekdir()`.
+ */
+long telldir(DIR* dirp);
- /* given path for dir with search pattern (struct is extended) */
- wchar_t dd_name[1];
-} _WDIR;
+/**
+ * @brief Sets the position of a directory stream.
+ *
+ * @param dirp A pointer to a `DIR` structure returned by `opendir()`.
+ * @param loc The new position of the directory stream. The following values are supported:
+ * - **0:** Seek to the beginning of the stream (equivalent to `rewinddir()`).
+ * - **-1:** Seek to the end of the stream.
+ * - **\>0:** Seek to a specific entry by its index (the value returned by `telldir()`).
+ *
+ * @retval EBADF The `dirp` argument does not refer to an open directory stream.
+ * @retval EINVAL The `loc` argument is invalid (e.g., negative value other than -1, or a
+ * value that is greater than the number of entries in the directory).
+ *
+ * @note Seeking to arbitrary positions (other than the beginning or end) is implemented
+ * by rewinding the directory stream and then calling `readdir()` repeatedly until
+ * the desired position is reached.
+ * @note **Time Complexity:**
+ * - O(1) for `loc = 0` (rewind) and `loc = -1` (seek to end).
+ * - O(N) for `loc > 0`, where N is the position being sought to. In the worst case,
+ * seeking to the end of a large directory can be a slow operation.
+ */
+void seekdir(DIR* dirp, long loc);
-
-
-_WDIR* __cdecl _wopendir (const wchar_t*);
-struct _wdirent* __cdecl _wreaddir (_WDIR*);
-int __cdecl _wclosedir (_WDIR*);
-void __cdecl _wrewinddir (_WDIR*);
-long __cdecl _wtelldir (_WDIR*);
-void __cdecl _wseekdir (_WDIR*, long);
-
-
-#ifdef __cplusplus
+#ifdef __cplusplus
}
#endif
-#endif /* Not RC_INVOKED */
-
-#endif /* Not _AEMU_DIRENT_H_ */
+#endif /* Not _AEMU_DIRENT_H_ */
\ No newline at end of file
diff --git a/windows/src/dirent/dirent.c b/windows/src/dirent/dirent.c
deleted file mode 100644
index d9200f9..0000000
--- a/windows/src/dirent/dirent.c
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * dirent.c
- * This file has no copyright assigned and is placed in the Public Domain.
- * This file is a part of the mingw-runtime package.
- * No warranty is given; refer to the file DISCLAIMER within the package.
- *
- * Derived from DIRLIB.C by Matt J. Weinstein
- * This note appears in the DIRLIB.H
- * DIRLIB.H by M. J. Weinstein Released to public domain 1-Jan-89
- *
- * Updated by Jeremy Bettis <jeremy@hksys.com>
- * Significantly revised and rewinddir, seekdir and telldir added by Colin
- * Peters <colin@fu.is.saga-u.ac.jp>
- *
- */
-
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-#include <io.h>
-#include <direct.h>
-
-#include "dirent.h"
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h> /* for GetFileAttributes */
-
-#include <tchar.h>
-
-#ifdef _UNICODE
-#define _tdirent _wdirent
-#define _TDIR _WDIR
-#define _topendir _wopendir
-#define _tclosedir _wclosedir
-#define _treaddir _wreaddir
-#define _trewinddir _wrewinddir
-#define _ttelldir _wtelldir
-#define _tseekdir _wseekdir
-#else
-#define _tdirent dirent
-#define _TDIR DIR
-#define _topendir opendir
-#define _tclosedir closedir
-#define _treaddir readdir
-#define _trewinddir rewinddir
-#define _ttelldir telldir
-#define _tseekdir seekdir
-#endif
-
-#define SUFFIX _T("*")
-#define SLASH _T("\\")
-
-
-/*
- * opendir
- *
- * Returns a pointer to a DIR structure appropriately filled in to begin
- * searching a directory.
- */
-_TDIR *
-_topendir (const _TCHAR *szPath)
-{
- _TDIR *nd;
- unsigned int rc;
- _TCHAR szFullPath[MAX_PATH];
-
- errno = 0;
-
- if (!szPath)
- {
- errno = EFAULT;
- return (_TDIR *) 0;
- }
-
- if (szPath[0] == _T('\0'))
- {
- errno = ENOTDIR;
- return (_TDIR *) 0;
- }
-
- /* Attempt to determine if the given path really is a directory. */
- rc = GetFileAttributes (szPath);
- if (rc == (unsigned int)-1)
- {
- /* call GetLastError for more error info */
- errno = ENOENT;
- return (_TDIR *) 0;
- }
- if (!(rc & FILE_ATTRIBUTE_DIRECTORY))
- {
- /* Error, entry exists but not a directory. */
- errno = ENOTDIR;
- return (_TDIR *) 0;
- }
-
- /* Make an absolute pathname. */
- _tfullpath (szFullPath, szPath, MAX_PATH);
-
- /* Allocate enough space to store DIR structure and the complete
- * directory path given. */
- nd = (_TDIR *) malloc (sizeof (_TDIR) + (_tcslen(szFullPath) + _tcslen (SLASH) +
- _tcslen(SUFFIX) + 1) * sizeof(_TCHAR));
-
- if (!nd)
- {
- /* Error, out of memory. */
- errno = ENOMEM;
- return (_TDIR *) 0;
- }
-
- /* Create the search expression. */
- _tcscpy (nd->dd_name, szFullPath);
-
- /* Add on a slash if the path does not end with one. */
- if (nd->dd_name[0] != _T('\0') &&
- nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('/') &&
- nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('\\'))
- {
- _tcscat (nd->dd_name, SLASH);
- }
-
- /* Add on the search pattern */
- _tcscat (nd->dd_name, SUFFIX);
-
- /* Initialize handle to -1 so that a premature closedir doesn't try
- * to call _findclose on it. */
- nd->dd_handle = -1;
-
- /* Initialize the status. */
- nd->dd_stat = 0;
-
- /* Initialize the dirent structure. ino and reclen are invalid under
- * Win32, and name simply points at the appropriate part of the
- * findfirst_t structure. */
- nd->dd_dir.d_ino = 0;
- nd->dd_dir.d_reclen = 0;
- nd->dd_dir.d_namlen = 0;
- memset (nd->dd_dir.d_name, 0, sizeof (nd->dd_dir.d_name));
-
- return nd;
-}
-
-
-/*
- * readdir
- *
- * Return a pointer to a dirent structure filled with the information on the
- * next entry in the directory.
- */
-struct _tdirent *
-_treaddir (_TDIR * dirp)
-{
- errno = 0;
-
- /* Check for valid DIR struct. */
- if (!dirp)
- {
- errno = EFAULT;
- return (struct _tdirent *) 0;
- }
-
- if (dirp->dd_stat < 0)
- {
- /* We have already returned all files in the directory
- * (or the structure has an invalid dd_stat). */
- return (struct _tdirent *) 0;
- }
- else if (dirp->dd_stat == 0)
- {
- /* We haven't started the search yet. */
- /* Start the search */
- dirp->dd_handle = _tfindfirst (dirp->dd_name, &(dirp->dd_dta));
-
- if (dirp->dd_handle == -1)
- {
- /* Whoops! Seems there are no files in that
- * directory. */
- dirp->dd_stat = -1;
- }
- else
- {
- dirp->dd_stat = 1;
- }
- }
- else
- {
- /* Get the next search entry. */
- if (_tfindnext (dirp->dd_handle, &(dirp->dd_dta)))
- {
- /* We are off the end or otherwise error.
- _findnext sets errno to ENOENT if no more file
- Undo this. */
- DWORD winerr = GetLastError();
- if (winerr == ERROR_NO_MORE_FILES)
- errno = 0;
- _findclose (dirp->dd_handle);
- dirp->dd_handle = -1;
- dirp->dd_stat = -1;
- }
- else
- {
- /* Update the status to indicate the correct
- * number. */
- dirp->dd_stat++;
- }
- }
-
- if (dirp->dd_stat > 0)
- {
- /* Successfully got an entry. Everything about the file is
- * already appropriately filled in except the length of the
- * file name. */
- dirp->dd_dir.d_namlen = _tcslen (dirp->dd_dta.name);
- _tcscpy (dirp->dd_dir.d_name, dirp->dd_dta.name);
- return &dirp->dd_dir;
- }
-
- return (struct _tdirent *) 0;
-}
-
-
-/*
- * closedir
- *
- * Frees up resources allocated by opendir.
- */
-int
-_tclosedir (_TDIR * dirp)
-{
- int rc;
-
- errno = 0;
- rc = 0;
-
- if (!dirp)
- {
- errno = EFAULT;
- return -1;
- }
-
- if (dirp->dd_handle != -1)
- {
- rc = _findclose (dirp->dd_handle);
- }
-
- /* Delete the dir structure. */
- free (dirp);
-
- return rc;
-}
-
-/*
- * rewinddir
- *
- * Return to the beginning of the directory "stream". We simply call findclose
- * and then reset things like an opendir.
- */
-void
-_trewinddir (_TDIR * dirp)
-{
- errno = 0;
-
- if (!dirp)
- {
- errno = EFAULT;
- return;
- }
-
- if (dirp->dd_handle != -1)
- {
- _findclose (dirp->dd_handle);
- }
-
- dirp->dd_handle = -1;
- dirp->dd_stat = 0;
-}
-
-/*
- * telldir
- *
- * Returns the "position" in the "directory stream" which can be used with
- * seekdir to go back to an old entry. We simply return the value in stat.
- */
-long
-_ttelldir (_TDIR * dirp)
-{
- errno = 0;
-
- if (!dirp)
- {
- errno = EFAULT;
- return -1;
- }
- return dirp->dd_stat;
-}
-
-/*
- * seekdir
- *
- * Seek to an entry previously returned by telldir. We rewind the directory
- * and call readdir repeatedly until either dd_stat is the position number
- * or -1 (off the end). This is not perfect, in that the directory may
- * have changed while we weren't looking. But that is probably the case with
- * any such system.
- */
-void
-_tseekdir (_TDIR * dirp, long lPos)
-{
- errno = 0;
-
- if (!dirp)
- {
- errno = EFAULT;
- return;
- }
-
- if (lPos < -1)
- {
- /* Seeking to an invalid position. */
- errno = EINVAL;
- return;
- }
- else if (lPos == -1)
- {
- /* Seek past end. */
- if (dirp->dd_handle != -1)
- {
- _findclose (dirp->dd_handle);
- }
- dirp->dd_handle = -1;
- dirp->dd_stat = -1;
- }
- else
- {
- /* Rewind and read forward to the appropriate index. */
- _trewinddir (dirp);
-
- while ((dirp->dd_stat < lPos) && _treaddir (dirp))
- ;
- }
-}
diff --git a/windows/src/dirent/dirent.cpp b/windows/src/dirent/dirent.cpp
new file mode 100644
index 0000000..56e7d79
--- /dev/null
+++ b/windows/src/dirent/dirent.cpp
@@ -0,0 +1,361 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// Implementation file (dirent.cpp)
+#include "dirent.h"
+
+#include <errno.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <codecvt>
+#include <locale>
+#include <memory>
+#include <string>
+
+namespace {
+
+using file_index_t = uint64_t;
+
+using DereferencedHandle = std::remove_pointer_t<HANDLE>;
+struct HandleCloser {
+ void operator()(HANDLE h) const {
+ if (h != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(h);
+ }
+ }
+};
+
+using UniqueHandle = std::unique_ptr<DereferencedHandle, HandleCloser>;
+
+// Translates Windows error codes to errno values
+int translate_windows_error_to_errno(DWORD errorCode) {
+ switch (errorCode) {
+ case ERROR_SUCCESS:
+ return 0;
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ return ENOENT;
+ case ERROR_ACCESS_DENIED:
+ return EACCES;
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_FILE_EXISTS:
+ return EEXIST;
+ case ERROR_INVALID_PARAMETER:
+ case ERROR_INVALID_NAME:
+ return EINVAL;
+ case ERROR_NOT_ENOUGH_MEMORY:
+ case ERROR_OUTOFMEMORY:
+ return ENOMEM;
+ case ERROR_WRITE_PROTECT:
+ return EROFS;
+ case ERROR_HANDLE_EOF:
+ return EPIPE;
+ case ERROR_HANDLE_DISK_FULL:
+ case ERROR_DISK_FULL:
+ return ENOSPC;
+ case ERROR_NOT_SUPPORTED:
+ return ENOTSUP;
+ case ERROR_DIRECTORY:
+ return ENOTDIR;
+ case ERROR_DIR_NOT_EMPTY:
+ return ENOTEMPTY;
+ case ERROR_BAD_PATHNAME:
+ return ENOENT;
+ case ERROR_OPERATION_ABORTED:
+ return EINTR;
+ case ERROR_INVALID_HANDLE:
+ return EBADF;
+ case ERROR_FILENAME_EXCED_RANGE:
+ case ERROR_CANT_RESOLVE_FILENAME:
+ return ENAMETOOLONG;
+ case ERROR_DEV_NOT_EXIST:
+ return ENODEV;
+ case ERROR_TOO_MANY_OPEN_FILES:
+ return EMFILE;
+ default:
+ return EIO;
+ }
+}
+
+// Get file index information
+file_index_t get_file_index(const std::wstring& path) {
+ UniqueHandle file(CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+
+ if (file.get() == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ BY_HANDLE_FILE_INFORMATION info;
+ if (!GetFileInformationByHandle(file.get(), &info)) {
+ return 0;
+ }
+
+ return (static_cast<file_index_t>(info.nFileIndexHigh) << 32) | info.nFileIndexLow;
+}
+
+// Convert UTF-8 to wide string
+std::wstring utf8_to_wide(const std::string& input) {
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ return converter.from_bytes(input);
+}
+
+// Convert wide string to UTF-8
+std::string wide_to_utf8(const std::wstring& input) {
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ return converter.to_bytes(input);
+}
+
+// Prepare directory path for Windows API
+std::wstring prepare_dir_path(const std::wstring& path) {
+ // Check if path already has extended-length prefix
+ if (path.rfind(L"\\\\?\\", 0) == 0) {
+ return path;
+ }
+
+ // Add extended-length prefix
+ return L"\\\\?\\" + path;
+}
+
+// Create search path with wildcard
+std::wstring create_search_path(const std::wstring& dir_path) {
+ std::wstring search_path = dir_path;
+ if (!search_path.empty() && search_path.back() != L'\\') {
+ search_path += L"\\";
+ }
+ search_path += L"*";
+ return search_path;
+}
+
+} // namespace
+
+// Internal DIR structure (hidden from users)
+struct InternalDir {
+ HANDLE handle;
+ WIN32_FIND_DATAW find_data;
+ dirent entry;
+ std::wstring path; // Original path (wide)
+ std::wstring search_path; // Search path with pattern
+ bool first;
+ bool end_reached;
+ long current_position; // Current position in the directory
+
+ // Constructor
+ InternalDir()
+ : handle(INVALID_HANDLE_VALUE), first(true), end_reached(false), current_position(0) {
+ memset(&entry, 0, sizeof(dirent));
+ }
+
+ // Destructor
+ ~InternalDir() {
+ if (handle != INVALID_HANDLE_VALUE) {
+ FindClose(handle);
+ }
+ }
+
+ private:
+ // Prevent copying and assignment to maintain unique ownership
+ InternalDir(const InternalDir&) = delete;
+ InternalDir& operator=(const InternalDir&) = delete;
+};
+
+// Opaque DIR type (declared in header)
+struct DIR {
+ std::unique_ptr<InternalDir> pImpl; // std::unique_ptr to hold the internal structure
+
+ DIR() : pImpl(std::make_unique<InternalDir>()) {}
+
+ private:
+ // Prevent copying and assignment to maintain unique ownership
+ DIR(const DIR&) = delete;
+ DIR& operator=(const DIR&) = delete;
+};
+
+DIR* opendir(const char* name) {
+ if (!name) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ // Convert to wide string
+ std::wstring wide_path = utf8_to_wide(name);
+ if (wide_path.empty() && !std::string(name).empty()) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ // Check if path exists and is a directory
+ DWORD attrs = GetFileAttributesW(wide_path.c_str());
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ errno = translate_windows_error_to_errno(GetLastError());
+ return nullptr;
+ }
+
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+ errno = ENOTDIR;
+ return nullptr;
+ }
+
+ // Prepare directory path
+ std::wstring dir_path = prepare_dir_path(wide_path);
+
+ // Create search path
+ std::wstring search_path = create_search_path(dir_path);
+
+ // Allocate and initialize DIR structure using unique_ptr
+ std::unique_ptr<DIR> dir = std::make_unique<DIR>();
+ if (!dir) {
+ errno = ENOMEM;
+ return nullptr;
+ }
+
+ // Initialize InternalDir structure
+ dir->pImpl->handle = FindFirstFileW(search_path.c_str(), &dir->pImpl->find_data);
+ if (dir->pImpl->handle == INVALID_HANDLE_VALUE) {
+ errno = translate_windows_error_to_errno(GetLastError());
+ return nullptr;
+ }
+
+ dir->pImpl->path = dir_path;
+ dir->pImpl->search_path = search_path;
+ dir->pImpl->first = true;
+ dir->pImpl->end_reached = false;
+
+ return dir.release(); // Release ownership to the caller
+}
+
+struct dirent* readdir(DIR* dirp) {
+ if (!dirp) {
+ errno = EBADF;
+ return nullptr;
+ }
+
+ if (dirp->pImpl->end_reached) {
+ return nullptr;
+ }
+
+ while (true) {
+ if (!dirp->pImpl->first && !FindNextFileW(dirp->pImpl->handle, &dirp->pImpl->find_data)) {
+ DWORD lastError = GetLastError();
+ if (lastError == ERROR_NO_MORE_FILES) {
+ dirp->pImpl->end_reached = true;
+ return nullptr;
+ } else {
+ errno = translate_windows_error_to_errno(lastError);
+ return nullptr;
+ }
+ }
+ dirp->pImpl->first = false;
+
+ // Skip "." and ".." entries
+ if (wcscmp(dirp->pImpl->find_data.cFileName, L".") == 0 ||
+ wcscmp(dirp->pImpl->find_data.cFileName, L"..") == 0) {
+ continue;
+ }
+
+ // Convert filename to UTF-8
+ std::string utf8_filename = wide_to_utf8(dirp->pImpl->find_data.cFileName);
+ if (utf8_filename.empty() && !std::wstring(dirp->pImpl->find_data.cFileName).empty()) {
+ errno = ENAMETOOLONG;
+ return nullptr;
+ }
+
+ // Copy filename to dirent structure, with bounds checking
+ if (utf8_filename.length() >= sizeof(dirp->pImpl->entry.d_name)) {
+ errno = ENAMETOOLONG;
+ return nullptr;
+ }
+ strcpy(dirp->pImpl->entry.d_name, utf8_filename.c_str());
+
+ // Get full path for the current file
+ std::wstring fullPath = dirp->pImpl->path + L"\\" + dirp->pImpl->find_data.cFileName;
+
+ // Get file index information
+ dirp->pImpl->entry.d_ino = get_file_index(fullPath);
+
+ // Increment position after successfully reading an entry
+ dirp->pImpl->current_position++;
+
+ return &dirp->pImpl->entry;
+ }
+}
+
+int closedir(DIR* dirp) {
+ if (!dirp) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Destructor of unique_ptr<InternalDir> will be called automatically,
+ // releasing resources held by InternalDir.
+
+ delete dirp; // Release memory held by DIR
+ return 0;
+}
+
+void rewinddir(DIR* dirp) {
+ if (!dirp) {
+ errno = EBADF;
+ return;
+ }
+
+ if (dirp->pImpl->handle != INVALID_HANDLE_VALUE) {
+ FindClose(dirp->pImpl->handle);
+ }
+
+ dirp->pImpl->handle = FindFirstFileW(dirp->pImpl->search_path.c_str(), &dirp->pImpl->find_data);
+ if (dirp->pImpl->handle == INVALID_HANDLE_VALUE) {
+ errno = translate_windows_error_to_errno(GetLastError());
+ return;
+ }
+ dirp->pImpl->first = true;
+ dirp->pImpl->end_reached = false;
+ dirp->pImpl->current_position = 0; // Reset position
+}
+
+long telldir(DIR* dirp) {
+ if (!dirp) {
+ errno = EBADF;
+ return -1;
+ }
+ return dirp->pImpl->end_reached ? -1 : dirp->pImpl->current_position;
+}
+
+void seekdir(DIR* dirp, long loc) {
+ if (!dirp) {
+ errno = EBADF;
+ return;
+ }
+
+ if (loc == 0) {
+ rewinddir(dirp);
+ } else if (loc == -1) {
+ // Seeking to the end is equivalent to reading until the end
+ while (readdir(dirp) != nullptr);
+ } else if (loc > 0) {
+ // Seek forward to a specific position
+ rewinddir(dirp); // Start from the beginning
+ for (long i = 0; i < loc; ++i) {
+ if (readdir(dirp) == nullptr) {
+ // Reached the end before the desired position
+ errno = EINVAL;
+ return;
+ }
+ }
+ } else {
+ errno = EINVAL; // Negative positions other than -1 are not supported
+ return;
+ }
+}
\ No newline at end of file
diff --git a/windows/tests/dirent_test.cpp b/windows/tests/dirent_test.cpp
new file mode 100644
index 0000000..27254c3
--- /dev/null
+++ b/windows/tests/dirent_test.cpp
@@ -0,0 +1,318 @@
+#include <gtest/gtest.h>
+
+#include "dirent.h"
+
+#include <codecvt>
+#include <filesystem>
+#include <fstream>
+#include <locale>
+#include <random>
+
+namespace fs = std::filesystem;
+
+// Helper function to create a directory with a specific name
+void createDirectory(const fs::path& dirName) { fs::create_directories(dirName); }
+
+// Helper function to create a file with a specific name
+void createFile(const fs::path& filename) {
+ std::ofstream file(filename);
+ file.close();
+}
+
+// Helper function to convert UTF-8 to wide string
+std::wstring utf8ToWide(const std::string& utf8Str) {
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ return converter.from_bytes(utf8Str);
+}
+
+// Helper function to convert wide string to UTF-8
+std::string wideToUtf8(const std::wstring& wideStr) {
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ return converter.to_bytes(wideStr);
+}
+
+class DirentTest : public ::testing::Test {
+ protected:
+ // Setup - create a temporary directory for testing
+ void SetUp() override {
+ // Generate a random directory name
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_int_distribution<> dist(0, 15); // For hex characters
+
+ std::string randomDirName = "dirent_test_";
+ for (int i = 0; i < 8; ++i) {
+ randomDirName += "0123456789abcdef"[dist(gen)];
+ }
+
+ tempDir = fs::temp_directory_path() / randomDirName;
+ fs::create_directories(tempDir);
+ }
+
+ // Teardown - remove the temporary directory
+ void TearDown() override {
+ try {
+ fs::remove_all(tempDir);
+ } catch (const fs::filesystem_error& e) {
+ std::cerr << "Warning failed to remove directory: " << e.what() << std::endl;
+ }
+ }
+
+ fs::path tempDir;
+};
+
+// Test opendir with an invalid directory name
+TEST_F(DirentTest, OpenDirInvalid) {
+ DIR* dir = opendir("invalid_dir");
+ ASSERT_EQ(nullptr, dir);
+ ASSERT_EQ(ENOENT, errno);
+}
+
+// Test opendir with a valid directory name
+TEST_F(DirentTest, OpenDirValid) {
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+ closedir(dir);
+}
+
+// Test readdir with an empty directory
+TEST_F(DirentTest, ReadDirEmpty) {
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+ struct dirent* entry = readdir(dir);
+ ASSERT_EQ(nullptr, entry);
+ closedir(dir);
+}
+
+// Test readdir with some files
+TEST_F(DirentTest, ReadDirBasic) {
+ createFile(tempDir / "file1.txt");
+ createFile(tempDir / "file2.txt");
+
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+
+ struct dirent* entry;
+ int count = 0;
+ while ((entry = readdir(dir)) != nullptr) {
+ ASSERT_TRUE(strcmp(entry->d_name, "file1.txt") == 0 ||
+ strcmp(entry->d_name, "file2.txt") == 0);
+ count++;
+ }
+ ASSERT_EQ(2, count);
+
+ closedir(dir);
+}
+
+// Test readdir with UTF-8 filenames
+TEST_F(DirentTest, ReadDirUtf8) {
+ std::wstring filename = L"hiফাইলhi.txt";
+
+ // We expect a utf8 filename..
+ std::string filenameu8 = wideToUtf8(filename); // u8"hiফাইলhi.txt";
+ createFile(tempDir / filename);
+
+ ASSERT_TRUE(fs::exists(tempDir / filename));
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+
+ struct dirent* entry = readdir(dir);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(filenameu8, entry->d_name);
+
+ closedir(dir);
+}
+
+// Test rewinddir
+TEST_F(DirentTest, RewindDir) {
+ createFile(tempDir / "file1.txt");
+ createFile(tempDir / "file2.txt");
+
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+
+ // Read the first entry
+ struct dirent* entry1 = readdir(dir);
+ ASSERT_NE(nullptr, entry1);
+
+ // Rewind the directory
+ rewinddir(dir);
+
+ // Read the first entry again
+ struct dirent* entry2 = readdir(dir);
+ ASSERT_NE(nullptr, entry2);
+ ASSERT_STREQ(entry1->d_name, entry2->d_name);
+
+ closedir(dir);
+}
+
+// Test telldir/seekdir (limited functionality)
+TEST_F(DirentTest, TellSeekDir) {
+ createFile(tempDir / "file1.txt");
+ createFile(tempDir / "file2.txt");
+ createFile(tempDir / "file3.txt");
+
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+
+ // Get initial position (should be 0)
+ long initialPos = telldir(dir);
+ ASSERT_EQ(0, initialPos);
+
+ // Read the first entry
+ struct dirent* entry1 = readdir(dir);
+ ASSERT_NE(nullptr, entry1);
+
+ // Get position (should be 1 now)
+ long pos1 = telldir(dir);
+ ASSERT_EQ(1, pos1);
+
+ // Read the second entry
+ struct dirent* entry2 = readdir(dir);
+ ASSERT_NE(nullptr, entry2);
+
+ // Get position (should be 2 now)
+ long pos2 = telldir(dir);
+ ASSERT_EQ(2, pos2);
+
+ // Seek to beginning
+ seekdir(dir, 0);
+ long currentPos = telldir(dir);
+ ASSERT_EQ(0, currentPos);
+
+ // Verify we can read again from the beginning
+ struct dirent* entry3 = readdir(dir);
+ ASSERT_NE(nullptr, entry3);
+ ASSERT_STREQ(entry1->d_name, entry3->d_name);
+
+ // Seek to position 1
+ seekdir(dir, 1);
+ currentPos = telldir(dir);
+ ASSERT_EQ(1, currentPos);
+
+ // Verify we can read the second entry again
+ struct dirent* entry4 = readdir(dir);
+ ASSERT_NE(nullptr, entry4);
+ ASSERT_STREQ(entry2->d_name, entry4->d_name);
+
+ // Seek to end
+ seekdir(dir, -1);
+ currentPos = telldir(dir);
+ ASSERT_EQ(-1, currentPos);
+
+ // Check that readdir returns nullptr after seekdir(-1)
+ struct dirent* entry5 = readdir(dir);
+ ASSERT_EQ(nullptr, entry5);
+
+ // Seek to position 2
+ seekdir(dir, 2);
+ currentPos = telldir(dir);
+ ASSERT_EQ(2, currentPos);
+
+ // Read the third entry
+ struct dirent* entry6 = readdir(dir);
+ ASSERT_NE(nullptr, entry6);
+ ASSERT_STREQ("file3.txt", entry6->d_name);
+
+ // Try seeking beyond the end
+ seekdir(dir, 10);
+ currentPos = telldir(dir);
+ ASSERT_EQ(errno, EINVAL); // Bad!
+
+ // Verify that readdir returns nullptr
+ struct dirent* entry7 = readdir(dir);
+ ASSERT_EQ(nullptr, entry7);
+
+ closedir(dir);
+}
+
+// Test closedir
+TEST_F(DirentTest, CloseDir) {
+ DIR* dir = opendir(tempDir.string().c_str());
+ ASSERT_NE(nullptr, dir);
+ int result = closedir(dir);
+ ASSERT_EQ(0, result);
+}
+
+// Test extended path
+TEST_F(DirentTest, ExtendedPath) {
+ // Create a path that exceeds MAX_PATH
+ std::wstring longDirName = L"\\\\?\\" + tempDir.wstring() + L"\\long_directory_name";
+ for (int i = 0; i < 30; ++i) {
+ longDirName += L"\\subdir";
+ }
+
+ // Create the long directory structure
+ ASSERT_TRUE(fs::create_directories(longDirName));
+
+ // Create a file within the long directory
+ std::wstring longFileName = longDirName + L"\\file.txt";
+ std::ofstream file(longFileName);
+ ASSERT_TRUE(file.is_open());
+ file.close();
+
+ // Convert to UTF-8 for opendir
+ std::string longDirNameUtf8 = wideToUtf8(longDirName);
+
+ // Open the directory using opendir
+ DIR* dir = opendir(longDirNameUtf8.c_str());
+ ASSERT_NE(nullptr, dir);
+
+ // Read directory entries
+ struct dirent* entry;
+ bool found = false;
+ while ((entry = readdir(dir)) != nullptr) {
+ if (strcmp(entry->d_name, "file.txt") == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ // Check if the file was found
+ ASSERT_TRUE(found);
+
+ // Close the directory
+ closedir(dir);
+
+ // Cleanup
+ fs::remove(longFileName);
+ ASSERT_FALSE(fs::exists(longFileName));
+}
+
+// Test various error conditions
+TEST_F(DirentTest, ErrorConditions) {
+ // Invalid directory name
+ DIR* dir = opendir(nullptr);
+ ASSERT_EQ(nullptr, dir);
+ ASSERT_EQ(EINVAL, errno);
+
+ // Directory not found
+ dir = opendir("nonexistent_directory");
+ ASSERT_EQ(nullptr, dir);
+ ASSERT_EQ(ENOENT, errno);
+
+ // Not a directory
+ createFile(tempDir / "file.txt");
+ dir = opendir((tempDir / "file.txt").c_str());
+ ASSERT_EQ(nullptr, dir);
+ ASSERT_EQ(ENOTDIR, errno);
+
+ // Invalid DIR pointer
+ struct dirent* entry = readdir(nullptr);
+ ASSERT_EQ(nullptr, entry);
+ ASSERT_EQ(EBADF, errno);
+
+ int result = closedir(nullptr);
+ ASSERT_EQ(-1, result);
+ ASSERT_EQ(EBADF, errno);
+
+ rewinddir(nullptr);
+ ASSERT_EQ(EBADF, errno);
+
+ seekdir(nullptr, 0);
+ ASSERT_EQ(EBADF, errno);
+
+ long pos = telldir(nullptr);
+ ASSERT_EQ(-1, pos);
+ ASSERT_EQ(EBADF, errno);
+}
\ No newline at end of file