diff options
-rw-r--r-- | src/buildtool/file_system/TARGETS | 1 | ||||
-rw-r--r-- | src/buildtool/file_system/file_system_manager.hpp | 72 | ||||
-rw-r--r-- | test/buildtool/file_system/file_system_manager.test.cpp | 102 |
3 files changed, 121 insertions, 54 deletions
diff --git a/src/buildtool/file_system/TARGETS b/src/buildtool/file_system/TARGETS index ef817912..d780c47a 100644 --- a/src/buildtool/file_system/TARGETS +++ b/src/buildtool/file_system/TARGETS @@ -11,6 +11,7 @@ , "deps": [ "object_type" , ["src/buildtool/logging", "logging"] + , ["src/buildtool/system", "system"] , ["@", "gsl-lite", "", "gsl-lite"] ] , "stage": ["src", "buildtool", "file_system"] diff --git a/src/buildtool/file_system/file_system_manager.hpp b/src/buildtool/file_system/file_system_manager.hpp index 75902890..df04bcec 100644 --- a/src/buildtool/file_system/file_system_manager.hpp +++ b/src/buildtool/file_system/file_system_manager.hpp @@ -8,12 +8,14 @@ #include <optional> #ifdef __unix__ +#include <sys/wait.h> #include <unistd.h> #endif #include "gsl-lite/gsl-lite.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/logging/logger.hpp" +#include "src/buildtool/system/system.hpp" /// \brief Implements primitive file system functionality. /// Catches all exceptions for use with exception-free callers. @@ -139,29 +141,52 @@ class FileSystemManager { [[nodiscard]] static auto CopyFile( std::filesystem::path const& src, std::filesystem::path const& dst, + bool fd_less = false, std::filesystem::copy_options opt = std::filesystem::copy_options::overwrite_existing) noexcept -> bool { - try { - return std::filesystem::copy_file(src, dst, opt); - } catch (std::exception const& e) { - Logger::Log(LogLevel::Error, - "copying file from {} to {}:\n{}", - src.string(), - dst.string(), - e.what()); - return false; + if (fd_less) { + pid_t pid = ::fork(); + if (pid == -1) { + Logger::Log( + LogLevel::Error, + "Failed to copy file: cannot fork a child process."); + return false; + } + + if (pid == 0) { + // Disable logging errors in child to avoid the use of mutexes. + System::ExitWithoutCleanup( + CopyFileImpl</*kLogError=*/false>(src, dst, opt) + ? EXIT_SUCCESS + : EXIT_FAILURE); + } + + int status{}; + ::waitpid(pid, &status, 0); + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + Logger::Log(LogLevel::Error, + "Failed copying file {} to {}", + src.string(), + dst.string()); + return false; + } + return true; } + return CopyFileImpl(src, dst, opt); } template <ObjectType kType> requires(IsFileObject(kType)) [[nodiscard]] static auto CopyFileAs( std::filesystem::path const& src, std::filesystem::path const& dst, + bool fd_less = false, std::filesystem::copy_options opt = std::filesystem::copy_options::overwrite_existing) noexcept -> bool { - return CopyFile(src, dst, opt) and + return CopyFile(src, dst, fd_less, opt) and SetFilePermissions(dst, IsExecutableObject(kType)); } @@ -169,14 +194,16 @@ class FileSystemManager { std::filesystem::path const& src, std::filesystem::path const& dst, ObjectType type, + bool fd_less = false, std::filesystem::copy_options opt = std::filesystem::copy_options::overwrite_existing) noexcept -> bool { switch (type) { case ObjectType::File: - return CopyFileAs<ObjectType::File>(src, dst, opt); + return CopyFileAs<ObjectType::File>(src, dst, fd_less, opt); case ObjectType::Executable: - return CopyFileAs<ObjectType::Executable>(src, dst, opt); + return CopyFileAs<ObjectType::Executable>( + src, dst, fd_less, opt); case ObjectType::Tree: break; } @@ -543,6 +570,27 @@ class FileSystemManager { } } + template <bool kLogError = true> + [[nodiscard]] static auto CopyFileImpl( + std::filesystem::path const& src, + std::filesystem::path const& dst, + std::filesystem::copy_options opt = + std::filesystem::copy_options::overwrite_existing) noexcept + -> bool { + try { + return std::filesystem::copy_file(src, dst, opt); + } catch (std::exception const& e) { + if constexpr (kLogError) { + Logger::Log(LogLevel::Error, + "copying file from {} to {}:\n{}", + src.string(), + dst.string(), + e.what()); + } + return false; + } + } + /// \brief Set special permissions for files. /// Set to 0444 for non-executables and set to 0555 for executables. static auto SetFilePermissions(std::filesystem::path const& path, diff --git a/test/buildtool/file_system/file_system_manager.test.cpp b/test/buildtool/file_system/file_system_manager.test.cpp index 84f9e8a3..2d0016cf 100644 --- a/test/buildtool/file_system/file_system_manager.test.cpp +++ b/test/buildtool/file_system/file_system_manager.test.cpp @@ -167,30 +167,13 @@ TEST_CASE("ReadFile", "[file_system]") { } TEST_CASE_METHOD(CopyFileFixture, "CopyFile", "[file_system]") { - // Copy file was successful - CHECK(FileSystemManager::CopyFile(from_, to_)); - - // file exists - CHECK(std::filesystem::exists(to_)); - CHECK(std::filesystem::is_regular_file(to_)); - - // Contents are equal - auto const content_from = FileSystemManager::ReadFile(from_); - CHECK(content_from.has_value()); - auto const content_to = FileSystemManager::ReadFile(to_); - CHECK(content_to.has_value()); - CHECK(content_from == content_to); -} - -TEST_CASE_METHOD(CopyFileFixture, "CopyFileAs", "[file_system]") { - SECTION("as file") { - // Copy as file was successful - CHECK(FileSystemManager::CopyFileAs(from_, to_, ObjectType::File)); + auto run_test = [&](bool fd_less) { + // Copy file was successful + CHECK(FileSystemManager::CopyFile(from_, to_, fd_less)); // file exists CHECK(std::filesystem::exists(to_)); CHECK(std::filesystem::is_regular_file(to_)); - CHECK(not FileSystemManager::IsExecutable(to_)); // Contents are equal auto const content_from = FileSystemManager::ReadFile(from_); @@ -198,29 +181,62 @@ TEST_CASE_METHOD(CopyFileFixture, "CopyFileAs", "[file_system]") { auto const content_to = FileSystemManager::ReadFile(to_); CHECK(content_to.has_value()); CHECK(content_from == content_to); + }; - // permissions should be 0444 - CHECK(HasFilePermissions(to_)); + SECTION("direct") { run_test(false); } + SECTION("fd_less") { run_test(true); } +} + +TEST_CASE_METHOD(CopyFileFixture, "CopyFileAs", "[file_system]") { + SECTION("as file") { + auto run_test = [&](bool fd_less) { + // Copy as file was successful + CHECK(FileSystemManager::CopyFileAs( + from_, to_, ObjectType::File, fd_less)); + + // file exists + CHECK(std::filesystem::exists(to_)); + CHECK(std::filesystem::is_regular_file(to_)); + CHECK(not FileSystemManager::IsExecutable(to_)); + + // Contents are equal + auto const content_from = FileSystemManager::ReadFile(from_); + CHECK(content_from.has_value()); + auto const content_to = FileSystemManager::ReadFile(to_); + CHECK(content_to.has_value()); + CHECK(content_from == content_to); + + // permissions should be 0444 + CHECK(HasFilePermissions(to_)); + }; + + SECTION("direct") { run_test(false); } + SECTION("fd_less") { run_test(true); } } SECTION("as executable") { - // Copy as file was successful - CHECK( - FileSystemManager::CopyFileAs(from_, to_, ObjectType::Executable)); - - // file exists - CHECK(std::filesystem::exists(to_)); - CHECK(std::filesystem::is_regular_file(to_)); - CHECK(FileSystemManager::IsExecutable(to_)); - - // Contents are equal - auto const content_from = FileSystemManager::ReadFile(from_); - CHECK(content_from.has_value()); - auto const content_to = FileSystemManager::ReadFile(to_); - CHECK(content_to.has_value()); - CHECK(content_from == content_to); - - // permissions should be 0555 - CHECK(HasExecutablePermissions(to_)); + auto run_test = [&](bool fd_less) { + // Copy as file was successful + CHECK(FileSystemManager::CopyFileAs( + from_, to_, ObjectType::Executable, fd_less)); + + // file exists + CHECK(std::filesystem::exists(to_)); + CHECK(std::filesystem::is_regular_file(to_)); + CHECK(FileSystemManager::IsExecutable(to_)); + + // Contents are equal + auto const content_from = FileSystemManager::ReadFile(from_); + CHECK(content_from.has_value()); + auto const content_to = FileSystemManager::ReadFile(to_); + CHECK(content_to.has_value()); + CHECK(content_from == content_to); + + // permissions should be 0555 + CHECK(HasExecutablePermissions(to_)); + }; + + SECTION("direct") { run_test(false); } + SECTION("fd_less") { run_test(true); } } } @@ -357,8 +373,10 @@ TEST_CASE("FileSystemManager", "[file_system]") { CHECK(file_content == test_content); // copy file without 'overwrite' - CHECK(FileSystemManager::CopyFile( - test_file, copy_file, std::filesystem::copy_options::none)); + CHECK(FileSystemManager::CopyFile(test_file, + copy_file, + /*fd_less=*/false, + std::filesystem::copy_options::none)); // copy file with 'overwrite' CHECK(FileSystemManager::CopyFile(copy_file, test_file)); |