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

Add gulrak filesystem, a c++17 compliant std::filesystem polyfill. #50624

Merged
merged 5 commits into from
Aug 27, 2021
Merged
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
2 changes: 2 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ PLF List and PLF Colony (src/list.h, src/colony.h) are licensed under the zLib l

getpost (tools/json_tools/format/getpost.h) is licensed under the MIT license, see file for text of license.

gulrak filesystem (src/third-party/ghc/*) is licensed under the MIT license, see file for text of license.

libintl-lite is licensed under Boost Software License 1.0. Visit https://www.boost.org/users/license.html to read the license.

libbacktrace is licensed under a BSD license (https://github.com/ianlancetaylor/libbacktrace/blob/master/LICENSE). The full license text is as follows:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ ifeq ($(PCH), 1)
endif
endif

CPPFLAGS += -isystem ${SRC_DIR}/third-party
CXXFLAGS += $(WARNINGS) $(DEBUG) $(DEBUGSYMS) $(PROFILE) $(OTHERS) -MMD -MP
TOOL_CXXFLAGS = -DCATA_IN_TOOL

Expand Down
2 changes: 1 addition & 1 deletion msvc-full-features/Cataclysm-common.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<CompileAsManaged>false</CompileAsManaged>
<AdditionalOptions>/bigobj /utf-8 %(AdditionalOptions)</AdditionalOptions>
<LanguageStandard>stdcpp14</LanguageStandard>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\src;$(MSBuildThisFileDirectory)..\src\third-party;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
<DisableSpecificWarnings>4819;4146;26495;26444;26451;4068;6319;6237</DisableSpecificWarnings>
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.1.4)

add_subdirectory(${CMAKE_SOURCE_DIR}/src/third-party)

set(MAIN_CPP ${CMAKE_SOURCE_DIR}/src/main.cpp)
set(MESSAGES_CPP ${CMAKE_SOURCE_DIR}/src/messages.cpp)
set(RESOURCE_RC ${CMAKE_SOURCE_DIR}/src/resource.rc)
Expand Down Expand Up @@ -33,6 +35,8 @@ if (TILES)
${CATACLYSM_DDA_HEADERS})
target_include_directories(cataclysm-tiles-common INTERFACE ${CMAKE_SOURCE_DIR}/src)

target_link_libraries(cataclysm-tiles-common third-party)

if (WIN32)
add_definitions(-DUSE_WINMAIN)
add_executable(
Expand Down Expand Up @@ -130,6 +134,8 @@ if (CURSES)
${CATACLYSM_DDA_HEADERS})
target_include_directories(cataclysm-common INTERFACE ${CMAKE_SOURCE_DIR}/src)

target_link_libraries(cataclysm-common third-party)

if (WIN32)
add_executable(cataclysm
${MAIN_CPP}
Expand Down
212 changes: 43 additions & 169 deletions src/filesystem.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
#include "filesystem.h"

#include <cstddef>
// IWYU pragma: no_include <sys/dirent.h>
// IWYU pragma: no_include <sys/errno.h>
// FILE I/O
#include <sys/stat.h>
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
Expand All @@ -17,103 +13,50 @@
#include <type_traits>
#include <vector>

#include <ghc/fs_std_fwd.hpp>

#include "cata_utility.h"
#include "debug.h"

#if defined(_MSC_VER)
# include <direct.h>

# include "wdirent.h"
#else
# include <dirent.h>
# include <unistd.h>
#endif

#if defined(_WIN32)
# include "platform_win.h"
#endif

namespace
{

#if defined(_WIN32)
bool do_mkdir( const std::string &path, const int /*mode*/ )
{
#if defined(_MSC_VER)
return _mkdir( path.c_str() ) == 0;
#else
return mkdir( path.c_str() ) == 0;
#endif
}
#else
bool do_mkdir( const std::string &path, const int mode )
{
return mkdir( path.c_str(), mode ) == 0;
}
#endif

} //anonymous namespace

bool assure_dir_exist( const std::string &path )
{
return do_mkdir( path, 0777 ) || ( errno == EEXIST && dir_exist( path ) );
std::error_code ec;
fs::path p( path );
bool ret = fs::is_directory( p, ec ) || ( !fs::exists( p, ec ) && fs::create_directories( p, ec ) );
return !ec && ret;
}

