Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround for opening file streams using wide string paths on old versions of MinGW #175

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmake/GhcHelper.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ macro(AddTestExecutableWithStdCpp cppStd)
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
target_compile_definitions(filesystem_test_cpp${cppStd} PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(MINGW)
target_compile_options(filesystem_test_cpp${cppStd} PUBLIC "-Wa,-mbig-obj")
endif()
if(EMSCRIPTEN)
set_target_properties(filesystem_test_cpp${cppStd} PROPERTIES LINK_FLAGS "-g4 -s DISABLE_EXCEPTION_CATCHING=0 -s ALLOW_MEMORY_GROWTH=1")
endif()
Expand Down
129 changes: 127 additions & 2 deletions include/ghc/filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,21 @@
#error "Can't raise unicode errors with exception support disabled"
#endif

#if defined(GHC_OS_WINDOWS)
#if !defined(__GLIBCXX__) || (defined(_GLIBCXX_HAVE__WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T))
#define GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
#elif defined(__GLIBCXX__) && defined(_WIO_DEFINED)
#define GHC_HAS_WIO_DEFINED
#include <ext/stdio_filebuf.h>
#include <fcntl.h>
#include <share.h>
#ifndef _S_IREAD
// Some tests disable the previous inclusion of sys/stat.h; however, we need it when using old versions of MinGW.
#include <sys/stat.h>
#endif
#endif
#endif

namespace ghc {
namespace filesystem {

Expand Down Expand Up @@ -1142,8 +1157,66 @@ GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std:
GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept;
#endif

#if defined(GHC_OS_WINDOWS) && (!defined(__GLIBCXX__) || (defined(_GLIBCXX_HAVE__WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T)))
#define GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
#ifdef GHC_HAS_WIO_DEFINED
namespace detail {
inline int open_flags(std::ios::openmode mode)
{
const bool out = (mode & std::ios::out) != 0;
const bool in = (mode & std::ios::in) != 0;
int flags = 0;
if (in && out) {
flags |= _O_RDWR | _O_CREAT;
}
else if (out) {
flags |= _O_WRONLY | _O_CREAT;
}
else {
flags |= _O_RDONLY;
}
if ((mode & std::ios::app) != 0) {
flags |= _O_APPEND;
}
if ((mode & std::ios::trunc) != 0) {
flags |= _O_TRUNC;
}
flags |= (mode & std::ios::binary) != 0 ? _O_BINARY : _O_TEXT;
return flags;
}

inline int permission_flags(std::ios::openmode mode) {
int flags = 0;
if ((mode & std::ios::in) != 0) {
flags |= _S_IREAD;
}
if ((mode & std::ios::out) != 0) {
flags |= _S_IWRITE;
}
return flags;
}

template< class charT, class traits = std::char_traits< charT>>
__gnu_cxx::stdio_filebuf<charT, traits> open_filebuf_from_unicode_path(const path& file_path, std::ios::openmode mode) {
// Opening a file handle/descriptor from the native (wide) string path.
int file_handle = 0;
_wsopen_s(&file_handle, GHC_NATIVEWP(file_path), open_flags(mode), _SH_DENYNO, permission_flags(mode));

// Creating a GLIBCXX stdio_filebuf object from the file handle (it will check if the handle is actually opened).
__gnu_cxx::stdio_filebuf<charT, traits> result{file_handle, mode};

// If mode has the flag std::ios::ate, we need to seek at the end of the filebuf.
if (result.is_open() && (mode & std::ios::ate) != 0){
const auto seek_result = result.pubseekoff(0, std::ios::end, mode);
const auto end_pos = typename traits::pos_type(typename traits::off_type(-1));
if (seek_result == end_pos) {
// If the seeking fails, we need to close the filebuf (as per the standard).
result.close();
}
}

// Returning the filebuf in any case, whether we successfully opened it or not (the caller will check it).
return result;
}
} // namespace detail
#endif

// Non-C++17 add-on std::fstream wrappers with path
Expand All @@ -1159,6 +1232,16 @@ class basic_filebuf : public std::basic_filebuf<charT, traits>
{
#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
return std::basic_filebuf<charT, traits>::open(p.wstring().c_str(), mode) ? this : 0;
#elif defined(GHC_HAS_WIO_DEFINED)
if (this->is_open()) {
return nullptr;
}
auto filebuf = detail::open_filebuf_from_unicode_path<charT, traits>(p, mode);
if (!filebuf.is_open()) {
return nullptr;
}
this->swap(filebuf);
return this;
#else
return std::basic_filebuf<charT, traits>::open(p.string().c_str(), mode) ? this : 0;
#endif
Expand All @@ -1176,6 +1259,20 @@ class basic_ifstream : public std::basic_ifstream<charT, traits>
{
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream<charT, traits>::open(p.wstring().c_str(), mode); }
#elif defined(GHC_HAS_WIO_DEFINED)
explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in)
{
open(p, mode);
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::in)
{
*this->rdbuf() = detail::open_filebuf_from_unicode_path<charT, traits>(p, mode | std::ios_base::in);
if (!this->is_open()) {
this->setstate(std::ios_base::failbit);
} else {
this->clear();
}
}
#else
explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in)
: std::basic_ifstream<charT, traits>(p.string().c_str(), mode)
Expand All @@ -1199,6 +1296,20 @@ class basic_ofstream : public std::basic_ofstream<charT, traits>
{
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream<charT, traits>::open(p.wstring().c_str(), mode); }
#elif defined(GHC_HAS_WIO_DEFINED)
explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out)
{
open(p, mode);
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::out)
{
*this->rdbuf() = detail::open_filebuf_from_unicode_path<charT, traits>(p, mode | std::ios_base::out);
if (!this->is_open()) {
this->setstate(std::ios_base::failbit);
} else {
this->clear();
}
}
#else
explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out)
: std::basic_ofstream<charT, traits>(p.string().c_str(), mode)
Expand All @@ -1222,6 +1333,20 @@ class basic_fstream : public std::basic_fstream<charT, traits>
{
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream<charT, traits>::open(p.wstring().c_str(), mode); }
#elif defined(GHC_HAS_WIO_DEFINED)
explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
{
open(p, mode);
}
void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
{
*this->rdbuf() = detail::open_filebuf_from_unicode_path<charT, traits>(p, mode | std::ios_base::in | std::ios_base::out);
if (!this->is_open()) {
this->setstate(std::ios_base::failbit);
} else {
this->clear();
}
}
#else
explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
: std::basic_fstream<charT, traits>(p.string().c_str(), mode)
Expand Down
13 changes: 11 additions & 2 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ else()
message("Generating test runner for normal test...")
add_executable(filesystem_test filesystem_test.cpp catch.hpp)
target_link_libraries(filesystem_test ghc_filesystem)
if(MINGW)
target_compile_options(filesystem_test PUBLIC "-Wa,-mbig-obj")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "(SunOS|Solaris)")
target_link_libraries(filesystem_test xnet)
endif()
Expand All @@ -59,10 +62,13 @@ else()
add_executable(filesystem_test_char filesystem_test.cpp catch.hpp)
target_link_libraries(filesystem_test_char ghc_filesystem)
SetTestCompileOptions(filesystem_test_char)
if(MINGW)
target_compile_options(filesystem_test_char PUBLIC "-Wa,-mbig-obj")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
target_compile_definitions(filesystem_test_char PRIVATE _CRT_SECURE_NO_WARNINGS GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE)
target_compile_definitions(filesystem_test_char PRIVATE _CRT_SECURE_NO_WARNINGS GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE)
else()
target_compile_definitions(filesystem_test_char PRIVATE GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE)
target_compile_definitions(filesystem_test_char PRIVATE GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE)
endif()
ParseAndAddCatchTests(filesystem_test_char)
endif()
Expand Down Expand Up @@ -90,6 +96,9 @@ SetTestCompileOptions(fwd_impl_test)
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
target_compile_definitions(fwd_impl_test PRIVATE _CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN NOMINMAX)
endif()
if(MINGW)
target_compile_options(fwd_impl_test PUBLIC "-Wa,-mbig-obj")
endif()
add_test(fwd_impl_test fwd_impl_test)

add_executable(exception exception.cpp)
Expand Down
Loading