foxygit / doom Log in
commit 134c3506933ccf8ed5d8c00e2711851fbddc9252
Author:     Roman Fomin <rfomin@gmail.com>
AuthorDate: Wed Aug 28 11:35:16 2024 +0700
Commit:     Roman Fomin <rfomin@gmail.com>
CommitDate: Wed Aug 28 13:18:15 2024 +0700

    Rewrite win_opendir.c

    Adapted from https://github.com/win32ports/dirent_h

    * Fixes issues with postfix "." for non-relative paths.

    * Support UTF8 paths as input, so we don't need `M_ConvertUtf8ToSysNativeMB`
      and `M_ConvertSysNativeMBToUtf8` functions.

    * Support long NTFS paths (32768 characters).
---
 configure.ac        |   2 +-
 src/CMakeLists.txt  |   4 +-
 src/Makefile.am     |   5 +-
 src/i_glob.c        |  20 +-
 src/m_misc.c        |  70 ------
 src/m_misc.h        |   2 -
 win32/win_opendir.c | 707 +++++++++++++++++++++++++++++-----------------------
 win32/win_opendir.h | 175 ++++++++-----
 8 files changed, 525 insertions(+), 460 deletions(-)

diff --git a/configure.ac b/configure.ac
index 8ccb9f30..b4d76ac4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -179,7 +179,7 @@ CFLAGS="$CFLAGS $SDL_CFLAGS ${SAMPLERATE_CFLAGS:-} ${PNG_CFLAGS:-} ${FLUIDSYNTH_
 LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-} ${FLUIDSYNTH_LIBS:-}"
 case "$host" in
   *-*-mingw* | *-*-cygwin* | *-*-msvc* )
