diff options
Diffstat (limited to 'src/buildtool/main/archive.cpp')
-rw-r--r-- | src/buildtool/main/archive.cpp | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/buildtool/main/archive.cpp b/src/buildtool/main/archive.cpp new file mode 100644 index 00000000..2f6fc099 --- /dev/null +++ b/src/buildtool/main/archive.cpp @@ -0,0 +1,180 @@ +// 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" + +#include <unistd.h> + +#include "src/buildtool/file_system/git_repo.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/utils/cpp/hex_string.hpp" + +extern "C" { +#include <archive.h> +#include <archive_entry.h> +} + +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); + } +} + +// NOLINTNEXTLINE(misc-no-recursion) +auto add_to_archive(archive* archive, + gsl::not_null<IExecutionApi*> const& api, + const Artifact::ObjectInfo& artifact, + const std::filesystem::path& location) -> bool { + auto constexpr kExecutable = 0555; + auto constexpr kFile = 0444; + + 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<archive_entry, decltype(&archive_entry_cleanup)> + entry{archive_entry_new(), archive_entry_cleanup}; + archive_entry_set_pathname(entry.get(), location.string().c_str()); + archive_entry_set_size(entry.get(), payload->size()); + archive_entry_set_filetype(entry.get(), AE_IFREG); + archive_entry_set_perm( + entry.get(), + artifact.type == ObjectType::Executable ? kExecutable : kFile); + archive_write_header(archive, entry.get()); + auto data = *payload; + archive_write_data(archive, data.c_str(), data.size()); + } break; + case ObjectType::Symlink: { + std::unique_ptr<archive_entry, decltype(&archive_entry_cleanup)> + entry{archive_entry_new(), archive_entry_cleanup}; + archive_entry_set_pathname(entry.get(), location.string().c_str()); + archive_entry_set_filetype(entry.get(), AE_IFLNK); + archive_entry_set_symlink(entry.get(), payload->c_str()); + archive_write_header(archive, entry.get()); + } break; + case ObjectType::Tree: { + 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<std::string, Artifact::ObjectInfo> tree{}; + for (auto const& [hash, entries] : *git_tree) { + auto hex_hash = ToHexString(hash); + for (auto const& entry : entries) { + tree[entry.name] = Artifact::ObjectInfo{ + .digest = ArtifactDigest( + hex_hash, 0, entry.type == ObjectType::Tree), + .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( + gsl::not_null<IExecutionApi*> const& api, + const Artifact::ObjectInfo& artifact, + const std::optional<std::filesystem::path>& output_path) -> bool { + + constexpr int kTarBlockSize = 512; + + std::unique_ptr<archive, decltype(&archive_write_closer)> 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 |