From fdcb378b262b93ebb603bcc1b6de98e79a037939 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 4 Feb 2022 07:06:23 -0600 Subject: [PATCH] Update to use std::filesystem rather than Boost (#238) This is intended to be a drop-in replacement for the functionality that is currently in FilesystemBoost, as is demonstrated via no test updates. Signed-off-by: Michael Carroll Co-authored-by: Louise Poubel Co-authored-by: Steve Peters --- examples/logging_performance.cc | 2 +- include/ignition/common/Filesystem.hh | 12 - include/ignition/common/SystemPaths.hh | 13 - src/CMakeLists.txt | 7 + src/DirIter.cc | 75 +++ src/Filesystem.cc | 570 ++++++++-------------- src/FilesystemBoost.cc | 638 ------------------------- src/SystemPaths.cc | 19 +- src/TempDirectory.cc | 63 +-- src/Util.cc | 27 +- 10 files changed, 325 insertions(+), 1101 deletions(-) create mode 100644 src/DirIter.cc delete mode 100644 src/FilesystemBoost.cc diff --git a/examples/logging_performance.cc b/examples/logging_performance.cc index c9c88a6d0..f92fa0fdb 100644 --- a/examples/logging_performance.cc +++ b/examples/logging_performance.cc @@ -42,7 +42,7 @@ void WriteToFile(std::string result_filename, std::string content) std::cerr << "Error writing to " << result_filename << std::endl; } out << content << std::flush; - std::cout << content; + //std::cout << content; } void MeasurePeakDuringLogWrites(const size_t id, std::vector &result) diff --git a/include/ignition/common/Filesystem.hh b/include/ignition/common/Filesystem.hh index aa36f0a6f..fb765d523 100644 --- a/include/ignition/common/Filesystem.hh +++ b/include/ignition/common/Filesystem.hh @@ -264,9 +264,6 @@ namespace ignition /// \param[in] _in Directory to iterate over. public: explicit DirIter(const std::string &_in); - /// \brief Destructor - public: ~DirIter(); - /// \brief Dereference operator; returns current directory record. /// \return A string representing the entire path of the directory /// record. @@ -282,15 +279,6 @@ namespace ignition /// \return true if the iterators are equal, false otherwise. public: bool operator!=(const DirIter &_other) const; - /// \brief Move to the next directory record, skipping . and .. records. - private: void Next(); - - /// \brief Set the internal variable to the empty string. - private: void SetInternalEmpty(); - - /// \brief Close an open directory handle. - private: void CloseHandle(); - /// \brief Pointer to private data. IGN_UTILS_IMPL_PTR(dataPtr) }; diff --git a/include/ignition/common/SystemPaths.hh b/include/ignition/common/SystemPaths.hh index 9b8224525..d368dc737 100644 --- a/include/ignition/common/SystemPaths.hh +++ b/include/ignition/common/SystemPaths.hh @@ -17,16 +17,6 @@ #ifndef IGNITION_COMMON_SYSTEMPATHS_HH_ #define IGNITION_COMMON_SYSTEMPATHS_HH_ -#include - -#ifdef _WIN32 - #include - #define GetCurrentDir _getcwd -#else - #include - #define GetCurrentDir getcwd -#endif - #include #include #include @@ -41,9 +31,6 @@ namespace ignition { namespace common { - // Forward declare private data class - class SystemPathsPrivate; - /// \class SystemPaths SystemPaths.hh ignition/common/SystemPaths.hh /// \brief Functions to handle getting system paths, keeps track of: /// \li SystemPaths#pluginPaths - plugin library paths diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca25e7ff6..62cc2ef15 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,10 +8,17 @@ ign_create_core_library( SOURCES ${sources} CXX_STANDARD 17) +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + set(CXX_FILESYSTEM_LIBRARIES stdc++fs) +else() + set(CXX_FILESYSTEM_LIBRARIES) +endif() + # Link the libraries that we always need target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} PRIVATE ${DL_TARGET} + ${CXX_FILESYSTEM_LIBRARIES} PUBLIC ignition-utils${IGN_UTILS_VER}::ignition-utils${IGN_UTILS_VER} ) diff --git a/src/DirIter.cc b/src/DirIter.cc new file mode 100644 index 000000000..d8b19ab90 --- /dev/null +++ b/src/DirIter.cc @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include + +#include "ignition/common/Filesystem.hh" + +namespace fs = std::filesystem; + +namespace ignition +{ +namespace common +{ + +class DirIter::Implementation +{ + /// \brief Filesystem iterator that this class is wrapping + public: fs::directory_iterator it; +}; + +////////////////////////////////////////////////// +DirIter::DirIter(): + dataPtr(ignition::utils::MakeImpl()) +{ + this->dataPtr->it = fs::directory_iterator(); +} + +////////////////////////////////////////////////// +DirIter::DirIter(const std::string &_in): + DirIter() +{ + try + { + this->dataPtr->it = fs::directory_iterator(_in); + } + catch (const fs::filesystem_error &ex) + { + } +} + +////////////////////////////////////////////////// +std::string DirIter::operator*() const +{ + return this->dataPtr->it->path().string(); +} + +////////////////////////////////////////////////// +const DirIter &DirIter::operator++() +{ + this->dataPtr->it++; + return *this; +} + +////////////////////////////////////////////////// +bool DirIter::operator!=(const DirIter &_other) const +{ + return (this->dataPtr->it != _other.dataPtr->it); +} + +} // namespace common +} // namespace ignition diff --git a/src/Filesystem.cc b/src/Filesystem.cc index 107b2ca7d..ba4fda7e8 100644 --- a/src/Filesystem.cc +++ b/src/Filesystem.cc @@ -15,16 +15,10 @@ * */ -#include - -#ifdef __linux__ -#include -#endif - -#include #include #include #include +#include #include #include #include @@ -36,466 +30,292 @@ #include #include #include - -#ifndef _WIN32 -#include -#include -#include -#else -#include -#include -#include "win_dirent.h" -#include "PrintWindowsSystemWarning.hh" -#endif - #include "ignition/common/Filesystem.hh" -#ifdef _WIN32 -# define IGN_PATH_MAX _MAX_PATH -#elif defined(PATH_MAX) -# define IGN_PATH_MAX PATH_MAX -#elif defined(_XOPEN_PATH_MAX) -# define IGN_PATH_MAX _XOPEN_PATH_MAX -#else -# define IGN_PATH_MAX _POSIX_PATH_MAX -#endif - -namespace igncmn = ignition::common; -using namespace ignition; -using namespace igncmn; - -///////////////////////////////////////////////// -bool ignition::common::isFile(const std::string &_path) -{ - std::ifstream f(_path); - return (!isDirectory(_path)) && f.good(); -} +namespace fs = std::filesystem; ///////////////////////////////////////////////// -bool ignition::common::removeDirectory(const std::string &_path, - const FilesystemWarningOp _warningOp) +// Return true if success, false if error +inline bool fsWarn(const std::string &_fcn, + const std::error_code &_ec, + const ignition::common::FilesystemWarningOp &_warningOp = + ignition::common::FSWO_LOG_WARNINGS) { - bool removed = false; - if (ignition::common::isDirectory(_path)) + if (_ec) { -#ifdef _WIN32 - removed = RemoveDirectory(_path.c_str()); - if (!removed && FSWO_LOG_WARNINGS == _warningOp) + if (ignition::common::FSWO_LOG_WARNINGS == _warningOp) { - ignition::common::PrintWindowsSystemWarning( - "Failed to remove directory [" + _path + "]"); + ignwarn << "Failed ignition::common::" << _fcn + << " (ec: " << _ec << " " << _ec.message() << ")\n"; } -#else - removed = (rmdir(_path.c_str()) == 0); - if (!removed) - { - // A sym link would end up here - removed = (std::remove(_path.c_str()) == 0); - } - - if (!removed && FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Failed to remove directory [" + _path + "]: " - << std::strerror(errno) << "\n"; - } -#endif - } - else if (_warningOp) - { - ignwarn << "The path [" << _path << "] does not refer to a directory\n"; + return false; } - - return removed; + return true; } ///////////////////////////////////////////////// -bool ignition::common::removeFile(const std::string &_existingFilename, - const FilesystemWarningOp _warningOp) +bool ignition::common::exists(const std::string &_path) { - const bool removed = (std::remove(_existingFilename.c_str()) == 0); - if (!removed && FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Failed to remove file [" << _existingFilename << "]: " - << std::strerror(errno) << "\n"; - } + return fs::exists(_path); +} - return removed; +///////////////////////////////////////////////// +bool ignition::common::isDirectory(const std::string &_path) +{ + return fs::is_directory(_path); } ///////////////////////////////////////////////// -bool ignition::common::removeDirectoryOrFile( - const std::string &_path, - const FilesystemWarningOp _warningOp) +bool ignition::common::isFile(const std::string &_path) { - if (ignition::common::isDirectory(_path)) - { - return ignition::common::removeDirectory(_path, _warningOp); - } - else if (ignition::common::isFile(_path)) - { - return ignition::common::removeFile(_path, _warningOp); - } - else if (FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "The path [" << _path << "] does not refer to a " - << "directory nor to a file\n"; - } - return false; + return fs::is_regular_file(_path); } ///////////////////////////////////////////////// -bool ignition::common::removeAll(const std::string &_path, - const FilesystemWarningOp _warningOp) +bool ignition::common::createDirectory(const std::string &_path) { - if (ignition::common::isDirectory(_path)) - { - DIR *dir = opendir(_path.c_str()); - if (dir) - { - struct dirent *p; - while ((p=readdir(dir))) - { - // Skip special files. - if (!std::strcmp(p->d_name, ".") || !std::strcmp(p->d_name, "..")) - continue; - - const auto removed = ignition::common::removeAll( - ignition::common::joinPaths(_path, p->d_name), _warningOp); - if (!removed) - return false; - } - } - closedir(dir); - } + std::error_code ec; + fs::create_directory(_path, ec); + return fsWarn("createDirectory", ec); +} - return ignition::common::removeDirectoryOrFile(_path, _warningOp); +///////////////////////////////////////////////// +bool ignition::common::createDirectories(const std::string &_path) +{ + std::error_code ec; + // Disregard return of create_directories, because it may return false if the + // directory is not actually created (already exists) + bool created = fs::create_directories(_path, ec); + (void) created; + return fsWarn("createDirectories", ec); } ///////////////////////////////////////////////// -bool ignition::common::moveFile(const std::string &_existingFilename, - const std::string &_newFilename, - const FilesystemWarningOp _warningOp) +std::string const ignition::common::separator(std::string const &_s) { - if (!copyFile(_existingFilename, _newFilename, _warningOp)) - return false; + fs::path path(_s); + return (_s / fs::path("")).string(); +} - if (removeFile(_existingFilename, _warningOp)) - return true; +///////////////////////////////////////////////// +void ignition::common::changeFromUnixPath(std::string &_path) { + std::replace(_path.begin(), _path.end(), '/', + static_cast(fs::path::preferred_separator)); +} - // The original file could not be removed, which means we are not - // able to "move" it (we can only copy it, apparently). Since this - // function is meant to move files, and we have failed to move the - // file, we should remove the copy that we made earlier. - removeFile(_newFilename, _warningOp); +///////////////////////////////////////////////// +std::string ignition::common::copyFromUnixPath(const std::string &_path) +{ + std::string copy = _path; + changeFromUnixPath(copy); + return copy; +} - return false; +///////////////////////////////////////////////// +void ignition::common::changeToUnixPath(std::string &_path) { + std::replace(_path.begin(), _path.end(), + static_cast(fs::path::preferred_separator), '/'); } ///////////////////////////////////////////////// -std::string ignition::common::absPath(const std::string &_path) +std::string ignition::common::copyToUnixPath(const std::string &_path) { - std::string result; - - // cppcheck-suppress ConfigurationNotChecked - char path[IGN_PATH_MAX] = ""; -#ifdef _WIN32 - if (GetFullPathName(_path.c_str(), IGN_PATH_MAX, &path[0], nullptr) != 0) -#else - if (realpath(_path.c_str(), &path[0]) != nullptr) -#endif - result = path; - else if (!_path.empty()) - { - // If _path is an absolute path, then return _path. - // An absolute path on Windows is a character followed by a colon and a - // backslash. - if (_path.compare(0, 1, "/") == 0 || _path.compare(1, 3, ":\\") == 0) - result = _path; - // Otherwise return the current working directory with _path appended. - else - result = joinPaths(ignition::common::cwd(), _path); - } - - ignition::common::replaceAll(result, result, "//", "/"); + std::string copy = _path; + changeToUnixPath(copy); + return copy;; +} - return result; +///////////////////////////////////////////////// +std::string ignition::common::absPath(const std::string &_path) +{ + return fs::absolute(_path).string(); } -// This is help function to handle windows paths, there are a mix between '/' -// and backslashes. -// joinPaths uses the system separator, in Windows this generate some issues -// with URIs -std::string checkWindowsPath(const std::string _path) +///////////////////////////////////////////////// +std::string ignition::common::joinPaths( + const std::string &_path1, const std::string &_path2) { - if (_path.empty()) - return _path; + fs::path p1{_path1}; + fs::path p2{_path2}; - // Check if this is a http or https, if so change backslashes generated by - // jointPaths to '/' - if ((_path.size() > 7 && 0 == _path.compare(0, 7, "http://")) || - (_path.size() > 8 && 0 == _path.compare(0, 8, "https://"))) - { - return std::regex_replace(_path, std::regex(R"(\\)"), "/"); - } + bool is_url = false; + + if (_path1.find("://") == std::string::npos) + p1 = p1.lexically_normal(); + else + is_url = true; - // This is a Windows path, convert all '/' into backslashes - std::string result = std::regex_replace(_path, std::regex(R"(/)"), "\\"); - std::string drive_letters; + // TODO(mjcarroll) Address the case that path2 is also a URI. + // It's likely not a valid scenario, but not currently covered by our test + // suite and doesn't return an error. + if (_path2.find("://") == std::string::npos) + p2 = p2.lexically_normal(); + else + is_url = true; - // only Windows contains absolute paths starting with drive letters - if (result.length() > 3 && 0 == result.compare(1, 2, ":\\")) + if (p2.string()[0] == fs::path::preferred_separator) { - drive_letters = result.substr(0, 3); - result = result.substr(3); + p2 = fs::path{p2.string().substr(1)}; } - result = drive_letters + std::regex_replace( - result, std::regex("[<>:\"|?*]"), ""); - return result; -} -////////////////////////////////////////////////// -std::string ignition::common::joinPaths(const std::string &_path1, - const std::string &_path2) -{ - - /// This function is used to avoid duplicated path separators at the - /// beginning/end of the string, and between the two paths being joined. - /// \param[in] _path This is the string to sanitize. - /// \param[in] _stripLeading True if the leading separator should be - /// removed. - auto sanitizeSlashes = [](const std::string &_path, - bool _stripLeading = false) - { - // Shortcut - if (_path.empty()) - return _path; - - std::string result = _path; - - // Use the appropriate character for each platform. -#ifndef _WIN32 - char replacement = '/'; -#else - char replacement = '\\'; -#endif - - // Sanitize the start of the path. - size_t index = 0; - size_t leadingIndex = _stripLeading ? 0 : 1; - for (; index < result.length() && result[index] == replacement; ++index) - { - } - if (index > leadingIndex) - result.erase(leadingIndex, index-leadingIndex); + auto ret = (p1 / p2); - // Sanitize the end of the path. - index = result.length()-1; - for (; index < result.length() && result[index] == replacement; --index) - { - } - index += 1; - if (index < result.length()-1) - result.erase(index+1); - return result; - }; - - std::string path; -#ifndef _WIN32 - path = sanitizeSlashes(sanitizeSlashes(separator(_path1)) + - sanitizeSlashes(_path2, true)); -#else // _WIN32 - std::string path1 = sanitizeSlashes(checkWindowsPath(_path1)); - std::string path2 = sanitizeSlashes(checkWindowsPath(_path2), true); - std::vector combined(path1.length() + path2.length() + 2); - if (::PathCombineA(combined.data(), path1.c_str(), path2.c_str()) != NULL) + if (is_url) { - path = sanitizeSlashes(checkWindowsPath(std::string(combined.data()))); + return copyToUnixPath(ret.string()); } else { - path = sanitizeSlashes(checkWindowsPath(separator(path1) + path2)); + return ret.lexically_normal().string(); } -#endif // _WIN32 - return path; } ///////////////////////////////////////////////// -std::string ignition::common::parentPath(const std::string &_path) +std::string ignition::common::cwd() { - std::string result; + std::error_code ec; + auto curdir = fs::current_path(ec); - size_t last_sep = _path.find_last_of(separator("")); - // If slash is the last character, find its parent directory - if (last_sep == _path.length() - 1) - last_sep = _path.substr(0, last_sep).find_last_of(separator("")); - - result = _path.substr(0, last_sep); + if (!fsWarn("cwd", ec)) + { + curdir = ""; + } - return result; + return curdir.string(); } ///////////////////////////////////////////////// -bool ignition::common::copyFile(const std::string &_existingFilename, - const std::string &_newFilename, - const FilesystemWarningOp _warningOp) +bool ignition::common::chdir(const std::string &_dir) { - std::string absExistingFilename = - ignition::common::absPath(_existingFilename); - std::string absNewFilename = ignition::common::absPath(_newFilename); + std::error_code ec; + fs::current_path(_dir, ec); + return fsWarn("chdir", ec); +} - if (absExistingFilename == absNewFilename) - return false; +///////////////////////////////////////////////// +std::string ignition::common::basename(const std::string &_path) +{ + fs::path p(_path); + // Maintain compatibility with ign-common + if (*_path.rbegin() == fs::path::preferred_separator) + p = fs::path(_path.substr(0, _path.size()-1)); + return p.filename().string(); +} -#ifdef _WIN32 - const bool copied = CopyFile(absExistingFilename.c_str(), - absNewFilename.c_str(), false); +///////////////////////////////////////////////// +std::string ignition::common::parentPath(const std::string &_path) +{ + fs::path p(_path); + // Maintain compatibility with ign-common + if (*_path.rbegin() == fs::path::preferred_separator) + p = fs::path(_path.substr(0, _path.size()-1)); + return p.parent_path().string(); +} - if (!copied && FSWO_LOG_WARNINGS == _warningOp) - { - ignition::common::PrintWindowsSystemWarning( - "Failed to copy file [" + absExistingFilename - + "] to [" + absNewFilename + "]"); - } +///////////////////////////////////////////////// +bool ignition::common::copyFile( + const std::string &_existingFilename, + const std::string &_newFilename, + const FilesystemWarningOp _warningOp) +{ + const auto copyOptions = fs::copy_options::overwrite_existing; + std::error_code ec; + auto ret = fs::copy_file(_existingFilename, _newFilename, copyOptions, ec); + return ret && fsWarn("copyFile", ec, _warningOp); +} - return copied; -#else - bool result = false; - std::ifstream in(absExistingFilename.c_str(), std::ifstream::binary); +///////////////////////////////////////////////// +bool ignition::common::copyDirectory( + const std::string &_existingDirname, + const std::string &_newDirname, + const FilesystemWarningOp _warningOp) +{ + const auto copyOptions = fs::copy_options::recursive + | fs::copy_options::overwrite_existing; - if (in.good()) - { - std::ofstream out(absNewFilename.c_str(), - std::ifstream::trunc | std::ifstream::binary); - if (out.good()) - { - out << in.rdbuf(); - result = ignition::common::isFile(absNewFilename); - } - else if (FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Failed to create file [" << absNewFilename << "]: " - << std::strerror(errno) << "\n"; - } - out.close(); - } - else if (FSWO_LOG_WARNINGS == _warningOp) + // std::filesystem won't create intermediate directories + // before copying, this maintains compatibility with ignition behavior. + if (!ignition::common::createDirectories(_newDirname)) { - ignwarn << "Failed to open file [" << absExistingFilename << "]: " - << std::strerror(errno) << "\n"; + return false; } - in.close(); - return result; -#endif + std::error_code ec; + fs::copy(_existingDirname, _newDirname, copyOptions, ec); + return fsWarn("copyDirectory", ec, _warningOp); } ///////////////////////////////////////////////// -bool ignition::common::copyDirectory(const std::string &_existingDirname, - const std::string &_newDirname, - const FilesystemWarningOp _warningOp) +bool ignition::common::moveFile( + const std::string &_existingFilename, + const std::string &_newFilename, + const FilesystemWarningOp _warningOp) { - // Check whether source directory exists - if (!exists(_existingDirname) || !isDirectory(_existingDirname)) + std::error_code ec; + fs::rename(_existingFilename, _newFilename, ec); + return fsWarn("moveFile", ec, _warningOp); +} + +///////////////////////////////////////////////// +bool ignition::common::removeDirectory( + const std::string &_path, + const FilesystemWarningOp _warningOp) +{ + if (!isDirectory(_path)) { if (FSWO_LOG_WARNINGS == _warningOp) { - ignwarn << "Source directory [" << _existingDirname - << "] does not exist or is not a directory" << std::endl; + ignwarn << "Cannot remove, not a directory [" << _path << "]\n"; } + return false; } - if (exists(_newDirname)) - { - if (!removeAll(_newDirname, _warningOp)) - { - if (FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Unable to remove existing destination directory [" - << _newDirname << "]\n"; - } - return false; - } - } - // Create the destination directory - if (!createDirectories(_newDirname)) + return removeDirectoryOrFile(_path, _warningOp); +} + +///////////////////////////////////////////////// +bool ignition::common::removeFile( + const std::string &_existingFilename, + const FilesystemWarningOp _warningOp) +{ + if (!isFile(_existingFilename)) { if (FSWO_LOG_WARNINGS == _warningOp) { - ignwarn << "Unable to create the destination directory [" - << _newDirname << "], please check the permission\n"; + ignwarn << "Cannot remove, not a file [" << _existingFilename << "]\n"; } return false; } - // Start copy from source to destination directory - for (DirIter file(_existingDirname); file != DirIter(); ++file) - { - std::string current(*file); - if (isDirectory(current)) - { - // Copy recursively - if (!copyDirectory(current, joinPaths(_newDirname, basename(current)), - _warningOp)) - { - if (FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Unable to copy directory to [" - << joinPaths(_newDirname, basename(current)) << "]\n"; - } - return false; - } - } - else - { - if (!copyFile(current, joinPaths(_newDirname, basename(current)), - _warningOp)) - { - if (FSWO_LOG_WARNINGS == _warningOp) - { - ignwarn << "Unable to copy file to [" - << joinPaths(_newDirname, basename(current)) << "]\n"; - } - return false; - } - } - } - return true; + return removeDirectoryOrFile(_existingFilename, _warningOp); } ///////////////////////////////////////////////// -bool ignition::common::createDirectories(const std::string &_path) +bool ignition::common::removeDirectoryOrFile( + const std::string &_path, + const FilesystemWarningOp _warningOp) { - size_t index = 0; - while (index < _path.size()) - { - size_t end = _path.find(separator(""), index+1); - std::string dir = _path.substr(0, end); - if (!exists(dir)) - { -#ifdef _WIN32 - dir = checkWindowsPath(dir); - if (_mkdir(dir.c_str()) != 0) - { -#else - // cppcheck-suppress ConfigurationNotChecked - if (mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) - { -#endif - ignerr << "Failed to create directory [" + dir + "]: " - << std::strerror(errno) << std::endl; - return false; - } - } - index = end; - } + std::error_code ec; + auto ret = fs::remove(_path, ec); + fsWarn("removeDirectoryOrFile", ec, _warningOp); + return ret; +} - return true; +///////////////////////////////////////////////// +bool ignition::common::removeAll( + const std::string &_path, + const FilesystemWarningOp _warningOp) +{ + std::error_code ec; + fs::remove_all(_path, ec); + return fsWarn("removeAll", ec, _warningOp); } -////////////////////////////////////////////////// -std::string ignition::common::uniqueFilePath(const std::string &_pathAndName, - const std::string &_extension) +///////////////////////////////////////////////// +std::string ignition::common::uniqueFilePath( + const std::string &_pathAndName, + const std::string &_extension) { std::string result = _pathAndName + "." + _extension; int count = 1; @@ -510,7 +330,7 @@ std::string ignition::common::uniqueFilePath(const std::string &_pathAndName, return result; } -////////////////////////////////////////////////// +///////////////////////////////////////////////// std::string ignition::common::uniqueDirectoryPath(const std::string &_dir) { std::string result = _dir; diff --git a/src/FilesystemBoost.cc b/src/FilesystemBoost.cc deleted file mode 100644 index 12e5cabc5..000000000 --- a/src/FilesystemBoost.cc +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright 2002-2009, 2014 Beman Dawes - * Copyright 2001 Dietmar Kuehl - * - * Distributed under the Boost Software License, Version 1.0. - * - * Boost Software License - Version 1.0 - August 17th, 2003 - * - * Permission is hereby granted, free of charge, to any person or organization - * obtaining a copy of the software and accompanying documentation covered by - * this license (the "Software") to use, reproduce, display, distribute, - * execute, and transmit the Software, and to prepare derivative works of the - * Software, and to permit third-parties to whom the Software is furnished to - * do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, including - * the above license grant, this restriction and the following disclaimer, - * must be included in all copies of the Software, in whole or in part, and - * all derivative works of the Software, unless such copies or derivative - * works are solely in the form of machine-executable object code generated by - * a source language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * Most of this code was borrowed from Boost in - * libs/filesystem/src/operations.cpp and - * libs/filesystem/include/boost/filesystem/operations.hpp. - */ - -#include -#include -#include -#include - -#ifndef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#endif - -#include "ignition/common/Console.hh" -#include "ignition/common/Filesystem.hh" - -namespace ignition -{ - namespace common - { - /// \internal - /// \brief Private data for the DirIter class. - class DirIter::Implementation - { - /// \def current - /// \brief The current directory item. - public: std::string current; - - /// \def dirname - /// \brief The original path to the directory. - public: std::string dirname; - - /// \def handle - /// \brief Opaque handle for holding the directory iterator. - public: void *handle; - - /// \def end - /// \brief Private variable to indicate whether the iterator has reached - /// the end. - public: bool end; - }; - -#ifndef _WIN32 - - static const char preferred_separator = '/'; - - ////////////////////////////////////////////////// - bool exists(const std::string &_path) - { - struct stat path_stat; - return ::stat(_path.c_str(), &path_stat) == 0; - } - - ////////////////////////////////////////////////// - bool isDirectory(const std::string &_path) - { - struct stat path_stat; - - if (::stat(_path.c_str(), &path_stat) != 0) - { - return false; - } - - return S_ISDIR(path_stat.st_mode); - } - - ////////////////////////////////////////////////// - bool createDirectory(const std::string &_path) - { - return ::mkdir(_path.c_str(), S_IRWXU|S_IRWXG|S_IRWXO) == 0; - } - - ////////////////////////////////////////////////// - std::string cwd() - { - std::string cur; - // loop 'til buffer large enough - for (int32_t path_max = 128;; path_max *= 2) - { - std::vector buf(path_max); - - if (::getcwd(buf.data(), buf.size()) == 0) - { - if (errno != ERANGE) - { - break; - } - } - else - { - char resolved[PATH_MAX]; - - if (realpath(buf.data(), resolved) != nullptr) - { - cur = std::string(resolved); - } - break; - } - } - return cur; - } - - ////////////////////////////////////////////////// - bool chdir(const std::string& _dir) - { - return ::chdir(_dir.c_str()) == 0; - } - - ////////////////////////////////////////////////// - DirIter::DirIter(const std::string &_in) - : dataPtr(ignition::utils::MakeImpl()) - { - this->dataPtr->dirname = _in; - - this->dataPtr->current = ""; - - this->dataPtr->handle = opendir(_in.c_str()); - - this->dataPtr->end = false; - - if (this->dataPtr->handle == nullptr) - { - this->dataPtr->end = true; - } - else - { - Next(); - } - } - - ////////////////////////////////////////////////// - void DirIter::Next() - { - while (true) - { - struct dirent *entry = - readdir(reinterpret_cast(this->dataPtr->handle)); // NOLINT - if (!entry) - { - this->dataPtr->end = true; - this->dataPtr->current = ""; - break; - } - - if ((strcmp(entry->d_name, ".") != 0) - && (strcmp(entry->d_name, "..") != 0)) - { - this->dataPtr->current = std::string(entry->d_name); - break; - } - } - } - - ////////////////////////////////////////////////// - void DirIter::CloseHandle() - { - closedir(reinterpret_cast(this->dataPtr->handle)); - } - -#else // Windows - - static const char preferred_separator = '\\'; - - ////////////////////////////////////////////////// - static bool not_found_error(int _errval) - { - return _errval == ERROR_FILE_NOT_FOUND - || _errval == ERROR_PATH_NOT_FOUND - || _errval == ERROR_INVALID_NAME // "tools/src/:sys:stat.h", "//foo" - || _errval == ERROR_INVALID_DRIVE // USB card reader with no card - || _errval == ERROR_NOT_READY // CD/DVD drive with no disc inserted - || _errval == ERROR_INVALID_PARAMETER // ":sys:stat.h" - || _errval == ERROR_BAD_PATHNAME // "//nosuch" on Win64 - || _errval == ERROR_BAD_NETPATH; // "//nosuch" on Win32 - } - - ////////////////////////////////////////////////// - static bool process_status_failure() - { - int errval(::GetLastError()); - - if (not_found_error(errval)) - { - return false; - } - else if ((errval == ERROR_SHARING_VIOLATION)) - { - return true; // odd, but this is what boost does - } - return false; - } - - struct handle_wrapper - { - HANDLE handle; - explicit handle_wrapper(HANDLE h) - : handle(h) {} - ~handle_wrapper() - { - if (handle != INVALID_HANDLE_VALUE) - { - ::CloseHandle(handle); - } - } - }; - - // REPARSE_DATA_BUFFER related definitions are in ntifs.h, which is part of - // the Windows Device Driver Kit. Since it's inconvenient, the definitions - // are provided here. http://msdn.microsoft.com/en-us/library/ms791514.aspx - - typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - /* Example of distinction between substitute and print names: - mklink /d ldrive c:\ - SubstituteName: c:\\??\ - PrintName: c:\ - */ - } SymbolicLinkReparseBuffer; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPointReparseBuffer; - struct { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - }; - } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; - - ////////////////////////////////////////////////// - HANDLE create_file_handle(const std::string &_path, DWORD _dwDesiredAccess, - DWORD _dwShareMode, - LPSECURITY_ATTRIBUTES _lpSecurityAttributes, - DWORD _dwCreationDisposition, - DWORD _dwFlagsAndAttributes, - HANDLE _hTemplateFile) - { - return ::CreateFileA(_path.c_str(), _dwDesiredAccess, - _dwShareMode, _lpSecurityAttributes, - _dwCreationDisposition, _dwFlagsAndAttributes, - _hTemplateFile); - } - -#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE -#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) -#endif - - ////////////////////////////////////////////////// - bool is_reparse_point_a_symlink(const std::string &_path) - { - handle_wrapper h(create_file_handle(_path, FILE_READ_EA, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - nullptr)); - if (h.handle == INVALID_HANDLE_VALUE) - { - return false; - } - - std::vector buf(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - - // Query the reparse data - DWORD dwRetLen; - BOOL result = ::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, - nullptr, 0, buf.data(), (DWORD)buf.size(), &dwRetLen, nullptr); - if (!result) - { - return false; - } - - return reinterpret_cast(&buf[0])->ReparseTag - == IO_REPARSE_TAG_SYMLINK - // Issue 9016 asked that NTFS directory junctions be recognized as - // directories. That is equivalent to recognizing them as symlinks, and - // then the normal symlink mechanism will recognize them as directories. - // - // Directory junctions are very similar to symlinks, but have some - // performance and other advantages over symlinks. They can be created - // from the command line with "mklink /j junction-name target-path". - || reinterpret_cast(&buf[0])->ReparseTag - == IO_REPARSE_TAG_MOUNT_POINT; // "directory junction" or "junction" - } - - ////////////////////////////////////////////////// - bool internal_check_path(const std::string &_path, DWORD &attr) - { - attr = ::GetFileAttributesA(_path.c_str()); - if (attr == INVALID_FILE_ATTRIBUTES) - { - return process_status_failure(); - } - - // reparse point handling; - // since GetFileAttributesW does not resolve symlinks, try to open file - // handle to discover if the file exists - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) - { - handle_wrapper h( - create_file_handle( - _path, - 0, // dwDesiredAccess; attributes only - FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, // lpSecurityAttributes - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); // hTemplateFile - if (h.handle == INVALID_HANDLE_VALUE) - { - return process_status_failure(); - } - - if (!is_reparse_point_a_symlink(_path)) - { - return true; - } - } - - return true; - } - - ////////////////////////////////////////////////// - bool exists(const std::string &_path) - { - DWORD attr; - return internal_check_path(_path, attr); - } - - ////////////////////////////////////////////////// - bool isDirectory(const std::string &_path) - { - DWORD attr; - - if (internal_check_path(_path, attr)) - { - return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - - return false; - } - - ////////////////////////////////////////////////// - bool createDirectory(const std::string &_path) - { - return ::CreateDirectoryA(_path.c_str(), 0) != 0; - } - - ////////////////////////////////////////////////// - std::string cwd() - { - DWORD sz; - if ((sz = ::GetCurrentDirectoryA(0, nullptr)) == 0) - { - sz = 1; - } - - std::vector buf(sz); - - if (::GetCurrentDirectoryA(sz, buf.data()) == 0) - { - // error - return std::string(""); - } - else - { - return buf.data(); - } - } - - ///////////////////////////////////////////////// - bool chdir(const std::string& _dir) - { - return ::SetCurrentDirectoryA(_dir.c_str()); - } - - ////////////////////////////////////////////////// - DirIter::DirIter(const std::string &_in) - : dataPtr(ignition::utils::MakeImpl()) - { - // use a form of search Sebastian Martel reports will work with Win98 - this->dataPtr->dirname = _in; - - this->dataPtr->current = ""; - - this->dataPtr->end = false; - - if (_in.empty()) - { - // To be compatible with Unix, if given an empty string, assume this - // is the end. - this->dataPtr->end = true; - return; - } - - std::string dirpath(_in); - dirpath += (dirpath.empty() - || (dirpath[dirpath.size()-1] != '\\' - && dirpath[dirpath.size()-1] != '/' - && dirpath[dirpath.size()-1] != ':'))? "\\*" : "*"; - - WIN32_FIND_DATAA data; - this->dataPtr->handle = ::FindFirstFileA(dirpath.c_str(), &data); - - // Signal EOF - if (this->dataPtr->handle == INVALID_HANDLE_VALUE) - { - this->dataPtr->handle = nullptr; - this->dataPtr->end = true; - return; - } - - this->dataPtr->current = std::string(data.cFileName); - - // Skip "." and ".." - while (this->dataPtr->current == "." || - this->dataPtr->current == "..") - { - this->Next(); - } - } - - ////////////////////////////////////////////////// - void DirIter::Next() - { - WIN32_FIND_DATAA data; - data.cFileName[0] = 0; - while (data.cFileName[0] == 0 || - std::string(data.cFileName) == "." || - std::string(data.cFileName) == "..") - { - auto found = ::FindNextFileA(this->dataPtr->handle, &data); - // Signal EOF - if (!found) - { - this->dataPtr->end = true; - this->dataPtr->current = ""; - return; - } - } - - this->dataPtr->current = std::string(data.cFileName); - } - - ////////////////////////////////////////////////// - void DirIter::CloseHandle() - { - ::FindClose(this->dataPtr->handle); - } - -#endif // _WIN32 - - ////////////////////////////////////////////////// - const std::string separator(const std::string &_p) - { - return _p + preferred_separator; - } - - ////////////////////////////////////////////////// - void changeFromUnixPath(std::string &_path) - { - // cppcheck-suppress knownConditionTrueFalse - // cppcheck-suppress unmatchedSuppression - if ('/' == preferred_separator) - return; - - std::replace(_path.begin(), _path.end(), '/', preferred_separator); - } - - ////////////////////////////////////////////////// - std::string copyFromUnixPath(const std::string &_path) - { - std::string copy = _path; - changeFromUnixPath(copy); - return copy; - } - - ////////////////////////////////////////////////// - void changeToUnixPath(std::string &_path) - { - // cppcheck-suppress knownConditionTrueFalse - // cppcheck-suppress unmatchedSuppression - if ('/' == preferred_separator) - return; - - std::replace(_path.begin(), _path.end(), preferred_separator, '/'); - } - - ////////////////////////////////////////////////// - std::string copyToUnixPath(const std::string &_path) - { - std::string copy = _path; - changeToUnixPath(copy); - return copy; - } - - ////////////////////////////////////////////////// - std::string basename(const std::string &_path) - { - bool last_was_slash = false; - std::string basename; - - basename.reserve(_path.length()); - - for (size_t i = 0; i < _path.length(); ++i) - { - if (_path[i] == preferred_separator) - { - if (i == (_path.length() - 1)) - { - // if this is the last character, then according to basename we - // should return the portion of the path *before* the slash, i.e. - // the one we were just building. However, as a special case, if - // basename is empty, we return just a "/". - if (basename.size() == 0) - { - basename.push_back(preferred_separator); - } - break; - } - - last_was_slash = true; - } - else - { - if (last_was_slash) - { - last_was_slash = false; - basename.clear(); - } - - basename.push_back(_path[i]); - } - } - - return basename; - } - - ////////////////////////////////////////////////// - DirIter::DirIter() : dataPtr(ignition::utils::MakeImpl()) - { - this->dataPtr->current = ""; - - this->dataPtr->dirname = ""; - - this->dataPtr->handle = nullptr; - - this->dataPtr->end = true; - } - - ////////////////////////////////////////////////// - std::string DirIter::operator*() const - { - return this->dataPtr->dirname + preferred_separator + - this->dataPtr->current; - } - - ////////////////////////////////////////////////// - // prefix operator; note that we don't support the postfix operator - // because it is complicated to do so - const DirIter& DirIter::operator++() - { - Next(); - return *this; - } - - ////////////////////////////////////////////////// - bool DirIter::operator!=(const DirIter &_other) const - { - return this->dataPtr->end != _other.dataPtr->end; - } - - ////////////////////////////////////////////////// - DirIter::~DirIter() - { - if (this->dataPtr->handle != nullptr) - { - CloseHandle(); - this->dataPtr->handle = nullptr; - } - } - } -} diff --git a/src/SystemPaths.cc b/src/SystemPaths.cc index 20ed8db4c..a8393d3eb 100644 --- a/src/SystemPaths.cc +++ b/src/SystemPaths.cc @@ -25,12 +25,6 @@ #include #include -#ifndef _WIN32 -#include -#else -#include "win_dirent.h" -#endif - #include "ignition/common/Console.hh" #include "ignition/common/StringUtils.hh" #include "ignition/common/SystemPaths.hh" @@ -101,18 +95,11 @@ SystemPaths::SystemPaths() else fullPath = path; - DIR *dir = opendir(fullPath.c_str()); - if (!dir) + if (!exists(fullPath)) { -#ifdef _WIN32 - mkdir(fullPath.c_str()); -#else - // cppcheck-suppress ConfigurationNotChecked - mkdir(fullPath.c_str(), S_IRWXU | S_IRGRP | S_IROTH); -#endif + createDirectories(fullPath); } - else - closedir(dir); + this->dataPtr->logPath = fullPath; // Populate this->dataPtr->filePaths with values from the default diff --git a/src/TempDirectory.cc b/src/TempDirectory.cc index bdc8a3840..cf65b3a64 100644 --- a/src/TempDirectory.cc +++ b/src/TempDirectory.cc @@ -19,13 +19,20 @@ #include +#include + #ifdef _WIN32 #include #include #include #include +#else +#include +#include #endif +namespace fs = std::filesystem; + using namespace ignition; using namespace common; @@ -47,43 +54,18 @@ inline bool fs_warn(const std::string &_fcn, return true; } -///////////////////////////////////////////////// -// Helper implementation of std::filesystem::temp_directory_path -// https://en.cppreference.com/w/cpp/filesystem/temp_directory_path -// \TODO(anyone) remove when using `std::filesystem` in C++17 and greater. -std::string temp_directory_path(std::error_code& _err) -{ - _err = std::error_code(); -#ifdef _WIN32 - TCHAR temp_path[MAX_PATH]; - DWORD size = GetTempPathA(MAX_PATH, temp_path); - if (size > MAX_PATH || size == 0) { - _err = std::error_code( - static_cast(GetLastError()), std::system_category()); - } - temp_path[size] = '\0'; -#else - std::string temp_path; - if(!ignition::common::env("TMPDIR", temp_path)) - { - temp_path = "/tmp"; - } -#endif - return std::string(temp_path); -} - ///////////////////////////////////////////////// std::string ignition::common::tempDirectoryPath() { std::error_code ec; - auto ret = temp_directory_path(ec); + auto ret = fs::temp_directory_path(ec); if (!fs_warn("tempDirectoryPath", ec)) { ret = ""; } - return ret; + return ret.string(); } ///////////////////////////////////////////////// @@ -95,11 +77,11 @@ std::string createTempDirectory( const std::string &_baseName, const std::string &_parentPath) { - std::string parentPath(_parentPath); - std::string templatePath = _baseName + "XXXXXX"; + fs::path parentPath(_parentPath); + fs::path templatePath = _baseName + "XXXXXX"; - std::string fullTemplateStr = joinPaths(parentPath, templatePath); - if (!createDirectories(parentPath)) + std::string fullTemplateStr = (parentPath / templatePath).string(); + if (!createDirectories(parentPath.string())) { std::error_code ec{errno, std::system_category()}; errno = 0; @@ -108,29 +90,32 @@ std::string createTempDirectory( #ifdef _WIN32 errno_t errcode = _mktemp_s(&fullTemplateStr[0], fullTemplateStr.size() + 1); - if (errcode) { + if (errcode) + { std::error_code ec(static_cast(errcode), std::system_category()); throw std::system_error(ec, "could not format the temp directory name template"); } - const std::string finalPath{fullTemplateStr}; - if (!createDirectories(finalPath)) { + const fs::path finalPath{fullTemplateStr}; + if (!createDirectories(finalPath.string())) + { std::error_code ec(static_cast(GetLastError()), std::system_category()); throw std::system_error(ec, "could not create the temp directory"); } #else const char * dirName = mkdtemp(&fullTemplateStr[0]); - if (dirName == nullptr) { + if (dirName == nullptr) + { std::error_code ec{errno, std::system_category()}; errno = 0; throw std::system_error(ec, "could not format or create the temp directory"); } - const std::string finalPath{dirName}; + const fs::path finalPath{dirName}; #endif - return finalPath; + return finalPath.string(); } ///////////////////////////////////////////////// @@ -140,7 +125,8 @@ std::string ignition::common::createTempDirectory( const FilesystemWarningOp _warningOp) { std::string ret; - try { + try + { ret = ::createTempDirectory(_baseName, _parentPath); } catch (const std::system_error &ex) @@ -192,6 +178,7 @@ TempDirectory::TempDirectory(const std::string &_prefix, this->dataPtr->isValid = true; common::chdir(this->dataPtr->path); } + this->dataPtr->path = common::cwd(); } ///////////////////////////////////////////////// diff --git a/src/Util.cc b/src/Util.cc index de13ac071..d22ba30e1 100644 --- a/src/Util.cc +++ b/src/Util.cc @@ -36,6 +36,8 @@ #include #include +#include + #ifndef _WIN32 #include #include @@ -55,8 +57,17 @@ static const auto &ignstrtok = strtok_r; #endif -static std::unique_ptr gSystemPaths( - new ignition::common::SystemPaths); +///////////////////////////////////////////////// +/// \brief Global instance of system paths for helper functions below +/// +/// This uses the NeverDestroyed pattern to prevent static initialization and +/// destruction order fiasco issues. +ignition::common::SystemPaths& GetSystemPaths() +{ + static + ignition::utils::NeverDestroyed paths; + return paths.Access(); +} ///////////////////////////////////////////////// // Internal class for SHA1 computation @@ -286,26 +297,26 @@ std::string ignition::common::timeToIso( ///////////////////////////////////////////////// std::string ignition::common::logPath() { - return gSystemPaths->LogPath(); + return GetSystemPaths().LogPath(); } ///////////////////////////////////////////////// void ignition::common::addSearchPathSuffix(const std::string &_suffix) { - gSystemPaths->AddSearchPathSuffix(_suffix); + GetSystemPaths().AddSearchPathSuffix(_suffix); } ///////////////////////////////////////////////// std::string ignition::common::findFile(const std::string &_file) { - return gSystemPaths->FindFile(_file, true); + return GetSystemPaths().FindFile(_file, true); } ///////////////////////////////////////////////// std::string ignition::common::findFile(const std::string &_file, const bool _searchLocalPath) { - return gSystemPaths->FindFile(_file, _searchLocalPath); + return GetSystemPaths().FindFile(_file, _searchLocalPath); } ///////////////////////////////////////////////// @@ -328,13 +339,13 @@ std::string ignition::common::findFilePath(const std::string &_file) void ignition::common::addFindFileURICallback( std::function _cb) { - gSystemPaths->AddFindFileURICallback(_cb); + GetSystemPaths().AddFindFileURICallback(_cb); } ///////////////////////////////////////////////// ignition::common::SystemPaths *ignition::common::systemPaths() { - return gSystemPaths.get(); + return &GetSystemPaths(); } /////////////////////////////////////////////////