-    LDFLAGS="$LDFLAGS -lwinmm"
+    LDFLAGS="$LDFLAGS -lwinmm -lshlwapi"
     ;;
   *)
 esac
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8b02fe66..6fbf0790 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -106,7 +106,7 @@ set(GAME_SOURCE_FILES

 set(GAME_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/../")

-if(MSVC)
+if(WIN32)
     list(APPEND GAME_SOURCE_FILES
          "../win32/win_opendir.c" "../win32/win_opendir.h")

@@ -143,7 +143,7 @@ if(FluidSynth_FOUND)
     list(APPEND EXTRA_LIBS FluidSynth::libfluidsynth)
 endif()
 if(WIN32)
-	list(APPEND EXTRA_LIBS winmm)
+	list(APPEND EXTRA_LIBS winmm shlwapi)
 endif()

 if(WIN32)
diff --git a/src/Makefile.am b/src/Makefile.am
index 3d964645..68ec563a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -21,6 +21,7 @@ execgames_SCRIPTS = $(SETUP_BINARIES)
 AM_CFLAGS = -I$(top_srcdir)/textscreen            \
             -I$(top_srcdir)/opl                   \
             -I$(top_srcdir)/pcsound               \
+            -I$(top_srcdir)/win32                 \
             @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@

 # Common source files used by absolutely everything:
@@ -117,7 +118,9 @@ w_file.c             w_file.h              \
 w_file_stdc.c                              \
 w_file_posix.c                             \
 w_file_win32.c                             \
-w_merge.c            w_merge.h
+w_merge.c            w_merge.h             \
+$(top_builddir)/win32/win_opendir.c        \
+$(top_builddir)/win32/win_opendir.h


 MEMORY_NATIVE_SOURCE_FILES=\
diff --git a/src/i_glob.c b/src/i_glob.c
index 90c53107..8b761283 100644
--- a/src/i_glob.c
+++ b/src/i_glob.c
@@ -24,8 +24,7 @@
 #include "m_misc.h"
 #include "config.h"

-#if defined(_MSC_VER)
-// For Visual C++, we need to include the win_opendir module.
+#if defined(_WIN32)
 #include <win_opendir.h>
 #define S_ISDIR(m)      (((m)& S_IFMT) == S_IFDIR)
 #elif defined(HAVE_DIRENT_H)
@@ -102,7 +101,6 @@ glob_t *I_StartMultiGlob(const char *directory, int flags,
     int num_globs;
     glob_t *result;
     va_list args;
-    char *directory_native;

     globs = malloc(sizeof(char *));
     if (globs == NULL)
@@ -141,18 +139,15 @@ glob_t *I_StartMultiGlob(const char *directory, int flags,
         return NULL;
     }

-    directory_native = M_ConvertUtf8ToSysNativeMB(directory);
-
-    result->dir = opendir(directory_native);
+    result->dir = opendir(directory);
     if (result->dir == NULL)
     {
         FreeStringList(globs, num_globs);
         free(result);
-        free(directory_native);
         return NULL;
     }

-    result->directory = directory_native;
+    result->directory = M_StringDuplicate(directory);
     result->globs = globs;
     result->num_globs = num_globs;
     result->flags = flags;
@@ -246,7 +241,6 @@ static boolean MatchesAnyGlob(const char *name, glob_t *glob)
 static char *NextGlob(glob_t *glob)
 {
     struct dirent *de;
-    char *temp, *ret;

     do
     {
@@ -259,13 +253,7 @@ static char *NextGlob(glob_t *glob)
           || !MatchesAnyGlob(de->d_name, glob));

     // Return the fully-qualified path, not just the bare filename.
-    temp = M_StringJoin(glob->directory, DIR_SEPARATOR_S, de->d_name, NULL);
-
-    ret = M_ConvertSysNativeMBToUtf8(temp);
-
-    free(temp);
-
-    return ret;
+    return M_StringJoin(glob->directory, DIR_SEPARATOR_S, de->d_name, NULL);
 }

 static void ReadAllFilenames(glob_t *glob)
diff --git a/src/m_misc.c b/src/m_misc.c
index ace206d4..ba1e1889 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -28,23 +28,15 @@
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 #include <io.h>
-#ifdef _MSC_VER
 #include <direct.h>
-#endif
 #else
 #include <sys/types.h>
 #endif

 #include "doomtype.h"

-#include "deh_str.h"
-
-#include "i_swap.h"
 #include "i_system.h"
-#include "i_video.h"
 #include "m_misc.h"
-#include "v_video.h"
-#include "w_wad.h"
 #include "z_zone.h"

 #ifdef _WIN32
@@ -122,16 +114,6 @@ char *M_ConvertWideToUtf8(const wchar_t *wstr)
     return ConvertWideToMultiByte(wstr, CP_UTF8);
 }

-static wchar_t *ConvertSysNativeMBToWide(const char *str)
-{
-    return ConvertMultiByteToWide(str, CP_ACP);
-}
-
-static char *ConvertWideToSysNativeMB(const wchar_t *wstr)
-{
-    return ConvertWideToMultiByte(wstr, CP_ACP);
-}
-
 // Convert UTF8 string to a wide string. The result is newly allocated and must
 // be freed by the caller after use.

@@ -141,58 +123,6 @@ wchar_t *M_ConvertUtf8ToWide(const char *str)
 }
 #endif

-// Convert multibyte string in system encoding to UTF8. The result is newly
-// allocated and must be freed by the caller after use.
-
-char *M_ConvertSysNativeMBToUtf8(const char *str)
-{
-#ifdef _WIN32
-    char *ret = NULL;
-    wchar_t *wstr = NULL;
-
-    wstr = ConvertSysNativeMBToWide(str);
-
-    if (!wstr)
-    {
-        return NULL;
-    }
-
-    ret = M_ConvertWideToUtf8(wstr);
-
-    free(wstr);
-
-    return ret;
-#else
-    return M_StringDuplicate(str);
-#endif
-}
-
-// Convert UTF8 string to multibyte string in system encoding. The result is
-// newly allocated and must be freed by the caller after use.
-
-char *M_ConvertUtf8ToSysNativeMB(const char *str)
-{
-#ifdef _WIN32
-    char *ret = NULL;
-    wchar_t *wstr = NULL;
-
-    wstr = M_ConvertUtf8ToWide(str);
-
-    if (!wstr)
-    {
-        return NULL;
-    }
-
-    ret = ConvertWideToSysNativeMB(wstr);
-
-    free(wstr);
-
-    return ret;
-#else
-    return M_StringDuplicate(str);
-#endif
-}
-
 FILE *M_fopen(const char *filename, const char *mode)
 {
 #ifdef _WIN32
diff --git a/src/m_misc.h b/src/m_misc.h
index 7b9e72cc..6dc18ccf 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -30,8 +30,6 @@
 wchar_t *M_ConvertUtf8ToWide(const char *str);
 char *M_ConvertWideToUtf8(const wchar_t *wstr);
 #endif
-char *M_ConvertUtf8ToSysNativeMB(const char *str);
-char *M_ConvertSysNativeMBToUtf8(const char *str);

 FILE *M_fopen(const char *filename, const char *mode);
 int M_remove(const char *path);
diff --git a/win32/win_opendir.c b/win32/win_opendir.c
index b2f67805..a03489c9 100644
--- a/win32/win_opendir.c
+++ b/win32/win_opendir.c
@@ -1,336 +1,425 @@
-//
-// 03/10/2006 James Haley
-//
-// For this module only:
-// This code is public domain. No change sufficient enough to constitute a
-// significant or original work has been made, and thus it remains as such.
-//
-//
-// DESCRIPTION:
-//
-// Implementation of POSIX opendir for Visual C++.
-// Derived from the MinGW C Library Extensions Source (released to the
-// public domain). As with other Win32 modules, don't include most DOOM
-// headers into this or conflicts will occur.
-//
-// Original Header:
-//
-// * 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>
-//
-
-#ifndef _MSC_VER
-#error i_opndir.c is for Microsoft Visual C++ only
-#endif
+/*
+MIT License
+Copyright (c) 2019 win32ports
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifdef _WIN32
+
+#include "win_opendir.h"

-#include <stdlib.h>
+#include <stdint.h>
 #include <errno.h>
-#include <string.h>

 #define WIN32_LEAN_AND_MEAN
-#include <windows.h> /* for GetFileAttributes */
+#include <windows.h>
+#include <shlwapi.h>

-#include <tchar.h>
-#define SUFFIX	_T("*")
-#define	SLASH	_T("\\")
+#ifdef _MSC_VER
+#pragma comment(lib, "Shlwapi.lib")
+#endif

-#include "win_opendir.h"
+#ifndef NTFS_MAX_PATH
+#define NTFS_MAX_PATH 32768
+#endif /* NTFS_MAX_PATH */
+
+#ifndef FSCTL_GET_REPARSE_POINT
+#define FSCTL_GET_REPARSE_POINT 0x900a8
+#endif /* FSCTL_GET_REPARSE_POINT */
+
+#ifndef FILE_NAME_NORMALIZED
+#define FILE_NAME_NORMALIZED 0
+#endif /* FILE_NAME_NORMALIZED */

-//
-// opendir
-//
-// Returns a pointer to a DIR structure appropriately filled in to begin
-// searching a directory.
-//
-DIR *opendir(const _TCHAR *szPath)
+int closedir(DIR *dirp)
 {
-   DIR *nd;
-   unsigned int rc;
-   _TCHAR szFullPath[MAX_PATH];
-
-   errno = 0;
-
-   if(!szPath)
-   {
-      errno = EFAULT;
-      return (DIR *)0;
-   }
-
-   if(szPath[0] == _T('\0'))
-   {
-      errno = ENOTDIR;
-      return (DIR *)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 (DIR *)0;
-   }
-   if(!(rc & FILE_ATTRIBUTE_DIRECTORY))
-   {
-      /* Error, entry exists but not a directory. */
-      errno = ENOTDIR;
-      return (DIR *)0;
-   }
-
-   /* Make an absolute pathname.  */
-   _tfullpath(szFullPath, szPath, MAX_PATH);
-
-   /* Allocate enough space to store DIR structure and the complete
-   * directory path given. */
-   nd = (DIR *)(malloc(sizeof(DIR) + (_tcslen(szFullPath)
-                                       + _tcslen(SLASH)
-                                       + _tcslen(SUFFIX) + 1)
-                                     * sizeof(_TCHAR)));
-
-   if(!nd)
-   {
-      /* Error, out of memory. */
-      errno = ENOMEM;
-      return (DIR *)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')
-      && _tcsrchr(nd->dd_name, _T('/'))  != nd->dd_name
-					    + _tcslen(nd->dd_name) - 1
-      && _tcsrchr(nd->dd_name, _T('\\')) != nd->dd_name
-      					    + _tcslen(nd->dd_name) - 1)
-   {
-      _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, FILENAME_MAX);
-
-   return nd;
+    struct __dir *data = NULL;
+    if (!dirp)
+    {
+        errno = EBADF;
+        return -1;
+    }
+    data = (struct __dir *) dirp;
+    CloseHandle((HANDLE) data->fd);
+    free(data->entries);
+    free(data);
+    return 0;
 }

-//
-// readdir
-//
-// Return a pointer to a dirent structure filled with the information on the
-// next entry in the directory.
-//
-struct dirent *readdir(DIR *dirp)
+static void __seterrno(int value)
 {
-   errno = 0;
-
-   /* Check for valid DIR struct. */
-   if(!dirp)
-   {
-      errno = EFAULT;
-      return (struct dirent *)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 dirent *)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 dirent *)0;
+#ifdef _MSC_VER
+    _set_errno(value);
+#else  /* _MSC_VER */
+    errno = value;
+#endif /* _MSC_VER */
 }

+static int __islink(const wchar_t *name, char *buffer)
+{
+    DWORD io_result = 0;
+    DWORD bytes_returned = 0;
+    HANDLE hFile = CreateFileW(
+        name, 0, 0, NULL, OPEN_EXISTING,
+        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0);
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        return 0;
+    }

-//
-// closedir
-//
-// Frees up resources allocated by opendir.
-//
-int closedir(DIR *dirp)
+    io_result = DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer,
+                                MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+                                &bytes_returned, NULL);
+
+    CloseHandle(hFile);
+
+    if (io_result == 0)
+    {
+        return 0;
+    }
+
+    return ((REPARSE_GUID_DATA_BUFFER *) buffer)->ReparseTag ==
+           IO_REPARSE_TAG_SYMLINK;
+}
+
+#pragma pack(push, 1)
+
+typedef struct dirent_FILE_ID_128
+{
+    BYTE Identifier[16];
+} dirent_FILE_ID_128;
+
+typedef struct _dirent_FILE_ID_INFO
+{
+    ULONGLONG VolumeSerialNumber;
+    dirent_FILE_ID_128 FileId;
+} dirent_FILE_ID_INFO;
+
+#pragma pack(pop)
+
+typedef enum dirent_FILE_INFO_BY_HANDLE_CLASS
+{
+    dirent_FileIdInfo = 18
+} dirent_FILE_INFO_BY_HANDLE_CLASS;
+
+static __ino_t __inode(const wchar_t *name)
 {
-   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;
+    __ino_t value = {0};
+    BOOL result;
+    dirent_FILE_ID_INFO fileid;
+    BY_HANDLE_FILE_INFORMATION info;
+    typedef BOOL(__stdcall * pfnGetFileInformationByHandleEx)(
+        HANDLE hFile, dirent_FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
+        LPVOID lpFileInformation, DWORD dwBufferSize);
+
+    HANDLE hKernel32 = GetModuleHandleW(L"kernel32.dll");
+    if (!hKernel32)
+    {
+        return value;
+    }
+
+    pfnGetFileInformationByHandleEx fnGetFileInformationByHandleEx =
+        (pfnGetFileInformationByHandleEx) GetProcAddress(
+            hKernel32, "GetFileInformationByHandleEx");
+    if (!fnGetFileInformationByHandleEx)
+    {
+        return value;
+    }
+
+    HANDLE hFile = CreateFileW(name, GENERIC_READ, FILE_SHARE_READ, NULL,
+                               OPEN_EXISTING, 0, 0);
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        return value;
+    }
+
+    result = fnGetFileInformationByHandleEx(hFile, dirent_FileIdInfo, &fileid,
+                                            sizeof(fileid));
+    if (result)
+    {
+        value.serial = fileid.VolumeSerialNumber;
+        memcpy(value.fileid, fileid.FileId.Identifier, 16);
+    }
+    else
+    {
+        result = GetFileInformationByHandle(hFile, &info);
+        if (result)
+        {
+            value.serial = info.dwVolumeSerialNumber;
+            memcpy(value.fileid + 8, &info.nFileIndexHigh, 4);
+            memcpy(value.fileid + 12, &info.nFileIndexLow, 4);
+        }
+    }
+    CloseHandle(hFile);
+    return value;
 }

