I attached preview patches for a new version that uses FindFirstFileW.
Perhaps these could be the final version, but I would appreciate if you
had time to comment these shortly at some point (no rush).
The main change is just one huge patch instead of multiple smaller
pieces. :-( Unfortunately I think it cannot be split well because many
changes depend on each other. Perhaps the addition of d_type and
_readdir_8dot3 could be split but those are fairly small pieces too.
I suggest reading dirent.c as a file instead of as a diff.
Some ponderings:
- I changed NAME_MAX to (MAX_PATH - 1) * 3 = 777. The commit message
explains why.
- get_d_type(): This should be conservative in sense that it should
never incorrectly return DT_REG, DT_DIR, or DT_LNK. It is fine to
return DT_UNKNOWN when unsure. With this in mind, I wonder if
FILE_ATTRIBUTE_PINNED and FILE_ATTRIBUTE_UNPINNED are OK in
supported_attrs, or if something else could be improved.
- Is _readdir_8dot3() worth it? It's not a lot of code, but every
little feature adds up. I suppose it at least is more useful than a
function that does best-fit mapping.
- The supported errors from FindFirstFileW likely lacks something,
and the unknown ones are reported as errno = EIO. In practice it
hopefully is good enough though. The errno values from _wfindfirst
aren't perfect either.
- I think it's valuable to say at the top of dirent.h the version of
mingw-w64 where the dirent code was revised. I already put 13.0.0
there but it might have been too optimistic.
<limits.h> requires non-standard _POSIX_ to expose NAME_MAX even though
some other parts of the headers check for _POSIX_C_SOURCE or
_XOPEN_SOURCE or _GNU_SOURCE etc. This should be improved but I didn't
attempt it now. While related to dirent, it's a separate issue.
A quick web search found even a recent example of NAME_MAX and _POSIX_.
See especially the end of the comment 4:
https://sourceware.org/bugzilla/show_bug.cgi?id=32243
If it exists in MSVC, I guess changing it in mingw-w64 might cause
incompatibility somewhere, especially if NAME_MAX happened to be used
in some DLL API header. *sigh*
--
Lasse Collin
From 0d98dea4c49e80c30a81c39068ece3ea5a0e5b9a Mon Sep 17 00:00:00 2001
From: Lasse Collin <[email protected]>
Date: Sat, 15 Feb 2025 18:02:45 +0200
Subject: [PATCH 1/3] crt: dirent: Update the disclaimer header and clean up
white space
Signed-off-by: Lasse Collin <[email protected]>
---
mingw-w64-crt/misc/dirent.c | 7 +++----
mingw-w64-headers/crt/dirent.h | 5 ++---
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/mingw-w64-crt/misc/dirent.c b/mingw-w64-crt/misc/dirent.c
index 4897cf492..cf95f9091 100644
--- a/mingw-w64-crt/misc/dirent.c
+++ b/mingw-w64-crt/misc/dirent.c
@@ -1,8 +1,8 @@
/*
* dirent.c
* This file has no copyright assigned and is placed in the Public Domain.
- * This file is part of the mingw-runtime package.
- * No warranty is given; refer to the file DISCLAIMER within the package.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
*
* Derived from DIRLIB.C by Matt J. Weinstein
* This note appears in the DIRLIB.H
@@ -11,7 +11,7 @@
* Updated by Jeremy Bettis <[email protected]>
* Significantly revised and rewinddir, seekdir and telldir added by Colin
* Peters <[email protected]>
- *
+ *
*/
#ifndef WIN32_LEAN_AND_MEAN
@@ -322,4 +322,3 @@ _tseekdir (_TDIR * dirp, long lPos)
;
}
}
-
diff --git a/mingw-w64-headers/crt/dirent.h b/mingw-w64-headers/crt/dirent.h
index 2d7a1b73f..421b09278 100644
--- a/mingw-w64-headers/crt/dirent.h
+++ b/mingw-w64-headers/crt/dirent.h
@@ -1,8 +1,8 @@
/*
* DIRENT.H (formerly DIRLIB.H)
* This file has no copyright assigned and is placed in the Public Domain.
- * This file is part of the mingw-runtime package.
- * No warranty is given; refer to the file DISCLAIMER within the package.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
*
*/
@@ -124,4 +124,3 @@ void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
#endif /* Not RC_INVOKED */
#endif /* Not _DIRENT_H_ */
-
--
2.48.1
From 3682160bbc21c574aa64d2735faa2a996b67e004 Mon Sep 17 00:00:00 2001
From: Lasse Collin <[email protected]>
Date: Sat, 15 Feb 2025 18:02:45 +0200
Subject: [PATCH 2/3] crt: Increase NAME_MAX from 255 to 777 (ABI break)
NAME_MAX is a POSIX constant; Windows doesn't define it. For this
reason, assume that NAME_MAX is only about filenames in multibyte
representation.
One wide character (UTF-16 code unit) may require up to three bytes
in UTF-8. (Four-byte UTF-8 characters consume two UTF-16 code units.)
In most cases, filenames are limited to 255 wide characters plus the
terminating \0, so defining NAME_MAX to 765 (3 * 255) should be
sufficient for all UTF-8 filenames. However, since MAX_PATH appears
in Win32 API data structures, let's use (MAX_PATH - 1) * 3 = 777
instead. 12 extra bytes don't matter much, and this way it's very
clear that NAME_MAX is large enough.
There seems to be no Windows locale that supports a code page with
longer encodings. For example, a single UTF-16 code unit may produce
four bytes in GB18030 but it cannot be used as a locale code page.
Signed-off-by: Lasse Collin <[email protected]>
---
mingw-w64-headers/crt/limits.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mingw-w64-headers/crt/limits.h b/mingw-w64-headers/crt/limits.h
index f0145dfb1..883f88053 100644
--- a/mingw-w64-headers/crt/limits.h
+++ b/mingw-w64-headers/crt/limits.h
@@ -105,7 +105,7 @@
#define LINK_MAX 1024
#define MAX_CANON _POSIX_MAX_CANON
#define MAX_INPUT _POSIX_MAX_INPUT
-#define NAME_MAX 255
+#define NAME_MAX 777
#define NGROUPS_MAX 16
#define OPEN_MAX 32
#undef PATH_MAX
--
2.48.1
From 1c08f1f9c85e034700288a2c2c01eb2c830821f9 Mon Sep 17 00:00:00 2001
From: Lasse Collin <[email protected]>
Date: Sat, 15 Feb 2025 18:02:45 +0200
Subject: [PATCH 3/3] crt: dirent: Revise thoroughly (ABI break)
Switch from _findfirst/_wfindfirst to FindFirstFileW:
- _findfirst doesn't support long UTF-8 filenames that don't fit
into MAX_PATH bytes. With MSVCRT, it fails with with EINVAL.
With UCRT, long filenames make the application crash.
- FindFirstFileA has the same limit of MAX_PATH bytes. It fails
with ERROR_MORE_DATA on longer filenames.
- FindFirstFileA and _findfirst (both MSVCRT and UCRT) use best-fit
mapping. Malicious filenames might result in a directory traversal
attack.[1] For example, U+2215 becomes ASCII '/' when best-fit
converted to Windows-1252.
- FindFirstFileW seems to be faster than UCRT's _findfirst or
_wfindfirst.
- FindFirstFileW isn't supported on Windows 95/98/ME, and thus
the new dirent code doesn't support those Windows versions.
To avoid best-fit mapping, make readdir convert from wide char to
the correct code page. If a lossless conversion isn't possible, the
entry is skipped. When the end of the directory is reached, readdir
fails with errno = EILSEQ if any entries were skipped. The delaying
of EILSEQ allows applications to still see all other filenames in
the directory.
Add _readdir_8dot3 which tries to use a 8.3 name to avoid EILSEQ.
Delete wdirent.c, and unify DIR and _WDIR. Calls to narrow and wide
functions on the same DIR can now be mixed.
Make dirent functions usable in applications that have been marked
as long path aware in their application manifests (no MAX_PATH limit).
struct dirent and struct _wdirent:
- Change the type of d_ino from "long" to "unsigned short" because
ino_t is unsigned short. d_ino is still always 0.
- Add d_type. Applications that support it can be faster because they
don't need to stat() each entry to know the type. Together with the
switch to FindFirstFileW, the time required to scan a directory tree
may reduce over 80 % (when data from disk is already cached in RAM).
- Add d_8dot3. d_8dot3 is normally 0 but it is set to 1 by
_readdir_8dot3 if a 8.3 filename was used in d_name.
- Set d_reclen to the size of the structure. Previously it was
always 0.
- The size reduction of d_ino matches the amount of space needed by
d_type and d_8dot3, thus the offsets of the old members don't
change. This might reduce ABI incompatiblity risks in case a pointer
to struct dirent is passed between components that were built
against different dirent versions. d_ino has always been 0, so
if the old struct dirent is interpret by programs built against
the new code, they will see d_type == DT_UNKNOWN and d_8dot3 == 0.
telldir and seekdir:
- Make seekdir rewind only when actually needed.
- Delay the forward seeking until readdir is called. This simplifies
error handling. Previously seekdir called readdir in a loop.
If there was an error, it could be detected by checking errno
after seekdir returned. seekdir is a void function, and portable
programs won't detect errors from it. When readdir does the forward
seeking, such seeking errors are reported by readdir.
- Once readdir returns NULL, make telldir return the position where
readdir stopped instead of -1. This matches Linux at least in the
normal end-of-dir situation.
Error handling:
- Never explictly set errno = 0. The dirent functions are defined
in POSIX, and no POSIX function shall set errno to zero.[2] To
distinguish errors from a successful end of the directory, one
must set errno = 0 before calling readdir().
- Don't set errno = ENOENT if the directory is completely empty and
doesn't have even the "." and ".." entries. It may happen when
reading a root directory of an empty drive.
- Detect lack of access permissions at opendir (EACCES) instead of
failing on the first readdir call with EINVAL.
- Detect dangling directory symlinks at opendir instead of failing
on the first readdir call. (ENOENT)
Define the DIR structure in dirent.c instead of dirent.h. The header
only provides an opaque typedef.
Don't include <io.h> in dirent.h.
Add API documentation to dirent.h. It mostly describes the details
that are specific to this implementation.
[1]
https://devco.re/blog/2025/01/09/worstfit-unveiling-hidden-transformers-in-windows-ansi/
[2] "No function in this volume of POSIX.1-2024 shall set errno to zero."
https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_03
Signed-off-by: Lasse Collin <[email protected]>
---
mingw-w64-crt/Makefile.am | 2 +-
mingw-w64-crt/misc/dirent.c | 835 ++++++++++++++++++++++++++-------
mingw-w64-crt/misc/wdirent.c | 5 -
mingw-w64-headers/crt/dirent.h | 266 ++++++++---
4 files changed, 854 insertions(+), 254 deletions(-)
delete mode 100644 mingw-w64-crt/misc/wdirent.c
diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 39fbdcbbc..e5186a1c8 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -970,7 +970,7 @@ src_libmingwex=\
misc/tsearch.c misc/twalk.c \
misc/wcstof.c \
misc/wcstold.c \
- misc/wdirent.c misc/winbs_uint64.c misc/winbs_ulong.c
misc/winbs_ushort.c \
+ misc/winbs_uint64.c misc/winbs_ulong.c misc/winbs_ushort.c \
misc/wmemchr.c misc/wmemcmp.c misc/wmemcpy.c
misc/wmemmove.c misc/wmempcpy.c \
misc/wmemset.c misc/ftw.c misc/ftw64.c
misc/mingw-access.c \
\
diff --git a/mingw-w64-crt/misc/dirent.c b/mingw-w64-crt/misc/dirent.c
index cf95f9091..c3e24eb24 100644
--- a/mingw-w64-crt/misc/dirent.c
+++ b/mingw-w64-crt/misc/dirent.c
@@ -12,6 +12,17 @@
* Significantly revised and rewinddir, seekdir and telldir added by Colin
* Peters <[email protected]>
*
+ * Revised by Lasse Collin <[email protected]> in 2025:
+ * - unified DIR and _WDIR
+ * - long path aware (no MAX_PATH limit)
+ * - readdir supports long UTF-8 filenames (up to 778 bytes including \0)
+ * - readdir avoids best-fit mapping with delayed EILSEQ at the end of the
+ * dir to prevent directory traversal attacks from malicious filenames
+ * - _readdir_8dot3 tries to fall back to 8.3 names instead of EILSEQ
+ * - added d_type to struct dirent and struct _wdirent
+ * - improved error handling
+ * - added API docs into dirent.h
+ * - Windows 95/98/ME is no longer supported
*/
#ifndef WIN32_LEAN_AND_MEAN
@@ -21,199 +32,675 @@
#include <stdlib.h>
#include <errno.h>
#include <string.h>
-#include <io.h>
-#include <direct.h>
+#include <locale.h>
+#include <limits.h>
#include <dirent.h>
+#include <windows.h>
-#include <windows.h> /* for GetFileAttributes */
-#include <tchar.h>
-#define SUFFIX _T("*")
-#define SLASH _T("\\")
+/* Maximum number of wide characters allowed in a pathname including
+ * the terminating \0. This constant is based on the behavior of
+ * GetFullPathNameW which rejects input strings that exceed this size. */
+#define DIRENT_WPATH_MAX 32767
+
+
+struct __dirent_DIR
+{
+ /*
+ * Status of search:
+ * 0 = rewinddir or seekdir has been called, and prepare_next_entry
+ * needs to call FindFirstFileW.
+ * 1 = The entry from FindFirstFileW is in dd_wfd. This is the
+ * status after opendir or _wopendir if the directory contains
+ * at least one entry.
+ * >1 = FindNextFileW has been called (dd_stat - 1) times.
+ * -1 = The end of the directory was reached or an error occurred.
+ */
+ long dd_stat;
+
+ /*
+ * Position in the directory as seen by the application.
+ * This is 0 after opendir, _wopendir, and rewinddir.
+ * readdir (via prepare_next_entry) increments this.
+ * seekdir sets this to an application-provided non-negative value.
+ */
+ long dd_pos;
+
+ /*
+ * prepare_next_entry returns this when the end of the directory
+ * is reached. This is -1 when there are no delayed errors. If
+ * a character set conversion problem occurs, this is set to EILSEQ.
+ */
+ int dd_endval;
+
+ /* Handle from FindFirstFileW */
+ HANDLE dd_handle;
+
+ /* Result from FindFirstFileW or FindNextFileW */
+ WIN32_FIND_DATAW dd_wfd;
+
+ /* dirent struct to return from readdir (NOTE: this makes this thread
+ * safe as long as only one thread uses a particular DIR struct at
+ * a time) */
+ union
+ {
+ struct dirent a;
+ struct _wdirent w;
+ } dd_entry;
+
+ /* given path for dir with search pattern (struct is extended) */
+ wchar_t dd_name[1];
+};
/*
- * opendir
+ * Get the code page used for filenames.
+ *
+ * If locale is UTF-8, the UCRT functions (fopen, _open, ...) use UTF-8
+ * filenames even if CP_ACP isn't UTF-8 or if SetFileApisToOEM has been
+ * called. That is, it's possible that UCRT functions and Win32 API ANSI
+ * functions use a different encoding for filenames. This can only
+ * happen with UTF-8 locales.
*
- * Returns a pointer to a DIR structure appropriately filled in to begin
- * searching a directory.
+ * If UTF-8 is set via an application manifest, then CP_ACP and CP_OEMCP
+ * are UTF-8, and both UCRT and Win32 API always use UTF-8 for filenames.
+ * This is true even if one calls setlocale to set a non-UTF-8 locale or
+ * if setlocale isn't called at all.
+ *
+ * MSVCRT doesn't support UTF-8 locales, and this cannot return CP_UTF8.
*/
-_TDIR *
-_topendir (const _TCHAR *szPath)
+static unsigned int
+get_code_page (void)
{
- _TDIR *nd;
- unsigned int rc;
- _TCHAR szFullPath[MAX_PATH];
+ return (___lc_codepage_func () == CP_UTF8)
+ ? CP_UTF8
+ : AreFileApisANSI () ? CP_ACP : CP_OEMCP;
+}
- errno = 0;
- if (!szPath)
+DIR *
+_wopendir (const wchar_t *path)
+{
+ DWORD full_path_alloc_size;
+ DWORD full_path_len;
+ int err;
+ DIR *dirp;
+
+ if (!path)
{
errno = EFAULT;
- return (_TDIR *) 0;
+ return NULL;
+ }
+
+ /* POSIX specifies that an empty string results in ENOENT. */
+ if (path[0] == L'\0')
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /*
+ * Make an absolute pathname.
+ *
+ * When buffer is too small or not provided, the return value of
+ * GetFullPathNameW includes the space needed for the terminating \0.
+ */
+ full_path_alloc_size = GetFullPathNameW (path, 0, NULL, NULL);
+ if (full_path_alloc_size == 0)
+ {
+ switch (GetLastError ())
+ {
+ case ERROR_CALL_NOT_IMPLEMENTED:
+ /* Windows 95/98/ME is not supported by this dirent code. */
+ errno = ENOSYS;
+ return NULL;
+
+ case ERROR_FILENAME_EXCED_RANGE:
+ /* The limit is 32767 wide chars including the terminating \0. */
+ errno = ENAMETOOLONG;
+ return NULL;
+
+ default:
+ /* GetFullPathNameW accepts various invalid inputs so errors
+ * should be uncommon. */
+ errno = ENOENT;
+ return NULL;
+ }
}
- if (szPath[0] == _T('\0'))
+ /* Allocate enough space to store DIR structure, the full path, and
+ * the two characters "\*" that will be appended. full_path_alloc_size
+ * already includes the space needed for the terminating \0. */
+ dirp = malloc (sizeof (DIR) + (full_path_alloc_size + 2) * sizeof (wchar_t));
+ if (!dirp)
{
- errno = ENOTDIR;
- return (_TDIR *) 0;
+ errno = ENOMEM;
+ return NULL;
}
- /* Attempt to determine if the given path really is a directory. */
- rc = GetFileAttributes (szPath);
- if (rc == INVALID_FILE_ATTRIBUTES)
+ /* On success, the return value from GetFullPathNameW does not include
+ * the terminating \0. */
+ full_path_len = GetFullPathNameW (path, full_path_alloc_size,
+ dirp->dd_name, NULL);
+ if (full_path_len >= full_path_alloc_size)
{
- /* call GetLastError for more error info */
+ free (dirp);
errno = ENOENT;
- return (_TDIR *) 0;
+ return NULL;
+ }
+
+ /* Create the search expression:
+ * Add on a slash if the path does not end with one. */
+ if (full_path_len > 0 &&
+ dirp->dd_name[full_path_len - 1] != L'/' &&
+ dirp->dd_name[full_path_len - 1] != L'\\')
+ {
+ dirp->dd_name[full_path_len++] = L'\\';
}
- if (!(rc & FILE_ATTRIBUTE_DIRECTORY))
+
+ /* Add on the search pattern. */
+ dirp->dd_name[full_path_len++] = L'*';
+ dirp->dd_name[full_path_len] = L'\0';
+
+ /* dd_name has now been initialized. dd_entry is initialized in every
+ * readdir call so it's not initialized here.
+ *
+ * Initialize the status to indicate that FindFirstFileW has been called
+ * to read the first entry into dd_wfd (it will be done a few lines later).
+ *
+ * Initialize the readdir/telldir position to be at the beginning of
+ * the directory (0).
+ *
+ * There is no delayed error to be returned at the end of the directory. */
+ dirp->dd_stat = 1;
+ dirp->dd_pos = 0;
+ dirp->dd_endval = -1;
+
+ /* Initialize dd_handle and dd_wfd. The FindFirstFileW call cannot be
+ * delayed until readdir because that would result in worse error detection.
+ * Specifically, it's not enough to call GetFileAttributesW here because it
+ * cannot detect EACCES or dangling directory symlinks. */
+ err = 0;
+ dirp->dd_handle = FindFirstFileW (dirp->dd_name, &dirp->dd_wfd);
+ if (dirp->dd_handle == INVALID_HANDLE_VALUE)
+ {
+ switch (GetLastError ())
+ {
+ case ERROR_FILE_NOT_FOUND:
+ /* It's a completely empty directory, for example, the root
+ * directory of an empty drive. This isn't an error.
+ * Mark that there are no more filenames to be read. */
+ dirp->dd_stat = -1;
+ break;
+
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_INVALID_NAME:
+ case ERROR_BAD_NETPATH:
+ case ERROR_BAD_NET_NAME:
+ /* In addition to the obvious reason, ERROR_PATH_NOT_FOUND
+ * may occur also if the search pattern is too long:
+ * 32767 wide chars including the \0 for a long path aware app,
+ * or 260 (MAX_PATH) including the \0 if the app isn't
+ * long path aware.
+ *
+ * ERROR_INVALID_NAME occurs with C:\:\* and such invalid inputs.
+ * GetFullPathNameW does accept invalid paths so this error is
+ * indeed possible.
+ *
+ * ERROR_BAD_NETPATH occurs if the server name cannot be resolved
+ * or if the server doesn't support file sharing.
+ *
+ * ERROR_BAD_NET_NAME occurs if the server can be contacted but
+ * the share doesn't exist. */
+ err = ENOENT;
+ break;
+
+ case ERROR_DIRECTORY:
+ case ERROR_INVALID_FUNCTION:
+ /* Detecting non-directories only works with the last pathname
+ * component. For example, if there is a file foo, passing
+ * foo\* to FindFirstFileW will fail with ERROR_DIRECTORY.
+ * foo\bar\* should be ENOTDIR too but it becomes ENOENT
+ * because FindFirstFileW fails with ERROR_PATH_NOT_FOUND.
+ *
+ * ERROR_INVALID_FUNCTION happens at least with the device
+ * namespace. _wopendir(L"nul") makes GetFullPathNameW produce
+ * \\.\nul, and then FindFirstFileW is called with \\.\nul\*
+ * which results in ERROR_INVALID_FUNCTION. */
+ err = ENOTDIR;
+ break;
+
+ case ERROR_CANT_RESOLVE_FILENAME:
+ /* At least a symlink loop can produce this error. */
+ err = ELOOP;
+ break;
+
+ case ERROR_ACCESS_DENIED:
+ err = EACCES;
+ break;
+
+ case ERROR_FILENAME_EXCED_RANGE:
+ /* Unsure if this error code can happen from FindFirstFileW
+ * but handle it just in case to avoid the default case. */
+ err = ENAMETOOLONG;
+ break;
+
+ case ERROR_NOT_ENOUGH_MEMORY:
+ err = ENOMEM;
+ break;
+
+ /*
+ * NOTE: ERROR_MORE_DATA is possible with FindFirstFileA if
+ * it encounters a filename that exceeds MAX_PATH bytes.
+ * That error shouldn't be possible with FindFirstFileW.
+ */
+
+ default:
+ /* Unknown error. */
+ err = EIO;
+ break;
+ }
+ }
+
+ if (err != 0)
{
- /* Error, entry exists but not a directory. */
- errno = ENOTDIR;
- return (_TDIR *) 0;
+ free (dirp);
+ errno = err;
+ return NULL;
}
- /* Make an absolute pathname. */
- _tfullpath (szFullPath, szPath, MAX_PATH);
+ return dirp;
+}
+
- /* 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));
+DIR *
+opendir (const char *path)
+{
+ unsigned int cp = get_code_page ();
+ wchar_t *wpath;
+ int wpath_size;
+ DIR *dirp;
- if (!nd)
+ if (!path)
{
- /* Error, out of memory. */
- errno = ENOMEM;
- return (_TDIR *) 0;
+ errno = EFAULT;
+ return NULL;
}
- /* Create the search expression. */
- _tcscpy (nd->dd_name, szFullPath);
+ /* Convert path to wide char. Use MB_ERR_INVALID_CHARS because UCRT's
+ * fopen, _open, _mkdir, ... reject invalid multibyte strings too.
+ * Compare to Win32 API's FindFirstFileA which apparently converts
+ * the argument without this flag and then accesses the resulting file
+ * or directory. */
+ wpath_size = MultiByteToWideChar (cp, MB_ERR_INVALID_CHARS,
+ path, -1, NULL, 0);
+ if (wpath_size <= 0)
+ {
+ errno = EILSEQ;
+ return NULL;
+ }
- /* 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('\\'))
+ /* Don't try to allocate memory if the pathname is so long that Windows
+ * would reject it anyway. */
+ if (wpath_size > DIRENT_WPATH_MAX)
{
- _tcscat (nd->dd_name, SLASH);
+ errno = ENAMETOOLONG;
+ return NULL;
}
- /* Add on the search pattern */
- _tcscat (nd->dd_name, SUFFIX);
+ wpath = malloc ((size_t) wpath_size * sizeof (wchar_t));
+ if (!wpath)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
- /* Initialize handle to -1 so that a premature closedir doesn't try
- * to call _findclose on it. */
- nd->dd_handle = -1;
+ if (MultiByteToWideChar (cp, MB_ERR_INVALID_CHARS,
+ path, -1, wpath, wpath_size) != wpath_size)
+ {
+ free (wpath);
+ errno = EILSEQ;
+ return NULL;
+ }
- /* Initialize the status. */
- nd->dd_stat = 0;
+ /* Open the directory using the wide char path. */
+ dirp = _wopendir (wpath);
- /* 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, 260 * sizeof(nd->dd_dir.d_name[0])
/*FILENAME_MAX*/);
+ /* wpath isn't needed anymore but we need to remember the errno
+ * in case _wopendir failed. */
+ {
+ int saved_errno = errno;
+ free (wpath);
+ errno = saved_errno;
+ }
- return nd;
+ return dirp;
}
-/*
- * readdir
- *
- * Return a pointer to a dirent structure filled with the information on the
- * next entry in the directory.
- */
-struct _tdirent *
-_treaddir (_TDIR * dirp)
+/* Convert attributes from WIN32_FIND_DATAW to d_type. */
+static unsigned char
+get_d_type (DWORD attrs, DWORD reparse_tag)
{
- errno = 0;
+ /*
+ * If we are unsure about the type, we should return DT_UNKNOWN.
+ * The d_type member is useful only if its value can be trusted
+ * to be correct when it's not DT_UNKNOWN.
+ *
+ * The following were omitted from supported_attrs:
+ * FILE_ATTRIBUTE_DEVICE
+ * FILE_ATTRIBUTE_OFFLINE
+ * FILE_ATTRIBUTE_VIRTUAL
+ * FILE_ATTRIBUTE_RECALL_ON_OPEN
+ * FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS
+ */
+ unsigned int supported_attrs = FILE_ATTRIBUTE_READONLY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_ARCHIVE |
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_ATTRIBUTE_TEMPORARY |
+ FILE_ATTRIBUTE_SPARSE_FILE |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_COMPRESSED |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
+ FILE_ATTRIBUTE_ENCRYPTED |
+ FILE_ATTRIBUTE_INTEGRITY_STREAM |
+ FILE_ATTRIBUTE_NO_SCRUB_DATA |
+ FILE_ATTRIBUTE_EA |
+ FILE_ATTRIBUTE_PINNED |
+ FILE_ATTRIBUTE_UNPINNED;
+
+ if (attrs & ~supported_attrs)
+ {
+ return DT_UNKNOWN;
+ }
+
+ /* Check for a reparse point before checking if it is a directory.
+ * A reparse point can also have the directory attribute. This
+ * includes "directory symlinks" too. */
+ if (attrs & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ return (reparse_tag == IO_REPARSE_TAG_SYMLINK) ? DT_LNK : DT_UNKNOWN;
+ }
+
+ if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ return DT_DIR;
+ }
+
+ return DT_REG;
+}
- /* Check for valid DIR struct. */
+
+/* Prepare dirp->dd_wfd and the common members of dirp->dd_entry. Return 0 on
+ * success, -1 on end of directory, and >0 (an errno value) on error. */
+static int
+prepare_next_entry (DIR *dirp)
+{
if (!dirp)
{
- errno = EFAULT;
- return (struct _tdirent *) 0;
+ return EFAULT;
}
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;
+ /* We have already successfully returned all files in the directory or
+ * an error has occurred. If there was an error, it has alredy been
+ * returned. Don't return it again. This ensures that an application
+ * cannot end up in an infite loop in case it tries to continue
+ * reading after an error. */
+ return -1;
}
- 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)
+ /* After rewinddir or seekdir, we might need to restart from the beginning
+ * of the directory. */
+ if (dirp->dd_stat == 0)
+ {
+ if (dirp->dd_handle != INVALID_HANDLE_VALUE)
{
- /* Whoops! Seems there are no files in that
- * directory. */
- dirp->dd_stat = -1;
+ (void) FindClose (dirp->dd_handle);
}
- else
+
+ dirp->dd_handle = FindFirstFileW (dirp->dd_name, &dirp->dd_wfd);
+ if (dirp->dd_handle == INVALID_HANDLE_VALUE)
{
- dirp->dd_stat = 1;
+ /* It's an empty directory or an error occurred. Make telldir
+ * return the position (0) where the end of directory or the
+ * error occurred. Update dd_stat to indicate the end of the
+ * directory or an error status. */
+ dirp->dd_pos = 0;
+ dirp->dd_stat = -1;
+
+ switch (GetLastError ())
+ {
+ case ERROR_FILE_NOT_FOUND:
+ /* It's a completely empty directory, for example, the root
+ * directory of an empty drive. Return dd_endval because
+ * seekdir doesn't reset it to -1. */
+ return dirp->dd_endval;
+
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_INVALID_NAME:
+ case ERROR_BAD_NETPATH:
+ case ERROR_BAD_NET_NAME:
+ case ERROR_DIRECTORY:
+ case ERROR_INVALID_FUNCTION:
+ case ERROR_CANT_RESOLVE_FILENAME:
+ case ERROR_ACCESS_DENIED:
+ case ERROR_FILENAME_EXCED_RANGE:
+ /* On POSIX systems, rewinddir or seekdir shouldn't fail
+ * since they can keep the directory open. On Windows, we
+ * have to restart the directory reading, and if the
+ * directory has been renamed, removed, or permissions
+ * changed, this can fail in various ways. Treat all these
+ * errors as an invalid position of the directory stream. */
+ return ENOENT;
+
+ case ERROR_NOT_ENOUGH_MEMORY:
+ return ENOMEM;
+
+ default:
+ return EIO;
+ }
}
+
+ /* The first entry has been read into dd_wfd. */
+ dirp->dd_stat = 1;
+ }
+
+ if (dirp->dd_pos == LONG_MAX)
+ {
+ /* Too many entries. Treat it as invalid directory stream position.
+ * It's highly unlikely that there are this many files in a directory,
+ * so adding code to handle larger directories seems pointless. */
+ return ENOENT;
}
- else
+
+ /* Increment the target position. */
+ dirp->dd_pos++;
+
+ /* Read the next entry into dd_wfd if required. Right after _wopendir we
+ * already have the first entry in dd_wfd and the loop doesn't run at all
+ * (dd_stat == 1 && dd_pos == 1). If we are seeking forward due to a seekdir
+ * call, we might read multiple entries to reach the target position. */
+ while (dirp->dd_stat < dirp->dd_pos)
{
- /* Get the next search entry. */
- if (_tfindnext (dirp->dd_handle, &(dirp->dd_dta)))
+ if (FindNextFileW (dirp->dd_handle, &dirp->dd_wfd) == 0)
{
- /* 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;
+ /* The end of the directory was reached or an error occurred. */
+ DWORD find_next_err = GetLastError ();
+ (void) FindClose (dirp->dd_handle);
+ dirp->dd_handle = INVALID_HANDLE_VALUE;
+
+ /* Make telldir return the position where the end of directory or
+ * the error occurred. Then update dd_stat to indicate the end of
+ * the directory or an error status. */
+ dirp->dd_pos = dirp->dd_stat;
dirp->dd_stat = -1;
+
+ switch (find_next_err)
+ {
+ case ERROR_NO_MORE_FILES:
+ return dirp->dd_endval;
+
+ case ERROR_NOT_ENOUGH_MEMORY:
+ return ENOMEM;
+
+ /* NOTE: ERROR_MORE_DATA is possible with FindNextFileA if
+ * it encounters a filename that exceeds MAX_PATH bytes.
+ * That error shouldn't be possible with FindNextFileW. */
+
+ default:
+ return EIO;
+ }
}
- else
- {
- /* Update the status to indicate the correct
- * number. */
- dirp->dd_stat++;
- }
+
+ /* Because dd_stat < dd_pos at the beginning of the loop,
+ * this cannot overflow. The loop runs more than once only
+ * when seekdir has been used. */
+ dirp->dd_stat++;
}
- if (dirp->dd_stat > 0)
+ /* These members are shared between the narrow and wide structs. */
+ dirp->dd_entry.w.d_ino = 0;
+ dirp->dd_entry.w.d_type = get_d_type (dirp->dd_wfd.dwFileAttributes,
+ dirp->dd_wfd.dwReserved0);
+ dirp->dd_entry.w.d_8dot3 = 0;
+
+ return 0;
+}
+
+
+struct _wdirent *
+_wreaddir (DIR *dirp)
+{
+ /* errno must be preserved when the end of the directory is
+ * successfully reached and we return NULL. */
+ int saved_errno = errno;
+
+ int err = prepare_next_entry (dirp);
+ if (err == 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;
+ /* Determine d_namlen and copy the filename from dd_wfd. */
+ size_t namlen = wcslen (dirp->dd_wfd.cFileName);
+ dirp->dd_entry.w.d_namlen = (unsigned short) namlen;
+ memcpy (dirp->dd_entry.w.d_name, dirp->dd_wfd.cFileName,
+ (namlen + 1) * sizeof (wchar_t));
+
+ /* d_reclen has a fixed value. */
+ dirp->dd_entry.w.d_reclen = sizeof (dirp->dd_entry.w);
+
+ /* It shouldn't be necessary to preserve errno when we return non-NULL.
+ * Do it anyway. */
+ errno = saved_errno;
+ return &dirp->dd_entry.w;
}
- return (struct _tdirent *) 0;
+ /* End of directory (-1) or an error (>0). */
+ errno = (err > 0) ? err : saved_errno;
+ return NULL;
}
-/*
- * closedir
- *
- * Frees up resources allocated by opendir.
- */
-int
-_tclosedir (_TDIR * dirp)
+static struct dirent *
+readdir_impl (DIR *dirp, BOOL fallback8dot3)
+{
+ int saved_errno = errno;
+
+ /*
+ * We want to detect lossy conversion in WideCharToMultiByte. The required
+ * arguments depend on the code page:
+ *
+ * - CP_ACP and CP_OEMCP support WC_NO_BEST_FIT_CHARS even when those
+ * code pages are set to UTF-8. Lossy conversion is detected via the
+ * last argument (BOOL*).
+ *
+ * - CP_UTF8 requires WC_ERR_INVALID_CHARS, and the last argument must be
+ * NULL. If the filename contains unpaired surrogates (invalid UTF-16),
+ * the return value will be 0. WC_ERR_INVALID_CHARS only works on
+ * Windows Vista and later, but CP_UTF8 is only used with UTF-8 locales
+ * which are only supported on Windows 10 and later.
+ *
+ * d_name is big enough that conversion cannot run out of buffer space
+ * with double-byte character sets or UTF-8.
+ */
+ unsigned int cp = get_code_page ();
+ DWORD flags = (cp == CP_UTF8) ? WC_ERR_INVALID_CHARS : WC_NO_BEST_FIT_CHARS;
+ BOOL was_lossy = FALSE;
+ BOOL *was_lossy_ptr = (cp == CP_UTF8) ? NULL : &was_lossy;
+
+ /* More than one entry may be read if there are filenames that cannot
+ * be represented in the code page specified in cp. */
+ int err;
+ while ((err = prepare_next_entry (dirp)) == 0)
+ {
+ /* Try to convert the wide char filename to multibyte. */
+ int conv_result = WideCharToMultiByte (
+ cp, flags,
+ dirp->dd_wfd.cFileName, -1,
+ dirp->dd_entry.a.d_name, sizeof (dirp->dd_entry.a.d_name),
+ NULL, was_lossy_ptr);
+
+ /* Check for <= 1 instead of <= 0 to ensure that the filename
+ * isn't an empty string. */
+ if (conv_result <= 1 || was_lossy)
+ {
+ /* If fallback to a 8.3 name wasn't requested or a 8.3 name doesn't
+ * exist, this filename has to be skipped. */
+ if (!fallback8dot3 || dirp->dd_wfd.cAlternateFileName[0] == '\0')
+ {
+ dirp->dd_endval = EILSEQ;
+ continue;
+ }
+
+ /* Try to use the 8.3 name. */
+ conv_result = WideCharToMultiByte (
+ cp, flags,
+ dirp->dd_wfd.cAlternateFileName, -1,
+ dirp->dd_entry.a.d_name, sizeof (dirp->dd_entry.a.d_name),
+ NULL, was_lossy_ptr);
+ if (conv_result <= 1 || was_lossy)
+ {
+ dirp->dd_endval = EILSEQ;
+ continue;
+ }
+
+ /* Mark that d_name contains a 8.3 name. */
+ dirp->dd_entry.a.d_8dot3 = 1;
+ }
+
+ dirp->dd_entry.a.d_namlen = (unsigned short) (conv_result - 1);
+ dirp->dd_entry.a.d_reclen = sizeof (dirp->dd_entry.a);
+
+ errno = saved_errno;
+ return &dirp->dd_entry.a;
+ }
+
+ errno = (err > 0) ? err : saved_errno;
+ return NULL;
+}
+
+
+struct dirent *
+readdir (DIR *dirp)
+{
+ return readdir_impl (dirp, FALSE);
+}
+
+
+struct dirent *
+_readdir_8dot3 (DIR *dirp)
{
- int rc;
+ return readdir_impl (dirp, TRUE);
+}
- errno = 0;
- rc = 0;
+
+int
+closedir (DIR *dirp)
+{
+ int rc = 0;
if (!dirp)
{
@@ -221,104 +708,92 @@ _tclosedir (_TDIR * dirp)
return -1;
}
- if (dirp->dd_handle != -1)
+ if (dirp->dd_handle != INVALID_HANDLE_VALUE)
{
- rc = _findclose (dirp->dd_handle);
+ rc = !FindClose (dirp->dd_handle);
}
/* Delete the dir structure. */
free (dirp);
- return rc;
+ if (rc)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ return 0;
}
-/*
- * rewinddir
- *
- * Return to the beginning of the directory "stream". We simply call findclose
- * and then reset things like an opendir.
- */
+
void
-_trewinddir (_TDIR * dirp)
+rewinddir (DIR *dirp)
{
- errno = 0;
-
if (!dirp)
{
errno = EFAULT;
return;
}
- if (dirp->dd_handle != -1)
- {
- _findclose (dirp->dd_handle);
- }
-
- dirp->dd_handle = -1;
+ /* prepare_next_entry will call FindFirstFileW. */
dirp->dd_stat = 0;
+ dirp->dd_pos = 0;
+
+ /* Clear a possible delayed EILSEQ. */
+ dirp->dd_endval = -1;
}
-/*
- * 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)
+telldir (DIR *dirp)
{
- errno = 0;
-
if (!dirp)
{
errno = EFAULT;
return -1;
}
- return dirp->dd_stat;
+
+ /* Return dd_pos even if dd_stat indicates end of directory or error.
+ * This way the end of directory behavior matches Linux where telldir keeps
+ * returning the same value as it did right after reading the last entry. */
+ return dirp->dd_pos;
}
-/*
- * 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)
+seekdir (DIR *dirp, long pos)
{
- errno = 0;
-
if (!dirp)
{
errno = EFAULT;
return;
}
- if (lPos < -1)
+ if (pos < 0)
{
/* Seeking to an invalid position. */
errno = EINVAL;
return;
}
- else if (lPos == -1)
+
+ /* If dd_stat indicates an error or the end of the directory, or if dd_stat
+ * is past the requested position by more than one entry, prepare_next_entry
+ * needs to reopen the dir and skip entries to reach the requested position.
+ *
+ * If pos is exactly one entry past the current position in dd_stat,
+ * there is no need to reopen the directory because prepare_next_entry
+ * can reuse the most recent entry (or use the first entry after reopening
+ * the directory if pos == 0 and an earlier rewinddir or seekdir already
+ * set dd_stat = 0).
+ *
+ * NOTE: pos can be LONG_MAX. Avoid an integer overflow. */
+ if (dirp->dd_stat < 0 || dirp->dd_stat - 1 > pos)
{
- /* Seek past end. */
- if (dirp->dd_handle != -1)
- {
- _findclose (dirp->dd_handle);
- }
- dirp->dd_handle = -1;
- dirp->dd_stat = -1;
+ /* prepare_next_entry will call FindFirstFileW. */
+ dirp->dd_stat = 0;
}
- else
- {
- /* Rewind and read forward to the appropriate index. */
- _trewinddir (dirp);
- while ((dirp->dd_stat < lPos) && _treaddir (dirp))
- ;
- }
+ /* If the next call is telldir, it will return the new position as required
+ * by POSIX. Any forward seeking is done when readdir is called. */
+ dirp->dd_pos = pos;
}
diff --git a/mingw-w64-crt/misc/wdirent.c b/mingw-w64-crt/misc/wdirent.c
deleted file mode 100644
index 6dcf42bd5..000000000
--- a/mingw-w64-crt/misc/wdirent.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#define _UNICODE 1
-#define UNICODE 1
-
-#include <wchar.h>
-#include "dirent.c"
diff --git a/mingw-w64-headers/crt/dirent.h b/mingw-w64-headers/crt/dirent.h
index 421b09278..2f7dd36d6 100644
--- a/mingw-w64-headers/crt/dirent.h
+++ b/mingw-w64-headers/crt/dirent.h
@@ -4,6 +4,7 @@
* This file is part of the mingw-w64 runtime package.
* No warranty is given; refer to the file DISCLAIMER.PD within this package.
*
+ * The dirent implementation was revised in mingw-w64 13.0.0.
*/
#ifndef _DIRENT_H_
@@ -12,8 +13,6 @@
/* All the headers include this file. */
#include <crtdefs.h>
-#include <io.h>
-
#ifndef RC_INVOKED
#pragma pack(push,_CRT_PACKING)
@@ -22,97 +21,228 @@
extern "C" {
#endif
+
+/* In addition to the d_ino and d_name members required by POSIX,
+ * also d_type, d_8dot3, d_reclen, and d_namlen are present in
+ * struct dirent and struct _wdirent. */
+#define _DIRENT_HAVE_D_TYPE 1
+#define _DIRENT_HAVE_D_8DOT3 1
+#define _DIRENT_HAVE_D_RECLEN 1
+#define _DIRENT_HAVE_D_NAMLEN 1
+
+/* Values for d_type:
+ *
+ * - DT_UNKNOWN is possible even for regular files and directories
+ * in some cases.
+ *
+ * - DT_DIR and DT_REG are used when the entry definitely is
+ * a regular file or directory.
+ *
+ * - DT_LNK is only used for reparse points that have the tag
+ * IO_REPARSE_TAG_SYMLINK. Other reparse points are DT_UNKNOWN.
+ *
+ * - Other values aren't used, but DT_CHR and DT_FIFO are defined still.
+ *
+ * The constants match glibc and *BSDs:
+ *
+ * - DT_DIR == (S_IFDIR >> 12)
+ * DT_REG == (S_IFREG >> 12)
+ * DT_FIFO == (S_IFIFO >> 12)
+ * DT_CHR == (S_IFCHR >> 12)
+ *
+ * - There is no S_IFLNK on Windows. However, on other systems,
+ * S_IFLNK == (S_IFREG | S_IFCHR), and S_IFCHR has the same
+ * value in glibc, *BSDs, and Windows. Thus the same DT_LNK
+ * constant is used here as on the other systems.
+ */
+#define DT_UNKNOWN 0
+#define DT_DIR 4
+#define DT_REG 8
+#define DT_LNK 10
+/* These following aren't used. They are only defined for compatibility. */
+#define DT_FIFO 1
+#define DT_CHR 2
+
struct dirent
{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
+ unsigned short d_ino; /* Always zero. */
+ unsigned char d_type; /* File type if known. */
+ unsigned char d_8dot3; /* 0 or 1, see _readdir_8dot3. */
+ unsigned short d_reclen; /* Size of this struct. */
unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[260]; /* [FILENAME_MAX] */ /* File name. */
+ char d_name[778]; /* [NAME_MAX + 1] */ /* File name. */
};
+typedef struct __dirent_DIR DIR;
+
/*
- * 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).
+ * opendir opens a directory stream corresponding to the specified directory.
+ * A pointer to a DIR is returned on success. On error, NULL is returned
+ * and errno is set:
+ * EFAULT The argument is NULL.
+ * ENOMEM Memory allocation failed.
+ * EILSEQ The pathname isn't a valid multibyte string.
+ * ENAMETOOLONG
+ * The pathname is too long. In many cases, too long
+ * pathnames result in ENOENT instead of ENAMETOOLONG.
+ * ENOENT The pathname doesn't exist or it is an empty string.
+ * ENOTDIR The last element in the pathname is not a directory.
+ * ELOOP Too many levels of symbolic links or other error that
+ * Windows reports as ERROR_CANT_RESOLVE_FILENAME.
+ * EACCES Access denied.
+ * EIO Unknown error, possibly an I/O error.
+ * ENOSYS This dirent implementation doesn't work on Windows 95/98/ME.
*/
-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_t dd_handle;
+DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
- /*
- * 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;
+/*
+ * readdir reads the next filename entry from the specified directory stream.
+ * On success, a pointer to struct dirent is returned. If there are no more
+ * entries, NULL is returned and the value of errno is not modified. If an
+ * error occurs, errno is set and NULL is returned. To distinguish the end
+ * of a directory from errors, the caller must set errno = 0 before calling
+ * readdir.
+ *
+ * If a filename is found that cannot be represented as a multibyte string,
+ * it is skipped. Once the end of the directory is successfully reached,
+ * readdir (or _readdir_8dot3 or _wreaddir) will fail with errno = EILSEQ.
+ * Delaying the error ensures that an application will see all other
+ * filenames in the directory still.
+ *
+ * Possible errors:
+ * EFAULT The argument is NULL.
+ * ENOMEM Memory allocation failed.
+ * EILSEQ At the end of the directory: at least one filename was
+ * skipped because it cannot be correctly converted to
+ * a multibyte string.
+ * ENOENT The directory stream position is invalid:
+ * - Previous call was rewinddir or seekdir, and now
+ * readdir couldn't reopen the directory.
+ * - 2,147,483,647 (LONG_MAX) entries have been read.
+ * Reading more would overflow the internal counters.
+ * EIO Unknown error, possibly an I/O error.
+ *
+ * As an extension, this implementation of readdir guarantees that errno
+ * is preserved (never modified) when a non-NULL value is returned.
+ *
+ * If _DIRENT_USE_READDIR_8DOT3 is defined before <dirent.h> is included,
+ * then readdir is an alias for _readdir_8dot3. See below how it differs.
+ */
+#ifndef _DIRENT_USE_READDIR_8DOT3
+struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
+#else
+struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*)
__MINGW_ASM_CALL(_readdir_8dot3);
+#endif
- /* given path for dir with search pattern (struct is extended) */
- char dd_name[1];
-} DIR;
+/*
+ * _readdir_8dot3 is like readdir except that if character set conversion
+ * fails, this function tries to fall back to short 8.3 filename if such
+ * a name exists. This way an application might be able to access files
+ * whose long name cannot be encoded in a multibyte string. A filename
+ * may still be skipped like in readdir if a short name isn't available.
+ *
+ * If a 8.3 name is returned, the d_8dot3 member in struct dirent is set
+ * to 1. Otherwise d_8dot3 is set to 0. readdir and _wreaddir never use
+ * 8.3 names, thus they always set d_8dot3 to 0.
+ *
+ * If _DIRENT_USE_READDIR_8DOT3 is defined before <dirent.h> is included,
+ * then readdir is an alias for _readdir_8dot3.
+ */
+struct dirent* __cdecl __MINGW_NOTHROW _readdir_8dot3 (DIR*);
-DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
-struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
+/*
+ * closedir closes the specified directory stream. On success, 0 is returned.
+ * On error, -1 is returned and errno is set:
+ * EFAULT The argument is NULL.
+ * EBADF Error closing the underlying HANDLE with FindClose. The
+ * memory allocated for the DIR was freed still.
+ */
int __cdecl __MINGW_NOTHROW closedir (DIR*);
+
+/*
+ * rewinddir resets the position to the beginning of the directory, and
+ * clears a possible delayed EILSEQ set by an earlier call to readdir
+ * or _readdir_8dot3.
+ *
+ * After rewinding, the next call to readdir, _readdir_8dot3, or _wreaddir
+ * has to reopen the directory. It can fail, for example, if the directory
+ * has been renamed, removed, or access permissions have changed.
+ */
void __cdecl __MINGW_NOTHROW rewinddir (DIR*);
+
+/*
+ * telldir returns the current position on success. On error, -1 is returned
+ * and errno is set. There is only one possible error:
+ * EFAULT The argument is NULL.
+ */
long __cdecl __MINGW_NOTHROW telldir (DIR*);
+
+/*
+ * seekdir sets the position for the next readdir, _readdir_8dot3, or
+ * _wreaddir call. A possible delayed EILSEQ set by an earlier call to
+ * readdir or _readdir_8dot3 is preserved (this differs from rewinddir).
+ *
+ * If seeking back by more than one entry, the next call to readdir,
+ * _readdir_8dot3, or _wreaddir has to reopen the directory. It can fail,
+ * for example, if the directory has been renamed, removed, or access
+ * permissions have changed. If reopening is successful, the readdir call
+ * will try to reach the requested position. This produces expected results
+ * only if the file system always returns the entries in the same order.
+ *
+ * If seeking back by exactly one entry, the next readdir, _readdir_8dot3,
+ * or _wreaddir will try to return the previous entry again which is fast.
+ * However, this is guaranteed to succeed only when using _wreaddir in the
+ * sequence telldir ; _wreaddir ; seekdir ; _wreaddir. If readdir or
+ * _readdir_8dot3 is used instead, a possible character set conversion
+ * problem may result in entries to be skipped, and then the seekdir
+ * step needs to seek backwards more than one entry.
+ */
void __cdecl __MINGW_NOTHROW seekdir (DIR*, long);
-/* wide char versions */
+/*
+ * Wide char versions
+ *
+ * One can mix calls to narrow and wide functions on the same DIR.
+ */
struct _wdirent
{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
+ unsigned short d_ino; /* Always zero. */
+ unsigned char d_type; /* File type if known. */
+ unsigned char d_8dot3; /* Always zero. */
+ unsigned short d_reclen; /* Size of this struct. */
unsigned short d_namlen; /* Length of name in d_name. */
wchar_t d_name[260]; /* [FILENAME_MAX] */ /* File name. */
};
/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
+ * _wopendir takes a wide character directory name. See opendir.
+ *
+ * _wopendir cannot fail with EILSEQ.
*/
-typedef struct
-{
- /* disk transfer area for this dir */
- struct _wfinddata_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 _wdirent dd_dir;
-
- /* _findnext handle */
- intptr_t 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) */
- wchar_t dd_name[1];
-} _WDIR;
-
-_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
-struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*);
-int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*);
-long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
+DIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
+
+/*
+ * _wreaddir returns a struct with wide char filename. See readdir.
+ *
+ * Character set conversion problems cannot occur in _wreaddir.
+ * However, if readdir or _readdir_8dot3 have been used on the same DIR,
+ * then a delayed error from those calls may be indicated at the end of
+ * the directory. (If filenames were already skipped by readdir or
+ * _readdir_8dot3, they aren't returned by a later _wreaddir call.)
+ */
+struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (DIR*);
+
+/*
+ * Compatibility aliases for source code that has been written against
+ * the old API:
+ */
+typedef DIR _WDIR;
+int __cdecl __MINGW_NOTHROW _wclosedir (DIR*) __MINGW_ASM_CALL(closedir);
+void __cdecl __MINGW_NOTHROW _wrewinddir (DIR*) __MINGW_ASM_CALL(rewinddir);
+long __cdecl __MINGW_NOTHROW _wtelldir (DIR*) __MINGW_ASM_CALL(telldir);
+void __cdecl __MINGW_NOTHROW _wseekdir (DIR*, long) __MINGW_ASM_CALL(seekdir);
#ifdef __cplusplus
--
2.48.1
_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public