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