-//
-// rewinddir
-//
-// Return to the beginning of the directory "stream". We simply call findclose
-// and then reset things like an opendir.
-//
-void rewinddir(DIR * dirp)
+static DIR *__internal_opendir(wchar_t *wname, int size)
 {
-   errno = 0;
-
-   if(!dirp)
-   {
-      errno = EFAULT;
-      return;
-   }
-
-   if(dirp->dd_handle != -1)
-   {
-      _findclose(dirp->dd_handle);
-   }
-
-   dirp->dd_handle = -1;
-   dirp->dd_stat = 0;
+    struct __dir *data = NULL;
+    struct dirent *tmp_entries = NULL;
+    static char default_char = '?';
+    static wchar_t *prefix = L"\\\\?\\";
+    static wchar_t *suffix = L"\\*.*";
+    static int extra_prefix = 4; /* use prefix "\\?\" to handle long file names */
+    static int extra_suffix = 4; /* use suffix "\*.*" to find everything */
+    WIN32_FIND_DATAW w32fd = {0};
+    HANDLE hFindFile = INVALID_HANDLE_VALUE;
+    static int grow_factor = 2;
+    char *buffer = NULL;
+
+    BOOL relative = PathIsRelativeW(wname + extra_prefix);
+
+    memcpy(wname + size - 1, suffix, sizeof(wchar_t) * extra_suffix);
+    wname[size + extra_suffix - 1] = 0;
+
+    if (relative)
+    {
+        wname += extra_prefix;
+        size -= extra_prefix;
+    }
+    hFindFile = FindFirstFileW(wname, &w32fd);
+    if (INVALID_HANDLE_VALUE == hFindFile)
+    {
+        __seterrno(ENOENT);
+        return NULL;
+    }
+
+    data = (struct __dir *) malloc(sizeof(struct __dir));
+    if (!data)
+    {
+        goto out_of_memory;
+    }
+    wname[size - 1] = 0;
+    data->fd = (intptr_t) CreateFileW(wname, 0, 0, NULL, OPEN_EXISTING,
+                                      FILE_FLAG_BACKUP_SEMANTICS, 0);
+    wname[size - 1] = L'\\';
+    data->count = 16;
+    data->index = 0;
+    data->entries = (struct dirent *) malloc(sizeof(struct dirent) * data->count);
+    if (!data->entries)
+    {
+        goto out_of_memory;
+    }
+    buffer = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+    if (!buffer)
+    {
+        goto out_of_memory;
+    }
+
+    do
+    {
+        WideCharToMultiByte(CP_UTF8, 0, w32fd.cFileName, -1,
+                            data->entries[data->index].d_name, NAME_MAX,
+                            &default_char, NULL);
+
+        memcpy(wname + size, w32fd.cFileName, sizeof(wchar_t) * NAME_MAX);
+
+        if (((w32fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ==
+             FILE_ATTRIBUTE_REPARSE_POINT)
+            && __islink(wname, buffer))
+        {
+            data->entries[data->index].d_type = DT_LNK;
+        }
+        else if ((w32fd.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) ==
+                 FILE_ATTRIBUTE_DEVICE)
+        {
+            data->entries[data->index].d_type = DT_CHR;
+        }
+        else if ((w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
+                 FILE_ATTRIBUTE_DIRECTORY)
+        {
+            data->entries[data->index].d_type = DT_DIR;
+        }
+        else
+        {
+            data->entries[data->index].d_type = DT_REG;
+        }
+
+        data->entries[data->index].d_ino = __inode(wname);
+        data->entries[data->index].d_reclen = sizeof(struct dirent);
+        data->entries[data->index].d_namelen =
+            (unsigned char) wcslen(w32fd.cFileName);
+        data->entries[data->index].d_off = 0;
+
+        if (++data->index == data->count)
+        {
+            tmp_entries = (struct dirent *) realloc(
+                data->entries,
+                sizeof(struct dirent) * data->count * grow_factor);
+            if (!tmp_entries)
+            {
+                goto out_of_memory;
+            }
+            data->entries = tmp_entries;
+            data->count *= grow_factor;
+        }
+    } while (FindNextFileW(hFindFile, &w32fd) != 0);
+
+    free(buffer);
+    FindClose(hFindFile);
+
+    data->count = data->index;
+    data->index = 0;
+    return (DIR *) data;
+
+out_of_memory:
+    if (data)
+    {
+        if (INVALID_HANDLE_VALUE != (HANDLE) data->fd)
+        {
+            CloseHandle((HANDLE) data->fd);
+        }
+        free(data->entries);
+    }
+    free(buffer);
+    free(data);
+    if (INVALID_HANDLE_VALUE != hFindFile)
+    {
+        FindClose(hFindFile);
+    }
+    __seterrno(ENOMEM);
+    return NULL;
+}
+
+static wchar_t *__get_buffer()
+{
+    wchar_t *name = malloc(sizeof(wchar_t) * (NTFS_MAX_PATH + NAME_MAX + 8));
+    if (name)
+    {
+        memcpy(name, L"\\\\?\\", sizeof(wchar_t) * 4);
+    }
+    return name;
 }

-//
-// 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 telldir(DIR *dirp)
+DIR *opendir(const char *name)
 {
-   errno = 0;
-
-   if(!dirp)
-   {
-      errno = EFAULT;
-      return -1;
-   }
-   return dirp->dd_stat;
+    DIR *dirp = NULL;
+    wchar_t *wname = __get_buffer();
+    int size = 0;
+    if (!wname)
+    {
+        errno = ENOMEM;
+        return NULL;
+    }
+    size = MultiByteToWideChar(CP_UTF8, 0, name, -1, wname + 4, NTFS_MAX_PATH);
+    if (0 == size)
+    {
+        free(wname);
+        return NULL;
+    }
+    dirp = __internal_opendir(wname, size + 4);
+    free(wname);
+    return dirp;
 }

-//
-// 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 seekdir(DIR *dirp, long lPos)
+DIR *_wopendir(const wchar_t *name)
 {
-   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. */
-      rewinddir(dirp);
-
-      while((dirp->dd_stat < lPos) && readdir(dirp))
-         ; /* do-nothing loop */
-   }
+    DIR *dirp = NULL;
+    wchar_t *wname = __get_buffer();
+    int size = 0;
+    if (!wname)
+    {
+        errno = ENOMEM;
+        return NULL;
+    }
+    size = (int) wcslen(name);
+    if (size > NTFS_MAX_PATH)
+    {
+        free(wname);
+        return NULL;
+    }
+    memcpy(wname + 4, name, sizeof(wchar_t) * (size + 1));
+    dirp = __internal_opendir(wname, size + 5);
+    free(wname);
+    return dirp;
+}
+
+DIR *fdopendir(intptr_t fd)
+{
+    DIR *dirp = NULL;
+    wchar_t *wname = __get_buffer();
+    int size = 0;
+
+    if (!wname)
+    {
+        errno = ENOMEM;
+        return NULL;
+    }
+    size = GetFinalPathNameByHandleW((HANDLE) fd, wname + 4, NTFS_MAX_PATH,
+                                     FILE_NAME_NORMALIZED);
+    if (0 == size)
+    {
+        free(wname);
+        errno = ENOTDIR;
+        return NULL;
+    }
+    dirp = __internal_opendir(wname, size + 5);
+    free(wname);
+    return dirp;
 }

-// EOF
+struct dirent *readdir(DIR *dirp)
+{
+    struct __dir *data = (struct __dir *) dirp;
+    if (!data)
+    {
+        errno = EBADF;
+        return NULL;
+    }
+    if (data->index < data->count)
+    {
+        return &data->entries[data->index++];
+    }
+    return NULL;
+}
+
+void seekdir(DIR *dirp, long int offset)
+{
+    if (dirp)
+    {
+        struct __dir *data = (struct __dir *) dirp;
+        data->index = (offset < data->count) ? offset : data->index;
+    }
+}
+
+void rewinddir(DIR *dirp)
+{
+    seekdir(dirp, 0);
+}
+
+long int telldir(DIR *dirp)
+{
+    if (!dirp)
+    {
+        errno = EBADF;
+        return -1;
+    }
+    return ((struct __dir *) dirp)->count;
+}
+
+intptr_t dirfd(DIR *dirp)
+{
+    if (!dirp)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+    return ((struct __dir *) dirp)->fd;
+}

+#endif /* _WIN32 */
diff --git a/win32/win_opendir.h b/win32/win_opendir.h
index c1844e11..94c9d2c8 100644
--- a/win32/win_opendir.h
+++ b/win32/win_opendir.h
@@ -1,73 +1,130 @@
-//
-// 03/10/2006 James Haley
-//
-// For this module only:
-// This code is public domain. No change sufficient enough to constitute a
-// significant or original work has been made, and thus it remains as such.
-//
-//
-// DESCRIPTION:
-//
-// Implementation of POSIX opendir for Visual C++.
-// Derived from the MinGW C Library Extensions Source (released to the
-//  public domain).
-//
-
-#ifndef I_OPNDIR_H__
-#define I_OPNDIR_H__
-
-#include <io.h>
-
-#ifndef FILENAME_MAX
-#define FILENAME_MAX 260
-#endif
+/*
+MIT License
+Copyright (c) 2019 win32ports
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifdef _WIN32
+
+#ifndef WIN_OPENDIR_H
+#define WIN_OPENDIR_H
+
+#include <sys/types.h>
+
+#ifndef NAME_MAX
+#define NAME_MAX 260
+#endif /* NAME_MAX */
+
+#ifndef DT_UNKNOWN
+#define DT_UNKNOWN 0
+#endif /* DT_UNKNOWN */
+
+#ifndef DT_FIFO
+#define DT_FIFO 1
+#endif /* DT_FIFO */
+
+#ifndef DT_CHR
+#define DT_CHR 2
+#endif /* DT_CHR */
+
+#ifndef DT_DIR
+#define DT_DIR 4
+#endif /* DT_DIR */
+
+#ifndef DT_BLK
+#define DT_BLK 6
+#endif /* DT_BLK */
+
+#ifndef DT_REG
+#define DT_REG 8
+#endif /* DT_REF */
+
+#ifndef DT_LNK
+#define DT_LNK 10
+#endif /* DT_LNK */
+
+#ifndef DT_SOCK
+#define DT_SOCK 12
+#endif /* DT_SOCK */
+
+#ifndef DT_WHT
+#define DT_WHT 14
+#endif /* DT_WHT */
+
+#ifndef _DIRENT_HAVE_D_NAMLEN
+#define _DIRENT_HAVE_D_NAMLEN 1
+#endif /* _DIRENT_HAVE_D_NAMLEN */
+
+#ifndef _DIRENT_HAVE_D_RECLEN
+#define _DIRENT_HAVE_D_RECLEN 1
+#endif /* _DIRENT_HAVE_D_RECLEN */
+
+#ifndef _DIRENT_HAVE_D_OFF
+#define _DIRENT_HAVE_D_OFF 1
+#endif /* _DIRENT_HAVE_D_OFF */
+
+#ifndef _DIRENT_HAVE_D_TYPE
+#define _DIRENT_HAVE_D_TYPE 1
+#endif /* _DIRENT_HAVE_D_TYPE  */
+
+typedef void *DIR;
+
+typedef struct ino_t
+{
+    unsigned long long serial;
+    unsigned char fileid[16];
+} __ino_t;

 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]; /* File name. */