bool dir_exist( const std::string &path )
{
DIR *dir = opendir( path.c_str() );
if( dir != nullptr ) {
closedir( dir );
return true;
}
return false;
return fs::is_directory( path );
}

bool file_exist( const std::string &path )
{
struct stat buffer;
return ( stat( path.c_str(), &buffer ) == 0 );
return fs::exists( path ) && !fs::is_directory( path );
}

#if defined(_WIN32)
bool remove_file( const std::string &path )
{
return DeleteFile( path.c_str() ) != 0;
std::error_code ec;
return fs::remove( path, ec );
}
#else
bool remove_file( const std::string &path )
{
return unlink( path.c_str() ) == 0;
}
#endif

#if defined(_WIN32)
bool rename_file( const std::string &old_path, const std::string &new_path )
{
// Windows rename function does not override existing targets, so we
// have to remove the target to make it compatible with the Linux rename
if( file_exist( new_path ) ) {
if( !remove_file( new_path ) ) {
return false;
}
}

return rename( old_path.c_str(), new_path.c_str() ) == 0;
}
#else
bool rename_file( const std::string &old_path, const std::string &new_path )
{
return rename( old_path.c_str(), new_path.c_str() ) == 0;
std::error_code ec;
fs::rename( old_path, new_path, ec );
return !ec;
}
#endif

bool remove_directory( const std::string &path )
{
#if defined(_WIN32)
return RemoveDirectory( path.c_str() );
#else
return remove( path.c_str() ) == 0;
#endif
std::error_code ec;
return fs::remove( path, ec );
}

