diff options
-rw-r--r-- | src/buildtool/file_system/file_system_manager.hpp | 85 | ||||
-rw-r--r-- | test/buildtool/file_system/file_system_manager.test.cpp | 77 |
2 files changed, 112 insertions, 50 deletions
diff --git a/src/buildtool/file_system/file_system_manager.hpp b/src/buildtool/file_system/file_system_manager.hpp index df04bcec..1be50ac0 100644 --- a/src/buildtool/file_system/file_system_manager.hpp +++ b/src/buildtool/file_system/file_system_manager.hpp @@ -445,49 +445,67 @@ class FileSystemManager { return true; } - [[nodiscard]] static auto WriteFile( - std::string const& content, - std::filesystem::path const& file) noexcept -> auto { + [[nodiscard]] static auto WriteFile(std::string const& content, + std::filesystem::path const& file, + bool fd_less = false) noexcept -> bool { if (not CreateDirectory(file.parent_path())) { Logger::Log(LogLevel::Error, "can not create directory {}", file.parent_path().string()); return false; } - try { - std::ofstream writer{file}; - if (!writer.is_open()) { + if (fd_less) { + pid_t pid = ::fork(); + if (pid == -1) { Logger::Log( - LogLevel::Error, "can not open file {}", file.string()); + LogLevel::Error, + "Failed to write file: cannot fork a child process."); + return false; + } + + if (pid == 0) { + // Disable logging errors in child to avoid the use of mutexes. + System::ExitWithoutCleanup( + WriteFileImpl</*kLogError=*/false>(content, file) + ? EXIT_SUCCESS + : EXIT_FAILURE); + } + + int status{}; + ::waitpid(pid, &status, 0); + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + Logger::Log( + LogLevel::Error, "Failed writing file {}", file.string()); return false; } - writer << content; - writer.close(); return true; - } catch (std::exception const& e) { - Logger::Log( - LogLevel::Error, "writing to {}:\n{}", file.string(), e.what()); - return false; } + return WriteFileImpl(content, file); } template <ObjectType kType> - requires(IsFileObject(kType)) [[nodiscard]] static auto WriteFileAs( - std::string const& content, - std::filesystem::path const& file) noexcept -> bool { - return WriteFile(content, file) and + requires(IsFileObject(kType)) + [[nodiscard]] static auto WriteFileAs(std::string const& content, + std::filesystem::path const& file, + bool fd_less = false) noexcept + -> bool { + return WriteFile(content, file, fd_less) and SetFilePermissions(file, IsExecutableObject(kType)); } [[nodiscard]] static auto WriteFileAs(std::string const& content, std::filesystem::path const& file, - ObjectType output_type) noexcept + ObjectType output_type, + bool fd_less = false) noexcept -> bool { switch (output_type) { case ObjectType::File: - return WriteFileAs<ObjectType::File>(content, file); + return WriteFileAs<ObjectType::File>(content, file, fd_less); case ObjectType::Executable: - return WriteFileAs<ObjectType::Executable>(content, file); + return WriteFileAs<ObjectType::Executable>( + content, file, fd_less); case ObjectType::Tree: return false; } @@ -591,6 +609,33 @@ class FileSystemManager { } } + template <bool kLogError = true> + [[nodiscard]] static auto WriteFileImpl( + std::string const& content, + std::filesystem::path const& file) noexcept -> bool { + try { + std::ofstream writer{file}; + if (!writer.is_open()) { + if constexpr (kLogError) { + Logger::Log( + LogLevel::Error, "can not open file {}", file.string()); + } + return false; + } + writer << content; + writer.close(); + return true; + } catch (std::exception const& e) { + if constexpr (kLogError) { + Logger::Log(LogLevel::Error, + "writing to {}:\n{}", + file.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 2d0016cf..fee5dca1 100644 --- a/test/buildtool/file_system/file_system_manager.test.cpp +++ b/test/buildtool/file_system/file_system_manager.test.cpp @@ -274,50 +274,67 @@ TEST_CASE("RemoveFile", "[file_system]") { TEST_CASE_METHOD(WriteFileFixture, "WriteFile", "[file_system]") { std::string const content{"This are the contents\nof the file.\n"}; - CHECK(FileSystemManager::WriteFile(content, file_path_)); - CHECK(std::filesystem::exists(file_path_)); - CHECK(std::filesystem::is_directory(file_path_.parent_path())); - CHECK(std::filesystem::is_regular_file(file_path_)); - - auto const written_content = FileSystemManager::ReadFile(file_path_); - CHECK(written_content.has_value()); - CHECK(written_content == content); -} - -TEST_CASE_METHOD(WriteFileFixture, "WriteFileAs", "[file_system]") { - SECTION("as a file") { - std::string const content{"This are the contents\nof the file.\n"}; - - CHECK(FileSystemManager::WriteFileAs( - content, file_path_, ObjectType::File)); + auto run_test = [&](bool fd_less) { + CHECK(FileSystemManager::WriteFile(content, file_path_, fd_less)); CHECK(std::filesystem::exists(file_path_)); CHECK(std::filesystem::is_directory(file_path_.parent_path())); CHECK(std::filesystem::is_regular_file(file_path_)); - CHECK(not FileSystemManager::IsExecutable(file_path_)); auto const written_content = FileSystemManager::ReadFile(file_path_); CHECK(written_content.has_value()); CHECK(written_content == content); + }; - // permissions should be 0444 - CHECK(HasFilePermissions(file_path_)); + SECTION("direct") { run_test(false); } + SECTION("fd-less") { run_test(true); } +} + +TEST_CASE_METHOD(WriteFileFixture, "WriteFileAs", "[file_system]") { + SECTION("as a file") { + std::string const content{"This are the contents\nof the file.\n"}; + + auto run_test = [&](bool fd_less) { + CHECK(FileSystemManager::WriteFileAs( + content, file_path_, ObjectType::File, fd_less)); + CHECK(std::filesystem::exists(file_path_)); + CHECK(std::filesystem::is_directory(file_path_.parent_path())); + CHECK(std::filesystem::is_regular_file(file_path_)); + CHECK(not FileSystemManager::IsExecutable(file_path_)); + + auto const written_content = + FileSystemManager::ReadFile(file_path_); + CHECK(written_content.has_value()); + CHECK(written_content == content); + + // permissions should be 0444 + CHECK(HasFilePermissions(file_path_)); + }; + + SECTION("direct") { run_test(false); } + SECTION("fd-less") { run_test(true); } } SECTION("as an executable") { std::string const content{"\n"}; - CHECK(FileSystemManager::WriteFileAs( - content, file_path_, ObjectType::Executable)); - CHECK(std::filesystem::exists(file_path_)); - CHECK(std::filesystem::is_directory(file_path_.parent_path())); - CHECK(std::filesystem::is_regular_file(file_path_)); - CHECK(FileSystemManager::IsExecutable(file_path_)); + auto run_test = [&](bool fd_less) { + CHECK(FileSystemManager::WriteFileAs( + content, file_path_, ObjectType::Executable, fd_less)); + CHECK(std::filesystem::exists(file_path_)); + CHECK(std::filesystem::is_directory(file_path_.parent_path())); + CHECK(std::filesystem::is_regular_file(file_path_)); + CHECK(FileSystemManager::IsExecutable(file_path_)); + + auto const written_content = + FileSystemManager::ReadFile(file_path_); + CHECK(written_content.has_value()); + CHECK(written_content == content); - auto const written_content = FileSystemManager::ReadFile(file_path_); - CHECK(written_content.has_value()); - CHECK(written_content == content); + // permissions should be 0555 + CHECK(HasExecutablePermissions(file_path_)); + }; - // permissions should be 0555 - CHECK(HasExecutablePermissions(file_path_)); + SECTION("direct") { run_test(false); } + SECTION("fd-less") { run_test(true); } } } |