This is the mail archive of the
libstdc++@gcc.gnu.org
mailing list for the libstdc++ project.
[patch] libstdc++/67747 Allocate space for dirent::d_name
- From: Jonathan Wakely <jwakely at redhat dot com>
- To: libstdc++ at gcc dot gnu dot org, gcc-patches at gcc dot gnu dot org
- Date: Tue, 29 Sep 2015 12:37:26 +0100
- Subject: [patch] libstdc++/67747 Allocate space for dirent::d_name
- Authentication-results: sourceware.org; auth=none
POSIX says that dirent::d_name has an unspecified length, so calls to
readdir_r must pass a buffer with enough trailing space for
{NAME_MAX}+1 characters. I wasn't doing that, which works OK on
GNU/Linux and BSD where d_name is a large array, but fails on Solaris
32-bit.
This uses pathconf to get NAME_MAX and allocates a buffer.
Tested powerpc64le-linux and x86_64-dragonfly4.1, I'm going to commit
this to trunk today (and backport all the filesystem fixes to
gcc-5-branch).
commit 16ff5d124b8e6c5d1f9dd4edb81b6ca5c9129134
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Tue Sep 29 11:58:19 2015 +0100
PR libstdc++/67747 Allocate space for dirent::d_name
PR libstdc++/67747
* src/filesystem/dir.cc (_Dir::dirent_buf): New member.
(get_name_max): New function.
(native_readdir) [_GLIBCXX_FILESYSTEM_IS_WINDOWS]: Copy to supplied
dirent object. Handle end of directory.
(_Dir::advance): Allocate space for d_name.
diff --git a/libstdc++-v3/src/filesystem/dir.cc b/libstdc++-v3/src/filesystem/dir.cc
index bce751c..d29f8eb 100644
--- a/libstdc++-v3/src/filesystem/dir.cc
+++ b/libstdc++-v3/src/filesystem/dir.cc
@@ -25,8 +25,12 @@
#include <experimental/filesystem>
#include <utility>
#include <stack>
+#include <stddef.h>
#include <string.h>
#include <errno.h>
+#ifdef _GLIBCXX_HAVE_UNISTD_H
+# include <unistd.h>
+#endif
#ifdef _GLIBCXX_HAVE_DIRENT_H
# ifdef _GLIBCXX_HAVE_SYS_TYPES_H
# include <sys/types.h>
@@ -64,20 +68,23 @@ struct fs::_Dir
fs::path path;
directory_entry entry;
file_type type = file_type::none;
+ unique_ptr<char[]> dirent_buf;
};
namespace
{
template<typename Bitmask>
- inline bool is_set(Bitmask obj, Bitmask bits)
+ inline bool
+ is_set(Bitmask obj, Bitmask bits)
{
return (obj & bits) != Bitmask::none;
}
// Returns {dirp, p} on success, {nullptr, p} on error.
// If an ignored EACCES error occurs returns {}.
- fs::_Dir
- open_dir(const fs::path& p, fs::directory_options options, std::error_code* ec)
+ inline fs::_Dir
+ open_dir(const fs::path& p, fs::directory_options options,
+ std::error_code* ec)
{
if (ec)
ec->clear();
@@ -99,8 +106,22 @@ namespace
return {nullptr, p};
}
+ inline long
+ get_name_max(const fs::path& path __attribute__((__unused__)))
+ {
+#ifdef _GLIBCXX_HAVE_UNISTD_H
+ long name_max = pathconf(path.c_str(), _PC_NAME_MAX);
+ if (name_max != -1)
+ return name_max;
+#endif
+
+ // Maximum path component on Windows is 255 (UTF-16?) characters,
+ // which is a reasonable default for POSIX too.
+ return 255;
+ }
+
inline fs::file_type
- get_file_type(const dirent& d __attribute__((__unused__)))
+ get_file_type(const ::dirent& d __attribute__((__unused__)))
{
#ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
switch (d.d_type)
@@ -129,12 +150,26 @@ namespace
#endif
}
- int
+ inline int
native_readdir(DIR* dirp, ::dirent*& entryp)
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
- if ((entryp = ::readdir(dirp)))
- return 0;
+ const int saved_errno = errno;
+ errno = 0;
+ if (auto entp = ::readdir(dirp))
+ {
+ size_t name_len = strlen(entp->d_name);
+ if (name_len > 255)
+ return ENAMETOOLONG;
+ size_t len = offsetof(::dirent, d_name) + name_len + 1;
+ memcpy(entryp, entp, len);
+ return 0;
+ }
+ else if (errno == 0) // End of directory reached.
+ {
+ errno = saved_errno;
+ entryp = nullptr;
+ }
return errno;
#else
return ::readdir_r(dirp, entryp, &entryp);
@@ -142,6 +177,7 @@ namespace
}
}
+
// Returns false when the end of the directory entries is reached.
// Reports errors by setting ec or throwing.
bool
@@ -150,9 +186,15 @@ fs::_Dir::advance(error_code* ec, directory_options options)
if (ec)
ec->clear();
- ::dirent ent;
- ::dirent* result = &ent;
- if (int err = native_readdir(dirp, result))
+ if (!dirent_buf)
+ {
+ size_t len = offsetof(::dirent, d_name) + get_name_max(path) + 1;
+ dirent_buf.reset(new char[len]);
+ }
+
+ ::dirent* entp = reinterpret_cast<::dirent*>(dirent_buf.get());
+
+ if (int err = native_readdir(dirp, entp))
{
if (err == EACCES
&& is_set(options, directory_options::skip_permission_denied))
@@ -165,13 +207,13 @@ fs::_Dir::advance(error_code* ec, directory_options options)
ec->assign(err, std::generic_category());
return true;
}
- else if (result != nullptr)
+ else if (entp != nullptr)
{
// skip past dot and dot-dot
- if (!strcmp(ent.d_name, ".") || !strcmp(ent.d_name, ".."))
+ if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, ".."))
return advance(ec, options);
- entry = fs::directory_entry{path / ent.d_name};
- type = get_file_type(ent);
+ entry = fs::directory_entry{path / entp->d_name};
+ type = get_file_type(*entp);
return true;
}
else