const char *cata_files::eol()
Expand Down Expand Up @@ -142,124 +85,56 @@ namespace
template <typename Function>
void for_each_dir_entry( const std::string &path, Function function )
{
using dir_ptr = DIR*;

if( path.empty() ) {
return;
}

const dir_ptr root = opendir( path.c_str() );
if( !root ) {
const char *e_str = strerror( errno );
std::error_code ec;
auto dir_iter = fs::directory_iterator( path, ec );
if( ec ) {
std::string e_str = ec.message();
DebugLog( D_WARNING, D_MAIN ) << "opendir [" << path << "] failed with \"" << e_str << "\".";
return;
}

while( const dirent *entry = readdir( root ) ) {
function( *entry );
for( auto &dir_entry : dir_iter ) {
function( dir_entry );
}
closedir( root );
}

//--------------------------------------------------------------------------------------------------
#if !defined(_WIN32)
std::string resolve_path( const std::string &full_path )
{
char *const result_str = realpath( full_path.c_str(), nullptr );
if( !result_str ) {
char *const e_str = strerror( errno );
DebugLog( D_WARNING, D_MAIN ) << "realpath [" << full_path << "] failed with \"" << e_str << "\".";
return {};
}

std::string result( result_str );
free( result_str );
return result;
}
#endif

// Returns true if entry is a directory, false otherwise.
//--------------------------------------------------------------------------------------------------
bool is_directory_stat( const std::string &full_path )
bool is_directory( const fs::directory_entry &entry, const std::string &full_path )
{
if( full_path.empty() ) {
return false;
}
// We do an extra dance here because directory_entry might not have a cached valid stat result.
std::error_code ec;
bool result = entry.is_directory( ec );

struct stat result;
if( stat( full_path.c_str(), &result ) != 0 ) {
const char *e_str = strerror( errno );
if( ec ) {
std::string e_str = ec.message();
DebugLog( D_WARNING, D_MAIN ) << "stat [" << full_path << "] failed with \"" << e_str << "\".";
return false;
}

if( S_ISDIR( result.st_mode ) ) {
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
return true;
}

#if !defined(_WIN32)
if( S_ISLNK( result.st_mode ) ) {
return is_directory_stat( resolve_path( full_path ) );
}
#endif

return false;
}

//--------------------------------------------------------------------------------------------------
// Returns true if entry is a directory, false otherwise.
//--------------------------------------------------------------------------------------------------
#if defined(__MINGW32__)
bool is_directory( const dirent &/*entry*/, const std::string &full_path )
{
// no dirent::d_type
return is_directory_stat( full_path );
}
#else
bool is_directory( const dirent &entry, const std::string &full_path )
{
if( entry.d_type == DT_DIR ) {
return true;
}

#if !defined(_WIN32)
if( entry.d_type == DT_LNK ) {
return is_directory_stat( resolve_path( full_path ) );
}
#endif

if( entry.d_type == DT_UNKNOWN ) {
return is_directory_stat( full_path );
}

return false;
}
#endif

//--------------------------------------------------------------------------------------------------
// Returns true if the name of entry matches "." or "..".
//--------------------------------------------------------------------------------------------------
bool is_special_dir( const dirent &entry )
{
return !strncmp( entry.d_name, ".", sizeof( entry.d_name ) - 1 ) ||
!strncmp( entry.d_name, "..", sizeof( entry.d_name ) - 1 );
return result;
}

//--------------------------------------------------------------------------------------------------
// If at_end is true, returns whether entry's name ends in match.
// Otherwise, returns whether entry's name contains match.
//--------------------------------------------------------------------------------------------------
bool name_contains( const dirent &entry, const std::string &match, const bool at_end )
bool name_contains( const fs::directory_entry &entry, const std::string &match, const bool at_end )
{
const size_t len_fname = strlen( entry.d_name );
std::string entry_name = entry.path().filename().u8string();
const size_t len_fname = entry_name.length();
const size_t len_match = match.length();

if( len_match > len_fname ) {
return false;
}

const size_t offset = at_end ? ( len_fname - len_match ) : 0;
return strstr( entry.d_name + offset, match.c_str() ) != nullptr;
return strstr( entry_name.c_str() + offset, match.c_str() ) != nullptr;
}

//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -288,13 +163,9 @@ std::vector<std::string> find_file_if_bfs( const std::string &root_path,
const std::ptrdiff_t n_dirs = static_cast<std::ptrdiff_t>( directories.size() );
const std::ptrdiff_t n_results = static_cast<std::ptrdiff_t>( results.size() );

for_each_dir_entry( path, [&]( const dirent & entry ) {
// exclude special directories.
if( is_special_dir( entry ) ) {
return;
}

const auto full_path = path + "/" + entry.d_name;
// We could use fs::recursive_directory_iterator maybe
for_each_dir_entry( path, [&]( const fs::directory_entry & entry ) {
const auto full_path = entry.path().generic_u8string();

// don't add files ending in '~'.
if( full_path.back() == '~' ) {
Expand Down Expand Up @@ -332,7 +203,8 @@ std::vector<std::string> find_file_if_bfs( const std::string &root_path,
std::vector<std::string> get_files_from_path( const std::string &pattern,
const std::string &root_path, const bool recursive_search, const bool match_extension )
{
return find_file_if_bfs( root_path, recursive_search, [&]( const dirent & entry, bool ) {
return find_file_if_bfs( root_path, recursive_search, [&]( const fs::directory_entry & entry,
bool ) {
return name_contains( entry, pattern, match_extension );
} );
}
Expand All @@ -351,7 +223,8 @@ std::vector<std::string> get_directories_with( const std::string &pattern,
return std::vector<std::string>();
}

auto files = find_file_if_bfs( root_path, recursive_search, [&]( const dirent & entry, bool ) {
auto files = find_file_if_bfs( root_path, recursive_search, [&]( const fs::directory_entry & entry,
bool ) {
return name_contains( entry, pattern, true );
} );

Expand Down Expand Up @@ -382,7 +255,8 @@ std::vector<std::string> get_directories_with( const std::vector<std::string> &p
const auto ext_beg = std::begin( patterns );
const auto ext_end = std::end( patterns );

auto files = find_file_if_bfs( root_path, recursive_search, [&]( const dirent & entry, bool ) {
auto files = find_file_if_bfs( root_path, recursive_search, [&]( const fs::directory_entry & entry,
bool ) {
return std::any_of( ext_beg, ext_end, [&]( const std::string & ext ) {
return name_contains( entry, ext, true );
} );
Expand Down
2 changes: 2 additions & 0 deletions src/filesystem_impl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// We use the forwarding header in filesystem.h, so we have to include the implementation header in a different source file than filesystem.cpp.
#include <ghc/fs_std_impl.hpp>
Loading