+    __ino_t d_ino;
+    off_t d_off;
+    unsigned short d_reclen;
+    unsigned char d_namelen;
+    unsigned char d_type;
+    char d_name[NAME_MAX];
 };

-/*
- * 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).
- */
-typedef struct
+struct __dir
 {
-   /* disk transfer area for this dir */
-   struct _finddata_t dd_dta;
+    struct dirent *entries;
+    intptr_t fd;
+    long int count;
+    long int index;
+};
+
+int closedir(DIR *dirp);
+
+DIR *opendir(const char *name);
+
+DIR *_wopendir(const wchar_t *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 dirent dd_dir;
+DIR *fdopendir(intptr_t fd);

-   /* _findnext handle */
-   intptr_t	dd_handle;
+struct dirent *readdir(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;
+void seekdir(DIR *dirp, long int offset);

-   /* given path for dir with search pattern (struct is extended) */
-   char dd_name[1];
-} DIR;
+void rewinddir(DIR *dirp);

-DIR *opendir(const char *);
-struct dirent *readdir(DIR *);
-int closedir(DIR *);
-void rewinddir(DIR *);
-long telldir(DIR *);
-void seekdir(DIR *, long);
+long int telldir(DIR *dirp);

-#endif
+intptr_t dirfd(DIR *dirp);

-// EOF
+#endif /* WIN_OPENDIR_H */

+#endif /* _WIN32 */