diff --git a/cmake/GhcHelper.cmake b/cmake/GhcHelper.cmake index 4d9e5a0..f1ea554 100644 --- a/cmake/GhcHelper.cmake +++ b/cmake/GhcHelper.cmake @@ -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() diff --git a/include/ghc/filesystem.hpp b/include/ghc/filesystem.hpp index 1770d73..9213f72 100644 --- a/include/ghc/filesystem.hpp +++ b/include/ghc/filesystem.hpp @@ -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 +#include +#include +#ifndef _S_IREAD +// Some tests disable the previous inclusion of sys/stat.h; however, we need it when using old versions of MinGW. +#include +#endif +#endif +#endif + namespace ghc { namespace filesystem { @@ -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 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 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 @@ -1159,6 +1232,16 @@ class basic_filebuf : public std::basic_filebuf { #ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR return std::basic_filebuf::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(p, mode); + if (!filebuf.is_open()) { + return nullptr; + } + this->swap(filebuf); + return this; #else return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; #endif @@ -1176,6 +1259,20 @@ class basic_ifstream : public std::basic_ifstream { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::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(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(p.string().c_str(), mode) @@ -1199,6 +1296,20 @@ class basic_ofstream : public std::basic_ofstream { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::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(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(p.string().c_str(), mode) @@ -1222,6 +1333,20 @@ class basic_fstream : public std::basic_fstream { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::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(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(p.string().c_str(), mode) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2275c61..bf3eec3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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() @@ -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() @@ -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)