// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd. // // 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. #ifndef BOOTSTRAP_BUILD_TOOL #include "src/buildtool/main/archive.hpp" #ifdef __unix__ #include #else #error "Non-unix is not supported yet" #endif #include #include #include #include #include #include #include #include "src/buildtool/common/artifact_digest.hpp" #include "src/buildtool/common/artifact_digest_factory.hpp" #include "src/buildtool/file_system/git_repo.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/logging/log_level.hpp" #include "src/buildtool/logging/logger.hpp" #include "src/utils/cpp/expected.hpp" #include "src/utils/cpp/hex_string.hpp" extern "C" { #include #include } namespace { void archive_write_closer(archive* a) { if (a != nullptr) { archive_write_close(a); archive_write_free(a); } } void archive_entry_cleanup(archive_entry* entry) { if (entry != nullptr) { archive_entry_free(entry); } } auto add_to_archive(archive* archive, IExecutionApi const& api, const Artifact::ObjectInfo& artifact, const std::filesystem::path& location) -> bool { auto constexpr kExecutablePerm = 0555; auto constexpr kFilePerm = 0444; auto constexpr kDefaultPerm = 07777; auto payload = api.RetrieveToMemory(artifact); if (not payload) { Logger::Log(LogLevel::Error, "Failed to retrieve artifact {}", artifact.ToString()); return false; } switch (artifact.type) { case ObjectType::File: case ObjectType::Executable: { std::unique_ptr entry{archive_entry_new(), archive_entry_cleanup}; archive_entry_set_pathname(entry.get(), location.string().c_str()); archive_entry_set_size(entry.get(), static_cast(payload->size())); archive_entry_set_filetype(entry.get(), AE_IFREG); archive_entry_set_perm(entry.get(), artifact.type == ObjectType::Executable ? kExecutablePerm : kFilePerm); archive_write_header(archive, entry.get()); archive_write_data(archive, payload->c_str(), payload->size()); } break; case ObjectType::Symlink: { std::unique_ptr entry{archive_entry_new(), archive_entry_cleanup}; archive_entry_set_pathname(entry.get(), location.string().c_str()); archive_entry_set_size(entry.get(), static_cast(payload->size())); archive_entry_set_filetype(entry.get(), AE_IFLNK); archive_entry_set_symlink(entry.get(), payload->c_str()); archive_entry_set_perm(entry.get(), kDefaultPerm); archive_write_header(archive, entry.get()); archive_write_data(archive, payload->c_str(), payload->size()); } break; case ObjectType::Tree: { // avoid creating empty unnamed folder for the initial call if (not location.empty()) { std::unique_ptr entry{archive_entry_new(), archive_entry_cleanup}; archive_entry_set_pathname(entry.get(), location.string().c_str()); archive_entry_set_size(entry.get(), 0U); archive_entry_set_filetype(entry.get(), AE_IFDIR); archive_entry_set_perm(entry.get(), kDefaultPerm); archive_write_header(archive, entry.get()); } auto git_tree = GitRepo::ReadTreeData( *payload, artifact.digest.hash(), [](auto const& /*unused*/) { return true; }, /*is_hex_id=*/true); if (not git_tree) { Logger::Log(LogLevel::Error, "Failed to parse {} as git tree for path {}", artifact.ToString(), location.string()); return false; } // Reorder entries to be keyed and sorted by name std::map tree{}; for (auto const& [hash, entries] : *git_tree) { auto hex_hash = ToHexString(hash); for (auto const& entry : entries) { auto digest = ArtifactDigestFactory::Create(api.GetHashType(), hex_hash, 0, IsTreeObject(entry.type)); if (not digest) { return false; } tree[entry.name] = Artifact::ObjectInfo{.digest = *std::move(digest), .type = entry.type, .failed = false}; } } for (auto const& [name, obj] : tree) { if (not add_to_archive(archive, api, obj, location / name)) { return false; } } } break; } return true; } } // namespace [[nodiscard]] auto GenerateArchive( IExecutionApi const& api, const Artifact::ObjectInfo& artifact, const std::optional& output_path) -> bool { constexpr int kTarBlockSize = 512; std::unique_ptr archive{ archive_write_new(), archive_write_closer}; if (archive == nullptr) { Logger::Log(LogLevel::Error, "Internal error: Call to archive_write_new() failed"); return false; } if (archive_write_set_format_pax_restricted(archive.get()) != ARCHIVE_OK) { Logger::Log(LogLevel::Error, "Internal error: Call to " "archive_write_set_format_pax_restriced() failed"); return false; } if (archive_write_set_bytes_per_block(archive.get(), kTarBlockSize) != ARCHIVE_OK) { Logger::Log(LogLevel::Error, "Internal error: Call to " "archive_write_set_bytes_per_block() failed"); return false; } if (output_path) { if (archive_write_open_filename( archive.get(), output_path->string().c_str()) != ARCHIVE_OK) { Logger::Log(LogLevel::Error, "Failed to open archive for writing at {}", output_path->string()); return false; } } else { if (archive_write_open_fd(archive.get(), STDOUT_FILENO) != ARCHIVE_OK) { Logger::Log(LogLevel::Error, "Failed to open stdout for writing archive to"); return false; } } if (not add_to_archive( archive.get(), api, artifact, std::filesystem::path{""})) { return false; } if (output_path) { Logger::Log(LogLevel::Info, "Archive of {} was installed to {}", artifact.ToString(), output_path->string()); } return true; } #endif