From ff6c747e10848ce5c5846f880dfd649896a5268b Mon Sep 17 00:00:00 2001 From: Paul Cristian Sarbu Date: Tue, 26 Sep 2023 15:29:11 +0200 Subject: ArchiveOps: Move libarchive utilities outside other_tools This way they can be used by 'just serve'. --- src/other_tools/root_maps/TARGETS | 2 +- src/other_tools/root_maps/content_git_map.cpp | 2 +- src/other_tools/utils/TARGETS | 12 +- src/other_tools/utils/archive_ops.cpp | 384 -------------------- src/other_tools/utils/archive_ops.hpp | 106 ------ src/utils/archive/TARGETS | 11 + src/utils/archive/archive_ops.cpp | 408 ++++++++++++++++++++++ src/utils/archive/archive_ops.hpp | 106 ++++++ test/end-to-end/just-mr/TARGETS | 2 +- test/end-to-end/just-mr/create_test_archives.cpp | 2 +- test/other_tools/utils/TARGETS | 20 +- test/other_tools/utils/archive_usage.test.cpp | 424 ----------------------- test/utils/TARGETS | 3 +- test/utils/archive/TARGETS | 15 + test/utils/archive/archive_usage.test.cpp | 424 +++++++++++++++++++++++ 15 files changed, 973 insertions(+), 948 deletions(-) delete mode 100644 src/other_tools/utils/archive_ops.cpp delete mode 100644 src/other_tools/utils/archive_ops.hpp create mode 100644 src/utils/archive/TARGETS create mode 100644 src/utils/archive/archive_ops.cpp create mode 100644 src/utils/archive/archive_ops.hpp delete mode 100644 test/other_tools/utils/archive_usage.test.cpp create mode 100644 test/utils/archive/TARGETS create mode 100644 test/utils/archive/archive_usage.test.cpp diff --git a/src/other_tools/root_maps/TARGETS b/src/other_tools/root_maps/TARGETS index b2cd1dfb..2b5fbd1a 100644 --- a/src/other_tools/root_maps/TARGETS +++ b/src/other_tools/root_maps/TARGETS @@ -81,7 +81,7 @@ ] , "stage": ["src", "other_tools", "root_maps"] , "private-deps": - [ ["src/other_tools/utils", "archive_ops"] + [ ["src/utils/archive", "archive_ops"] , ["src/buildtool/execution_api/local", "local"] , ["src/buildtool/file_system", "file_storage"] , ["src/buildtool/storage", "storage"] diff --git a/src/other_tools/root_maps/content_git_map.cpp b/src/other_tools/root_maps/content_git_map.cpp index 9930984e..c6e0809b 100644 --- a/src/other_tools/root_maps/content_git_map.cpp +++ b/src/other_tools/root_maps/content_git_map.cpp @@ -20,7 +20,7 @@ #include "src/buildtool/storage/storage.hpp" #include "src/other_tools/just_mr/progress_reporting/progress.hpp" #include "src/other_tools/just_mr/progress_reporting/statistics.hpp" -#include "src/other_tools/utils/archive_ops.hpp" +#include "src/utils/archive/archive_ops.hpp" namespace { diff --git a/src/other_tools/utils/TARGETS b/src/other_tools/utils/TARGETS index 9e3fa89d..df08d551 100644 --- a/src/other_tools/utils/TARGETS +++ b/src/other_tools/utils/TARGETS @@ -1,14 +1,4 @@ -{ "archive_ops": - { "type": ["@", "rules", "CC", "library"] - , "name": ["archive_ops"] - , "hdrs": ["archive_ops.hpp"] - , "srcs": ["archive_ops.cpp"] - , "deps": [["@", "gsl", "", "gsl"]] - , "stage": ["src", "other_tools", "utils"] - , "private-deps": - [["src/buildtool/file_system", "file_system_manager"], ["", "libarchive"]] - } -, "curl_context": +{ "curl_context": { "type": ["@", "rules", "CC", "library"] , "name": ["curl_context"] , "hdrs": ["curl_context.hpp"] diff --git a/src/other_tools/utils/archive_ops.cpp b/src/other_tools/utils/archive_ops.cpp deleted file mode 100644 index f0d02531..00000000 --- a/src/other_tools/utils/archive_ops.cpp +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright 2022 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. - -#include "src/other_tools/utils/archive_ops.hpp" - -#include "src/buildtool/file_system/file_system_manager.hpp" - -extern "C" { -#include -#include -} - -namespace { - -/// \brief Default block size for archive extraction. -constexpr size_t kArchiveBlockSize = 10240; - -/// \brief Clean-up function for archive entry objects. -void archive_entry_cleanup(archive_entry* entry) { - if (entry != nullptr) { - archive_entry_free(entry); - } -} - -/// \brief Clean-up function for archive objects open for writing. -void archive_write_closer(archive* a_out) { - if (a_out != nullptr) { - archive_write_close(a_out); // libarchive handles non-openness - archive_write_free(a_out); // also do cleanup! - } -} - -/// \brief Clean-up function for archive objects open for reading. -void archive_read_closer(archive* a_in) { - if (a_in != nullptr) { - archive_read_close(a_in); // libarchive handles non-openness - archive_read_free(a_in); // also do cleanup! - } -} - -auto enable_write_filter(archive* aw, ArchiveType type) -> bool { - switch (type) { - case ArchiveType::Tar: - return true; // no compression filter - case ArchiveType::TarGz: - return (archive_write_add_filter_gzip(aw) == ARCHIVE_OK); - case ArchiveType::TarBz2: - return (archive_write_add_filter_bzip2(aw) == ARCHIVE_OK); - case ArchiveType::TarXz: - return (archive_write_add_filter_xz(aw) == ARCHIVE_OK); - case ArchiveType::TarLz: - return (archive_write_add_filter_lzip(aw) == ARCHIVE_OK); - case ArchiveType::TarLzma: - return (archive_write_add_filter_lzma(aw) == ARCHIVE_OK); - default: - return false; - } -} - -auto enable_read_filter(archive* ar, ArchiveType type) -> bool { - switch (type) { - case ArchiveType::Tar: - return true; // no outside compression filter - case ArchiveType::TarGz: - return (archive_read_support_filter_gzip(ar) == ARCHIVE_OK); - case ArchiveType::TarBz2: - return (archive_read_support_filter_bzip2(ar) == ARCHIVE_OK); - case ArchiveType::TarXz: - return (archive_read_support_filter_xz(ar) == ARCHIVE_OK); - case ArchiveType::TarLz: - return (archive_read_support_filter_lzip(ar) == ARCHIVE_OK); - case ArchiveType::TarLzma: - return (archive_read_support_filter_lzma(ar) == ARCHIVE_OK); - case ArchiveType::TarAuto: - return (archive_read_support_filter_all(ar) == ARCHIVE_OK); - default: - return false; - } -} - -} // namespace - -auto ArchiveOps::WriteEntry(archive_entry* entry, archive* aw) - -> std::optional { - std::filesystem::path entry_path{archive_entry_sourcepath(entry)}; - // only write to archive if entry is file - if (FileSystemManager::IsFile(entry_path)) { - auto content = FileSystemManager::ReadFile(entry_path); - if (not content) { - return "ArchiveOps: failed to open file entry while creating " - "archive"; - } - if (not content->empty()) { - auto content_size = content->size(); - archive_write_data(aw, content->c_str(), content_size); - } - } - return std::nullopt; -} - -auto ArchiveOps::CopyData(archive* ar, archive* aw) - -> std::optional { - int r{}; - const void* buff{nullptr}; - size_t size{}; - la_int64_t offset{}; - - while (true) { - r = archive_read_data_block(ar, &buff, &size, &offset); - if (r == ARCHIVE_EOF) { - return std::nullopt; // success! - } - if (r != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - if (archive_write_data_block(aw, buff, size, offset) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(aw)); - } - } - return std::nullopt; // success! -} - -auto ArchiveOps::EnableWriteFormats(archive* aw, ArchiveType type) - -> std::optional { - switch (type) { - case ArchiveType::Zip: { - if (archive_write_set_format_zip(aw) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(aw)); - } - } break; - case ArchiveType::_7Zip: { - if (archive_write_set_format_7zip(aw) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(aw)); - } - } break; - case ArchiveType::ZipAuto: { - return std::string( - "ArchiveOps: Writing a zip-like archive must be explicit"); - } - case ArchiveType::Tar: - case ArchiveType::TarGz: - case ArchiveType::TarBz2: - case ArchiveType::TarXz: - case ArchiveType::TarLz: - case ArchiveType::TarLzma: { - if ((archive_write_set_format_pax_restricted(aw) != ARCHIVE_OK) or - not enable_write_filter(aw, type)) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(aw)); - } - } break; - case ArchiveType::TarAuto: - return std::string( - "ArchiveOps: Writing a tarball-type archive must be explicit!"); - } - return std::nullopt; // success! -} - -auto ArchiveOps::EnableReadFormats(archive* ar, ArchiveType type) - -> std::optional { - switch (type) { - case ArchiveType::Zip: { - if (archive_read_support_format_zip(ar) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - } break; - case ArchiveType::_7Zip: { - if (archive_read_support_format_7zip(ar) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - } break; - case ArchiveType::ZipAuto: { - if (archive_read_support_format_7zip(ar) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - if (archive_read_support_format_zip(ar) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - } break; - case ArchiveType::TarAuto: - case ArchiveType::Tar: - case ArchiveType::TarGz: - case ArchiveType::TarBz2: - case ArchiveType::TarXz: - case ArchiveType::TarLz: - case ArchiveType::TarLzma: { - if ((archive_read_support_format_tar(ar) != ARCHIVE_OK) or - not enable_read_filter(ar, type)) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(ar)); - } - } break; - } - return std::nullopt; // success! -} - -auto ArchiveOps::CreateArchive(ArchiveType type, - std::string const& name, - std::filesystem::path const& source) noexcept - -> std::optional { - return CreateArchive(type, name, source, std::filesystem::path(".")); -} - -auto ArchiveOps::CreateArchive(ArchiveType type, - std::string const& name, - std::filesystem::path const& source, - std::filesystem::path const& destDir) noexcept - -> std::optional { - try { - // make sure paths will be relative wrt current dir - auto rel_source = std::filesystem::relative(source); - - std::unique_ptr a_out{ - archive_write_new(), archive_write_closer}; - if (a_out == nullptr) { - return std::string("ArchiveOps: archive_write_new failed"); - } - // enable the correct format for archive type - auto res = EnableWriteFormats(a_out.get(), type); - if (res != std::nullopt) { - return *res; - } - // open archive to write - if (not FileSystemManager::CreateDirectory(destDir)) { - return std::string( - "ArchiveOps: could not create destination directory ") + - destDir.string(); - } - if (archive_write_open_filename( - a_out.get(), (destDir / std::filesystem::path(name)).c_str()) != - ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(a_out.get())); - } - // open source - std::unique_ptr disk{ - archive_read_disk_new(), archive_read_closer}; - if (disk == nullptr) { - return std::string("ArchiveOps: archive_read_disk_new failed"); - } - archive_read_disk_set_standard_lookup(disk.get()); - if (archive_read_disk_open(disk.get(), rel_source.c_str()) != - ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(disk.get())); - } - // create archive - while (true) { - std::unique_ptr - entry{archive_entry_new(), archive_entry_cleanup}; - if (entry == nullptr) { - return std::string("ArchiveOps: archive_entry_new failed"); - } - int r = archive_read_next_header2(disk.get(), entry.get()); - if (r == ARCHIVE_EOF) { - return std::nullopt; // nothing left to archive; success! - } - if (r != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(disk.get())); - } - // if entry is a directory, make sure we descend into all its - // children - archive_read_disk_descend(disk.get()); - // get info on current entry - if (archive_write_header(a_out.get(), entry.get()) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(a_out.get())); - } - // write entry into archive - auto res = WriteEntry(entry.get(), a_out.get()); - if (res != std::nullopt) { - return *res; - } - } - } catch (std::exception const& ex) { - Logger::Log( - LogLevel::Error, "archive create failed with:\n{}", ex.what()); - return std::nullopt; - } -} - -auto ArchiveOps::ExtractArchive(ArchiveType type, - std::filesystem::path const& source) noexcept - -> std::optional { - return ExtractArchive(type, source, std::filesystem::path(".")); -} - -auto ArchiveOps::ExtractArchive(ArchiveType type, - std::filesystem::path const& source, - std::filesystem::path const& destDir) noexcept - -> std::optional { - try { - std::unique_ptr a_in{ - archive_read_new(), archive_read_closer}; - if (a_in == nullptr) { - return std::string("ArchiveOps: archive_read_new failed"); - } - // enable support for known formats - auto res = EnableReadFormats(a_in.get(), type); - if (res != std::nullopt) { - return *res; - } - // open archive for reading - if (archive_read_open_filename( - a_in.get(), source.c_str(), kArchiveBlockSize) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(a_in.get())); - } - // set up writer to disk - std::unique_ptr disk{ - archive_write_disk_new(), archive_write_closer}; - if (disk == nullptr) { - return std::string("ArchiveOps: archive_write_disk_new failed"); - } - // Select which attributes we want to restore. - uint flags = ARCHIVE_EXTRACT_TIME; - flags |= static_cast(ARCHIVE_EXTRACT_PERM); - flags |= static_cast(ARCHIVE_EXTRACT_FFLAGS); - archive_write_disk_set_options(disk.get(), static_cast(flags)); - archive_write_disk_set_standard_lookup(disk.get()); - // make sure destination directory exists - if (not FileSystemManager::CreateDirectory(destDir)) { - return std::string( - "ArchiveOps: could not create destination directory ") + - destDir.string(); - } - // extract the archive - archive_entry* entry{nullptr}; - while (true) { - int r = archive_read_next_header(a_in.get(), &entry); - if (r == ARCHIVE_EOF) { - return std::nullopt; // nothing left to extract; success! - } - if (r != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(a_in.get())); - } - // set correct destination path - auto new_entry_path = - destDir / std::filesystem::path(archive_entry_pathname(entry)); - archive_entry_set_pathname(entry, new_entry_path.c_str()); - if (archive_write_header(disk.get(), entry) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(disk.get())); - } - // write to disk if file - if (archive_entry_size(entry) > 0) { - auto res = CopyData(a_in.get(), disk.get()); - if (res != std::nullopt) { - return *res; - } - } - // finish entry writing - if (archive_write_finish_entry(disk.get()) != ARCHIVE_OK) { - return std::string("ArchiveOps: ") + - std::string(archive_error_string(disk.get())); - } - } - } catch (std::exception const& ex) { - Logger::Log( - LogLevel::Error, "archive extract failed with:\n{}", ex.what()); - return std::nullopt; - } -} diff --git a/src/other_tools/utils/archive_ops.hpp b/src/other_tools/utils/archive_ops.hpp deleted file mode 100644 index 773115bc..00000000 --- a/src/other_tools/utils/archive_ops.hpp +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022 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 INCLUDED_SRC_OTHER_TOOLS_UTILS_ARCHIVE_OPS_HPP -#define INCLUDED_SRC_OTHER_TOOLS_UTILS_ARCHIVE_OPS_HPP - -#include -#include - -#include "gsl/gsl" - -extern "C" { -using archive = struct archive; -using archive_entry = struct archive_entry; -} - -enum class ArchiveType : size_t { - Zip, - _7Zip, - ZipAuto, // autodetect zip-like archives - Tar, // uncompressed - TarGz, - TarBz2, - TarXz, - TarLz, - TarLzma, - TarAuto // autodetect tarball-type archives -}; - -/// \brief Class handling archiving and unarchiving operations via libarchive -class ArchiveOps { - public: - /// \brief Create archive of given type from file or directory at - /// source. All paths will be takes relative to current directory. - /// Destination folder is the current directory. Archive is stored under - /// given name. Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static CreateArchive( - ArchiveType type, - std::string const& name, - std::filesystem::path const& source) noexcept - -> std::optional; - - /// \brief Create archive of given type from file or directory at source and - /// store it in destDir folder under given name. All paths will be taken as - /// relative to the current directory. Destination directory is created if - /// not present. Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static CreateArchive( - ArchiveType type, - std::string const& name, - std::filesystem::path const& source, - std::filesystem::path const& destDir) noexcept - -> std::optional; - - /// \brief Extract archive pointed to by source into destDir folder. The - /// destination folder is the current directory and the type of archive is - /// specified from currently supported formats: tar, zip, tar.gz, tar.bz2. - /// Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static ExtractArchive( - ArchiveType type, - std::filesystem::path const& source) noexcept - -> std::optional; - - /// \brief Extract archive pointed to by source into destDir folder. The - /// type of archive is specified from currently supported formats: tar, zip, - /// tar.gz, tar.bz2. Returns nullopt on success, or an error string if - /// failure. - [[nodiscard]] auto static ExtractArchive( - ArchiveType type, - std::filesystem::path const& source, - std::filesystem::path const& destDir) noexcept - -> std::optional; - - private: - /// \brief Copy entry into archive object. - /// Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static WriteEntry(archive_entry* entry, archive* aw) - -> std::optional; - - /// \brief Copy data blocks from one archive object to another. - /// Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static CopyData(archive* ar, archive* aw) - -> std::optional; - - /// \brief Set up the appropriate supported format for writing an archive. - /// Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static EnableWriteFormats(archive* aw, ArchiveType type) - -> std::optional; - - /// \brief Set up the supported formats for reading in an archive. - /// Returns nullopt on success, or an error string if failure. - [[nodiscard]] auto static EnableReadFormats(archive* ar, ArchiveType type) - -> std::optional; -}; - -#endif // INCLUDED_SRC_OTHER_TOOLS_UTILS_ARCHIVE_OPS_HPP diff --git a/src/utils/archive/TARGETS b/src/utils/archive/TARGETS new file mode 100644 index 00000000..df3e6009 --- /dev/null +++ b/src/utils/archive/TARGETS @@ -0,0 +1,11 @@ +{ "archive_ops": + { "type": ["@", "rules", "CC", "library"] + , "name": ["archive_ops"] + , "hdrs": ["archive_ops.hpp"] + , "srcs": ["archive_ops.cpp"] + , "deps": [["@", "gsl", "", "gsl"]] + , "stage": ["src", "utils", "archive"] + , "private-deps": + [["src/buildtool/file_system", "file_system_manager"], ["", "libarchive"]] + } +} diff --git a/src/utils/archive/archive_ops.cpp b/src/utils/archive/archive_ops.cpp new file mode 100644 index 00000000..c92358c1 --- /dev/null +++ b/src/utils/archive/archive_ops.cpp @@ -0,0 +1,408 @@ +// Copyright 2022 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. + +#include "src/utils/archive/archive_ops.hpp" + +#include "src/buildtool/file_system/file_system_manager.hpp" + +extern "C" { +#include +#include +} + +#ifndef BOOTSTRAP_BUILD_TOOL +namespace { + +/// \brief Default block size for archive extraction. +constexpr size_t kArchiveBlockSize = 10240; + +/// \brief Clean-up function for archive entry objects. +void archive_entry_cleanup(archive_entry* entry) { + if (entry != nullptr) { + archive_entry_free(entry); + } +} + +/// \brief Clean-up function for archive objects open for writing. +void archive_write_closer(archive* a_out) { + if (a_out != nullptr) { + archive_write_close(a_out); // libarchive handles non-openness + archive_write_free(a_out); // also do cleanup! + } +} + +/// \brief Clean-up function for archive objects open for reading. +void archive_read_closer(archive* a_in) { + if (a_in != nullptr) { + archive_read_close(a_in); // libarchive handles non-openness + archive_read_free(a_in); // also do cleanup! + } +} + +auto enable_write_filter(archive* aw, ArchiveType type) -> bool { + switch (type) { + case ArchiveType::Tar: + return true; // no compression filter + case ArchiveType::TarGz: + return (archive_write_add_filter_gzip(aw) == ARCHIVE_OK); + case ArchiveType::TarBz2: + return (archive_write_add_filter_bzip2(aw) == ARCHIVE_OK); + case ArchiveType::TarXz: + return (archive_write_add_filter_xz(aw) == ARCHIVE_OK); + case ArchiveType::TarLz: + return (archive_write_add_filter_lzip(aw) == ARCHIVE_OK); + case ArchiveType::TarLzma: + return (archive_write_add_filter_lzma(aw) == ARCHIVE_OK); + default: + return false; + } +} + +auto enable_read_filter(archive* ar, ArchiveType type) -> bool { + switch (type) { + case ArchiveType::Tar: + return true; // no outside compression filter + case ArchiveType::TarGz: + return (archive_read_support_filter_gzip(ar) == ARCHIVE_OK); + case ArchiveType::TarBz2: + return (archive_read_support_filter_bzip2(ar) == ARCHIVE_OK); + case ArchiveType::TarXz: + return (archive_read_support_filter_xz(ar) == ARCHIVE_OK); + case ArchiveType::TarLz: + return (archive_read_support_filter_lzip(ar) == ARCHIVE_OK); + case ArchiveType::TarLzma: + return (archive_read_support_filter_lzma(ar) == ARCHIVE_OK); + case ArchiveType::TarAuto: + return (archive_read_support_filter_all(ar) == ARCHIVE_OK); + default: + return false; + } +} + +} // namespace +#endif // BOOTSTRAP_BUILD_TOOL + +auto ArchiveOps::WriteEntry(archive_entry* entry, archive* aw) + -> std::optional { +#ifndef BOOTSTRAP_BUILD_TOOL + std::filesystem::path entry_path{archive_entry_sourcepath(entry)}; + // only write to archive if entry is file + if (FileSystemManager::IsFile(entry_path)) { + auto content = FileSystemManager::ReadFile(entry_path); + if (not content) { + return "ArchiveOps: failed to open file entry while creating " + "archive"; + } + if (not content->empty()) { + auto content_size = content->size(); + archive_write_data(aw, content->c_str(), content_size); + } + } +#endif // BOOTSTRAP_BUILD_TOOL + return std::nullopt; +} + +auto ArchiveOps::CopyData(archive* ar, archive* aw) + -> std::optional { +#ifndef BOOTSTRAP_BUILD_TOOL + int r{}; + const void* buff{nullptr}; + size_t size{}; + la_int64_t offset{}; + + while (true) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) { + return std::nullopt; // success! + } + if (r != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + if (archive_write_data_block(aw, buff, size, offset) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(aw)); + } + } +#endif // BOOTSTRAP_BUILD_TOOL + return std::nullopt; // success! +} + +auto ArchiveOps::EnableWriteFormats(archive* aw, ArchiveType type) + -> std::optional { +#ifndef BOOTSTRAP_BUILD_TOOL + switch (type) { + case ArchiveType::Zip: { + if (archive_write_set_format_zip(aw) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(aw)); + } + } break; + case ArchiveType::_7Zip: { + if (archive_write_set_format_7zip(aw) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(aw)); + } + } break; + case ArchiveType::ZipAuto: { + return std::string( + "ArchiveOps: Writing a zip-like archive must be explicit"); + } + case ArchiveType::Tar: + case ArchiveType::TarGz: + case ArchiveType::TarBz2: + case ArchiveType::TarXz: + case ArchiveType::TarLz: + case ArchiveType::TarLzma: { + if ((archive_write_set_format_pax_restricted(aw) != ARCHIVE_OK) or + not enable_write_filter(aw, type)) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(aw)); + } + } break; + case ArchiveType::TarAuto: + return std::string( + "ArchiveOps: Writing a tarball-type archive must be explicit!"); + } +#endif // BOOTSTRAP_BUILD_TOOL + return std::nullopt; // success! +} + +auto ArchiveOps::EnableReadFormats(archive* ar, ArchiveType type) + -> std::optional { +#ifndef BOOTSTRAP_BUILD_TOOL + switch (type) { + case ArchiveType::Zip: { + if (archive_read_support_format_zip(ar) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + } break; + case ArchiveType::_7Zip: { + if (archive_read_support_format_7zip(ar) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + } break; + case ArchiveType::ZipAuto: { + if (archive_read_support_format_7zip(ar) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + if (archive_read_support_format_zip(ar) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + } break; + case ArchiveType::TarAuto: + case ArchiveType::Tar: + case ArchiveType::TarGz: + case ArchiveType::TarBz2: + case ArchiveType::TarXz: + case ArchiveType::TarLz: + case ArchiveType::TarLzma: { + if ((archive_read_support_format_tar(ar) != ARCHIVE_OK) or + not enable_read_filter(ar, type)) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(ar)); + } + } break; + } +#endif // BOOTSTRAP_BUILD_TOOL + return std::nullopt; // success! +} + +auto ArchiveOps::CreateArchive(ArchiveType type, + std::string const& name, + std::filesystem::path const& source) noexcept + -> std::optional { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#endif + return CreateArchive(type, name, source, std::filesystem::path(".")); +} + +auto ArchiveOps::CreateArchive(ArchiveType type, + std::string const& name, + std::filesystem::path const& source, + std::filesystem::path const& destDir) noexcept + -> std::optional { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + try { + // make sure paths will be relative wrt current dir + auto rel_source = std::filesystem::relative(source); + + std::unique_ptr a_out{ + archive_write_new(), archive_write_closer}; + if (a_out == nullptr) { + return std::string("ArchiveOps: archive_write_new failed"); + } + // enable the correct format for archive type + auto res = EnableWriteFormats(a_out.get(), type); + if (res != std::nullopt) { + return *res; + } + // open archive to write + if (not FileSystemManager::CreateDirectory(destDir)) { + return std::string( + "ArchiveOps: could not create destination directory ") + + destDir.string(); + } + if (archive_write_open_filename( + a_out.get(), (destDir / std::filesystem::path(name)).c_str()) != + ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(a_out.get())); + } + // open source + std::unique_ptr disk{ + archive_read_disk_new(), archive_read_closer}; + if (disk == nullptr) { + return std::string("ArchiveOps: archive_read_disk_new failed"); + } + archive_read_disk_set_standard_lookup(disk.get()); + if (archive_read_disk_open(disk.get(), rel_source.c_str()) != + ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(disk.get())); + } + // create archive + while (true) { + std::unique_ptr + entry{archive_entry_new(), archive_entry_cleanup}; + if (entry == nullptr) { + return std::string("ArchiveOps: archive_entry_new failed"); + } + int r = archive_read_next_header2(disk.get(), entry.get()); + if (r == ARCHIVE_EOF) { + return std::nullopt; // nothing left to archive; success! + } + if (r != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(disk.get())); + } + // if entry is a directory, make sure we descend into all its + // children + archive_read_disk_descend(disk.get()); + // get info on current entry + if (archive_write_header(a_out.get(), entry.get()) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(a_out.get())); + } + // write entry into archive + auto res = WriteEntry(entry.get(), a_out.get()); + if (res != std::nullopt) { + return *res; + } + } + } catch (std::exception const& ex) { + Logger::Log( + LogLevel::Error, "archive create failed with:\n{}", ex.what()); + return std::nullopt; + } +#endif // BOOTSTRAP_BUILD_TOOL +} + +auto ArchiveOps::ExtractArchive(ArchiveType type, + std::filesystem::path const& source) noexcept + -> std::optional { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#endif + return ExtractArchive(type, source, std::filesystem::path(".")); +} + +auto ArchiveOps::ExtractArchive(ArchiveType type, + std::filesystem::path const& source, + std::filesystem::path const& destDir) noexcept + -> std::optional { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + try { + std::unique_ptr a_in{ + archive_read_new(), archive_read_closer}; + if (a_in == nullptr) { + return std::string("ArchiveOps: archive_read_new failed"); + } + // enable support for known formats + auto res = EnableReadFormats(a_in.get(), type); + if (res != std::nullopt) { + return *res; + } + // open archive for reading + if (archive_read_open_filename( + a_in.get(), source.c_str(), kArchiveBlockSize) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(a_in.get())); + } + // set up writer to disk + std::unique_ptr disk{ + archive_write_disk_new(), archive_write_closer}; + if (disk == nullptr) { + return std::string("ArchiveOps: archive_write_disk_new failed"); + } + // Select which attributes we want to restore. + uint flags = ARCHIVE_EXTRACT_TIME; + flags |= static_cast(ARCHIVE_EXTRACT_PERM); + flags |= static_cast(ARCHIVE_EXTRACT_FFLAGS); + archive_write_disk_set_options(disk.get(), static_cast(flags)); + archive_write_disk_set_standard_lookup(disk.get()); + // make sure destination directory exists + if (not FileSystemManager::CreateDirectory(destDir)) { + return std::string( + "ArchiveOps: could not create destination directory ") + + destDir.string(); + } + // extract the archive + archive_entry* entry{nullptr}; + while (true) { + int r = archive_read_next_header(a_in.get(), &entry); + if (r == ARCHIVE_EOF) { + return std::nullopt; // nothing left to extract; success! + } + if (r != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(a_in.get())); + } + // set correct destination path + auto new_entry_path = + destDir / std::filesystem::path(archive_entry_pathname(entry)); + archive_entry_set_pathname(entry, new_entry_path.c_str()); + if (archive_write_header(disk.get(), entry) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(disk.get())); + } + // write to disk if file + if (archive_entry_size(entry) > 0) { + auto res = CopyData(a_in.get(), disk.get()); + if (res != std::nullopt) { + return *res; + } + } + // finish entry writing + if (archive_write_finish_entry(disk.get()) != ARCHIVE_OK) { + return std::string("ArchiveOps: ") + + std::string(archive_error_string(disk.get())); + } + } + } catch (std::exception const& ex) { + Logger::Log( + LogLevel::Error, "archive extract failed with:\n{}", ex.what()); + return std::nullopt; + } +#endif // BOOTSTRAP_BUILD_TOOL +} diff --git a/src/utils/archive/archive_ops.hpp b/src/utils/archive/archive_ops.hpp new file mode 100644 index 00000000..933bb814 --- /dev/null +++ b/src/utils/archive/archive_ops.hpp @@ -0,0 +1,106 @@ +// Copyright 2022 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 INCLUDED_SRC_UTILS_ARCHIVE_ARCHIVE_OPS_HPP +#define INCLUDED_SRC_UTILS_ARCHIVE_ARCHIVE_OPS_HPP + +#include +#include + +#include "gsl/gsl" + +extern "C" { +using archive = struct archive; +using archive_entry = struct archive_entry; +} + +enum class ArchiveType : size_t { + Zip, + _7Zip, + ZipAuto, // autodetect zip-like archives + Tar, // uncompressed + TarGz, + TarBz2, + TarXz, + TarLz, + TarLzma, + TarAuto // autodetect tarball-type archives +}; + +/// \brief Class handling archiving and unarchiving operations via libarchive +class ArchiveOps { + public: + /// \brief Create archive of given type from file or directory at + /// source. All paths will be takes relative to current directory. + /// Destination folder is the current directory. Archive is stored under + /// given name. Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static CreateArchive( + ArchiveType type, + std::string const& name, + std::filesystem::path const& source) noexcept + -> std::optional; + + /// \brief Create archive of given type from file or directory at source and + /// store it in destDir folder under given name. All paths will be taken as + /// relative to the current directory. Destination directory is created if + /// not present. Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static CreateArchive( + ArchiveType type, + std::string const& name, + std::filesystem::path const& source, + std::filesystem::path const& destDir) noexcept + -> std::optional; + + /// \brief Extract archive pointed to by source into destDir folder. The + /// destination folder is the current directory and the type of archive is + /// specified from currently supported formats: tar, zip, tar.gz, tar.bz2. + /// Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static ExtractArchive( + ArchiveType type, + std::filesystem::path const& source) noexcept + -> std::optional; + + /// \brief Extract archive pointed to by source into destDir folder. The + /// type of archive is specified from currently supported formats: tar, zip, + /// tar.gz, tar.bz2. Returns nullopt on success, or an error string if + /// failure. + [[nodiscard]] auto static ExtractArchive( + ArchiveType type, + std::filesystem::path const& source, + std::filesystem::path const& destDir) noexcept + -> std::optional; + + private: + /// \brief Copy entry into archive object. + /// Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static WriteEntry(archive_entry* entry, archive* aw) + -> std::optional; + + /// \brief Copy data blocks from one archive object to another. + /// Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static CopyData(archive* ar, archive* aw) + -> std::optional; + + /// \brief Set up the appropriate supported format for writing an archive. + /// Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static EnableWriteFormats(archive* aw, ArchiveType type) + -> std::optional; + + /// \brief Set up the supported formats for reading in an archive. + /// Returns nullopt on success, or an error string if failure. + [[nodiscard]] auto static EnableReadFormats(archive* ar, ArchiveType type) + -> std::optional; +}; + +#endif // INCLUDED_SRC_UTILS_ARCHIVE_ARCHIVE_OPS_HPP diff --git a/test/end-to-end/just-mr/TARGETS b/test/end-to-end/just-mr/TARGETS index 23ad1062..f8a8d10d 100644 --- a/test/end-to-end/just-mr/TARGETS +++ b/test/end-to-end/just-mr/TARGETS @@ -25,7 +25,7 @@ , "private-deps": [ ["@", "src", "src/buildtool/file_system", "file_system_manager"] , ["@", "src", "src/buildtool/logging", "logging"] - , ["@", "src", "src/other_tools/utils", "archive_ops"] + , ["@", "src", "src/utils/archive", "archive_ops"] , ["@", "src", "src/utils/cpp", "tmp_dir"] ] , "private-ldflags": ["-pthread"] diff --git a/test/end-to-end/just-mr/create_test_archives.cpp b/test/end-to-end/just-mr/create_test_archives.cpp index f7223410..5f50bade 100644 --- a/test/end-to-end/just-mr/create_test_archives.cpp +++ b/test/end-to-end/just-mr/create_test_archives.cpp @@ -17,7 +17,7 @@ #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/logging/log_config.hpp" #include "src/buildtool/logging/log_sink_cmdline.hpp" -#include "src/other_tools/utils/archive_ops.hpp" +#include "src/utils/archive/archive_ops.hpp" #include "src/utils/cpp/tmp_dir.hpp" namespace { diff --git a/test/other_tools/utils/TARGETS b/test/other_tools/utils/TARGETS index 11bb619b..b43788f1 100644 --- a/test/other_tools/utils/TARGETS +++ b/test/other_tools/utils/TARGETS @@ -1,17 +1,4 @@ -{ "archive_usage": - { "type": ["@", "rules", "CC/test", "test"] - , "name": ["archive_usage"] - , "srcs": ["archive_usage.test.cpp"] - , "private-deps": - [ ["@", "catch2", "", "catch2"] - , ["", "catch-main"] - , ["@", "src", "src/buildtool/file_system", "file_system_manager"] - , ["@", "src", "", "libarchive"] - , ["@", "src", "src/other_tools/utils", "archive_ops"] - ] - , "stage": ["test", "other_tools", "utils"] - } -, "curl_usage_install": +{ "curl_usage_install": { "type": ["@", "rules", "CC", "binary"] , "tainted": ["test"] , "name": ["curl_usage_install"] @@ -43,8 +30,5 @@ , "stage": ["test", "other_tools", "utils"] } , "TESTS": - { "type": "install" - , "tainted": ["test"] - , "deps": ["archive_usage", "curl_usage", "curl_url"] - } + {"type": "install", "tainted": ["test"], "deps": ["curl_usage", "curl_url"]} } diff --git a/test/other_tools/utils/archive_usage.test.cpp b/test/other_tools/utils/archive_usage.test.cpp deleted file mode 100644 index 09d080c5..00000000 --- a/test/other_tools/utils/archive_usage.test.cpp +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2022 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. - -#include -#include -#include - -#include "catch2/catch_test_macros.hpp" -#include "catch2/generators/catch_generators_all.hpp" -#include "src/buildtool/file_system/file_system_manager.hpp" -#include "src/other_tools/utils/archive_ops.hpp" - -extern "C" { -#include -#include -} - -namespace { - -using file_t = std::pair; -using filetree_t = std::unordered_map; - -constexpr size_t kBlockSize = 10240; -constexpr int kFilePerm = 0644; -constexpr int kDirectoryPerm = 0755; - -auto const kExpected = filetree_t{{"foo", {"foo", AE_IFREG}}, - {"bar/", {"", AE_IFDIR}}, - {"bar/baz", {"baz", AE_IFREG}}}; - -struct archive_test_info_t { - std::string test_name; - ArchiveType type; - std::string test_dir; - std::string filename; - std::vector tools; - std::string cmd; -}; - -std::vector const kTestScenarios = { - {.test_name = "tar", - .type = ArchiveType::Tar, - .test_dir = "test_tar", - .filename = "test.tar", - .tools = {"tar"}, - .cmd = "/usr/bin/tar xf"}, - {.test_name = "tar.gz", - .type = ArchiveType::TarGz, - .test_dir = "test_tar_gz", - .filename = "test.tar.gz", - .tools = {"tar", "gzip"}, - .cmd = "/usr/bin/tar xzf"}, - {.test_name = "tar.bz2", - .type = ArchiveType::TarBz2, - .test_dir = "test_tar_bz2", - .filename = "test.tar.bz2", - .tools = {"tar", "bzip2"}, - .cmd = "/usr/bin/tar xjf"}, - {.test_name = "tar.xz", - .type = ArchiveType::TarXz, - .test_dir = "test_tar_xz", - .filename = "test.tar.xz", - .tools = {"tar", "xz"}, - .cmd = "/usr/bin/tar xJf"}, - {.test_name = "tar.lz", - .type = ArchiveType::TarLz, - .test_dir = "test_tar_lz", - .filename = "test.tar.lz", - .tools = {"tar", "lzip"}, - .cmd = "/usr/bin/tar --lzip -x -f"}, - {.test_name = "tar.lzma", - .type = ArchiveType::TarLzma, - .test_dir = "test_tar_lzma", - .filename = "test.tar.lzma", - .tools = {"tar", "lzma"}, - .cmd = "/usr/bin/tar --lzma -x -f"}, - {.test_name = "zip", - .type = ArchiveType::Zip, - .test_dir = "test_zip", - .filename = "test.zip", - .tools = {"unzip"}, - .cmd = "/usr/bin/unzip"}, - {.test_name = "7zip", - .type = ArchiveType::_7Zip, - .test_dir = "test_7zip", - .filename = "test.7z", - .tools = {"7z"}, // 7z comes with its own lzma-type compression - .cmd = "/usr/bin/7z x"}}; - -[[nodiscard]] auto read_archive(archive* a, std::string const& path) - -> filetree_t { - filetree_t result{}; - - REQUIRE(archive_read_open_filename(a, path.c_str(), kBlockSize) == - ARCHIVE_OK); - - archive_entry* entry{}; - while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { - auto size = archive_entry_size(entry); - auto buf = std::string(static_cast(size), '\0'); - REQUIRE(archive_read_data(a, buf.data(), buf.size()) == - static_cast(buf.size())); - result.emplace(archive_entry_pathname(entry), - file_t{buf, archive_entry_filetype(entry)}); - } - REQUIRE(archive_read_close(a) == ARCHIVE_OK); - - return result; -} - -void write_archive(archive* a, - std::string const& path, - filetree_t const& files) { - REQUIRE(archive_write_open_filename(a, path.c_str()) == ARCHIVE_OK); - - archive_entry* entry = archive_entry_new(); - for (auto const& [path, file] : files) { - auto const& [content, type] = file; - archive_entry_set_pathname(entry, path.c_str()); - archive_entry_set_filetype(entry, type); - if (type == AE_IFREG) { - auto buf = std::filesystem::path{path}.filename().string(); - archive_entry_set_perm(entry, kFilePerm); - archive_entry_set_size(entry, static_cast(buf.size())); - REQUIRE(archive_write_header(a, entry) == ARCHIVE_OK); - REQUIRE(archive_write_data(a, buf.data(), buf.size()) == - static_cast(buf.size())); - } - else { - archive_entry_set_perm(entry, kDirectoryPerm); - archive_entry_set_size(entry, 0); - REQUIRE(archive_write_header(a, entry) == ARCHIVE_OK); - } - entry = archive_entry_clear(entry); - } - archive_entry_free(entry); - REQUIRE(archive_write_close(a) == ARCHIVE_OK); -} - -void extract_archive(std::string const& path) { - auto* a = archive_read_new(); - REQUIRE(a != nullptr); - REQUIRE(archive_read_support_format_tar(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_format_zip(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_format_7zip(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_gzip(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_bzip2(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_xz(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_lzip(a) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_lzma(a) == ARCHIVE_OK); - REQUIRE(archive_read_open_filename(a, path.c_str(), kBlockSize) == - ARCHIVE_OK); - - auto* out = archive_write_disk_new(); - REQUIRE(out != nullptr); - archive_entry* entry{}; - int r{}; - while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK) { - REQUIRE(archive_write_header(out, entry) == ARCHIVE_OK); - if (archive_entry_size(entry) > 0) { - void const* buf{}; - size_t size{}; - int64_t offset{}; - int r2{}; - while ((r2 = archive_read_data_block(a, &buf, &size, &offset)) == - ARCHIVE_OK) { - REQUIRE(archive_write_data_block(out, buf, size, offset) == - ARCHIVE_OK); - } - REQUIRE(r2 == ARCHIVE_EOF); - REQUIRE(archive_write_finish_entry(out) == ARCHIVE_OK); - } - } - REQUIRE(r == ARCHIVE_EOF); - REQUIRE(archive_read_close(a) == ARCHIVE_OK); - REQUIRE(archive_read_free(a) == ARCHIVE_OK); - REQUIRE(archive_write_close(out) == ARCHIVE_OK); - REQUIRE(archive_write_free(out) == ARCHIVE_OK); -} - -void compare_extracted( - std::filesystem::path const& extract_dir = ".") noexcept { - for (auto const& [path, file] : kExpected) { - auto const& [content, type] = file; - switch (type) { - case AE_IFREG: { - REQUIRE(FileSystemManager::IsFile(extract_dir / path)); - auto data = FileSystemManager::ReadFile(extract_dir / path); - REQUIRE(data); - CHECK(*data == content); - } break; - case AE_IFDIR: - CHECK(FileSystemManager::IsDirectory(extract_dir / path)); - break; - default: - CHECK(false); - } - } -} - -void create_files(std::filesystem::path const& destDir = ".") noexcept { - for (auto const& [path, file] : kExpected) { - auto const& [content, type] = file; - switch (type) { - case AE_IFREG: { - CHECK(FileSystemManager::WriteFile(content, destDir / path)); - } break; - case AE_IFDIR: - CHECK(FileSystemManager::CreateDirectory(destDir / path)); - break; - default: - CHECK(false); - } - } -} - -void enable_write_format_and_filter(archive* aw, ArchiveType type) { - switch (type) { - case ArchiveType::Zip: { - REQUIRE(archive_write_set_format_zip(aw) == ARCHIVE_OK); - } break; - case ArchiveType::_7Zip: { - REQUIRE(archive_write_set_format_7zip(aw) == ARCHIVE_OK); - } break; - case ArchiveType::Tar: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - } break; - case ArchiveType::TarGz: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - REQUIRE(archive_write_add_filter_gzip(aw) == ARCHIVE_OK); - } break; - case ArchiveType::TarBz2: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - REQUIRE(archive_write_add_filter_bzip2(aw) == ARCHIVE_OK); - } break; - case ArchiveType::TarXz: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - REQUIRE(archive_write_add_filter_xz(aw) == ARCHIVE_OK); - } break; - case ArchiveType::TarLz: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - REQUIRE(archive_write_add_filter_lzip(aw) == ARCHIVE_OK); - } break; - case ArchiveType::TarLzma: { - REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); - REQUIRE(archive_write_add_filter_lzma(aw) == ARCHIVE_OK); - } break; - case ArchiveType::ZipAuto: - case ArchiveType::TarAuto: - return; // unused - } -} - -void enable_read_format_and_filter(archive* ar, ArchiveType type) { - switch (type) { - case ArchiveType::Zip: { - REQUIRE(archive_read_support_format_zip(ar) == ARCHIVE_OK); - } break; - case ArchiveType::_7Zip: { - REQUIRE(archive_read_support_format_7zip(ar) == ARCHIVE_OK); - } break; - case ArchiveType::Tar: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - } break; - case ArchiveType::TarGz: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_gzip(ar) == ARCHIVE_OK); - } break; - case ArchiveType::TarBz2: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_bzip2(ar) == ARCHIVE_OK); - } break; - case ArchiveType::TarXz: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_xz(ar) == ARCHIVE_OK); - } break; - case ArchiveType::TarLz: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_lzip(ar) == ARCHIVE_OK); - } break; - case ArchiveType::TarLzma: { - REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); - REQUIRE(archive_read_support_filter_lzma(ar) == ARCHIVE_OK); - } break; - case ArchiveType::ZipAuto: - case ArchiveType::TarAuto: - return; // unused - } -} - -} // namespace - -TEST_CASE("Archive read context", "[archive_context]") { - auto* a = archive_read_new(); - REQUIRE(a != nullptr); - CHECK(archive_read_free(a) == ARCHIVE_OK); -} - -TEST_CASE("Archive write context", "[archive_context]") { - auto* a = archive_write_new(); - REQUIRE(a != nullptr); - CHECK(archive_write_free(a) == ARCHIVE_OK); -} - -TEST_CASE("Archive write disk context", "[archive_context]") { - auto* a = archive_write_disk_new(); - REQUIRE(a != nullptr); - CHECK(archive_read_free(a) == ARCHIVE_OK); -} - -TEST_CASE("Read-write archives", "[archive_read_write]") { - // get the scenario - auto test_index = - GENERATE(Catch::Generators::range(0, kTestScenarios.size())); - auto const& scenario = kTestScenarios[test_index]; - - // perform the test - REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, - /*recursively=*/true)); - REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); - auto anchor = FileSystemManager::ChangeDirectory(scenario.test_dir); - - SECTION(std::string("Write ") + scenario.test_name) { - auto* out = archive_write_new(); - REQUIRE(out != nullptr); - enable_write_format_and_filter(out, scenario.type); - write_archive(out, scenario.filename, kExpected); - REQUIRE(archive_write_free(out) == ARCHIVE_OK); - - SECTION(std::string("Read ") + scenario.test_name) { - auto* in = archive_read_new(); - REQUIRE(in != nullptr); - enable_read_format_and_filter(in, scenario.type); - CHECK(read_archive(in, scenario.filename) == kExpected); - REQUIRE(archive_read_free(in) == ARCHIVE_OK); - } - - SECTION(std::string("Extract ") + scenario.test_name + " to disk") { - extract_archive(scenario.filename); - compare_extracted(); - } - - bool tools_exist{true}; - for (auto const& tool : scenario.tools) { - tools_exist &= FileSystemManager::IsExecutable( - std::string("/usr/bin/") + tool); - } - - if (tools_exist) { - SECTION("Extract via system tools") { - REQUIRE( - system((scenario.cmd + " " + scenario.filename).c_str()) == - 0); - compare_extracted(); - } - } - } -} - -TEST_CASE("ArchiveOps", "[archive_ops]") { - // get the scenario - auto test_index = - GENERATE(Catch::Generators::range(0, kTestScenarios.size())); - auto const& scenario = kTestScenarios[test_index]; - - // perform the test - std::optional res{std::nullopt}; - - SECTION(std::string("Write ") + scenario.test_name) { - REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, - /*recursively=*/true)); - REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); - - create_files(scenario.test_dir); - - res = ArchiveOps::CreateArchive( - scenario.type, scenario.filename, scenario.test_dir, "."); - if (res != std::nullopt) { - FAIL(*res); - } - - SECTION(std::string("Extract ") + scenario.test_name + " to disk") { - REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, - /*recursively=*/true)); - REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); - res = ArchiveOps::ExtractArchive( - scenario.type, scenario.filename, "."); - if (res != std::nullopt) { - FAIL(*res); - } - compare_extracted(scenario.test_dir); - } - - bool tools_exist{true}; - for (auto const& tool : scenario.tools) { - tools_exist &= FileSystemManager::IsExecutable( - std::string("/usr/bin/") + tool); - } - if (tools_exist) { - SECTION("Extract via system tools") { - REQUIRE( - FileSystemManager::RemoveDirectory(scenario.test_dir, - /*recursively=*/true)); - REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); - - REQUIRE( - system((scenario.cmd + " " + scenario.filename).c_str()) == - 0); - compare_extracted(scenario.test_dir); - } - } - } -} diff --git a/test/utils/TARGETS b/test/utils/TARGETS index 2a6f99d0..94fb936f 100644 --- a/test/utils/TARGETS +++ b/test/utils/TARGETS @@ -92,6 +92,7 @@ , "TESTS": { "type": "install" , "tainted": ["test"] - , "dirs": [[["./", "cpp", "TESTS"], "cpp"]] + , "dirs": + [[["./", "cpp", "TESTS"], "cpp"], [["./", "archive", "TESTS"], "archive"]] } } diff --git a/test/utils/archive/TARGETS b/test/utils/archive/TARGETS new file mode 100644 index 00000000..15b849c5 --- /dev/null +++ b/test/utils/archive/TARGETS @@ -0,0 +1,15 @@ +{ "archive_usage": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["archive_usage"] + , "srcs": ["archive_usage.test.cpp"] + , "private-deps": + [ ["@", "catch2", "", "catch2"] + , ["", "catch-main"] + , ["@", "src", "src/buildtool/file_system", "file_system_manager"] + , ["@", "src", "", "libarchive"] + , ["@", "src", "src/utils/archive", "archive_ops"] + ] + , "stage": ["test", "utils", "archive"] + } +, "TESTS": {"type": "install", "tainted": ["test"], "deps": ["archive_usage"]} +} diff --git a/test/utils/archive/archive_usage.test.cpp b/test/utils/archive/archive_usage.test.cpp new file mode 100644 index 00000000..6a8eb751 --- /dev/null +++ b/test/utils/archive/archive_usage.test.cpp @@ -0,0 +1,424 @@ +// Copyright 2022 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. + +#include +#include +#include + +#include "catch2/catch_test_macros.hpp" +#include "catch2/generators/catch_generators_all.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/utils/archive/archive_ops.hpp" + +extern "C" { +#include +#include +} + +namespace { + +using file_t = std::pair; +using filetree_t = std::unordered_map; + +constexpr size_t kBlockSize = 10240; +constexpr int kFilePerm = 0644; +constexpr int kDirectoryPerm = 0755; + +auto const kExpected = filetree_t{{"foo", {"foo", AE_IFREG}}, + {"bar/", {"", AE_IFDIR}}, + {"bar/baz", {"baz", AE_IFREG}}}; + +struct archive_test_info_t { + std::string test_name; + ArchiveType type; + std::string test_dir; + std::string filename; + std::vector tools; + std::string cmd; +}; + +std::vector const kTestScenarios = { + {.test_name = "tar", + .type = ArchiveType::Tar, + .test_dir = "test_tar", + .filename = "test.tar", + .tools = {"tar"}, + .cmd = "/usr/bin/tar xf"}, + {.test_name = "tar.gz", + .type = ArchiveType::TarGz, + .test_dir = "test_tar_gz", + .filename = "test.tar.gz", + .tools = {"tar", "gzip"}, + .cmd = "/usr/bin/tar xzf"}, + {.test_name = "tar.bz2", + .type = ArchiveType::TarBz2, + .test_dir = "test_tar_bz2", + .filename = "test.tar.bz2", + .tools = {"tar", "bzip2"}, + .cmd = "/usr/bin/tar xjf"}, + {.test_name = "tar.xz", + .type = ArchiveType::TarXz, + .test_dir = "test_tar_xz", + .filename = "test.tar.xz", + .tools = {"tar", "xz"}, + .cmd = "/usr/bin/tar xJf"}, + {.test_name = "tar.lz", + .type = ArchiveType::TarLz, + .test_dir = "test_tar_lz", + .filename = "test.tar.lz", + .tools = {"tar", "lzip"}, + .cmd = "/usr/bin/tar --lzip -x -f"}, + {.test_name = "tar.lzma", + .type = ArchiveType::TarLzma, + .test_dir = "test_tar_lzma", + .filename = "test.tar.lzma", + .tools = {"tar", "lzma"}, + .cmd = "/usr/bin/tar --lzma -x -f"}, + {.test_name = "zip", + .type = ArchiveType::Zip, + .test_dir = "test_zip", + .filename = "test.zip", + .tools = {"unzip"}, + .cmd = "/usr/bin/unzip"}, + {.test_name = "7zip", + .type = ArchiveType::_7Zip, + .test_dir = "test_7zip", + .filename = "test.7z", + .tools = {"7z"}, // 7z comes with its own lzma-type compression + .cmd = "/usr/bin/7z x"}}; + +[[nodiscard]] auto read_archive(archive* a, std::string const& path) + -> filetree_t { + filetree_t result{}; + + REQUIRE(archive_read_open_filename(a, path.c_str(), kBlockSize) == + ARCHIVE_OK); + + archive_entry* entry{}; + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + auto size = archive_entry_size(entry); + auto buf = std::string(static_cast(size), '\0'); + REQUIRE(archive_read_data(a, buf.data(), buf.size()) == + static_cast(buf.size())); + result.emplace(archive_entry_pathname(entry), + file_t{buf, archive_entry_filetype(entry)}); + } + REQUIRE(archive_read_close(a) == ARCHIVE_OK); + + return result; +} + +void write_archive(archive* a, + std::string const& path, + filetree_t const& files) { + REQUIRE(archive_write_open_filename(a, path.c_str()) == ARCHIVE_OK); + + archive_entry* entry = archive_entry_new(); + for (auto const& [path, file] : files) { + auto const& [content, type] = file; + archive_entry_set_pathname(entry, path.c_str()); + archive_entry_set_filetype(entry, type); + if (type == AE_IFREG) { + auto buf = std::filesystem::path{path}.filename().string(); + archive_entry_set_perm(entry, kFilePerm); + archive_entry_set_size(entry, static_cast(buf.size())); + REQUIRE(archive_write_header(a, entry) == ARCHIVE_OK); + REQUIRE(archive_write_data(a, buf.data(), buf.size()) == + static_cast(buf.size())); + } + else { + archive_entry_set_perm(entry, kDirectoryPerm); + archive_entry_set_size(entry, 0); + REQUIRE(archive_write_header(a, entry) == ARCHIVE_OK); + } + entry = archive_entry_clear(entry); + } + archive_entry_free(entry); + REQUIRE(archive_write_close(a) == ARCHIVE_OK); +} + +void extract_archive(std::string const& path) { + auto* a = archive_read_new(); + REQUIRE(a != nullptr); + REQUIRE(archive_read_support_format_tar(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_format_zip(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_format_7zip(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_gzip(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_bzip2(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_xz(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_lzip(a) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_lzma(a) == ARCHIVE_OK); + REQUIRE(archive_read_open_filename(a, path.c_str(), kBlockSize) == + ARCHIVE_OK); + + auto* out = archive_write_disk_new(); + REQUIRE(out != nullptr); + archive_entry* entry{}; + int r{}; + while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK) { + REQUIRE(archive_write_header(out, entry) == ARCHIVE_OK); + if (archive_entry_size(entry) > 0) { + void const* buf{}; + size_t size{}; + int64_t offset{}; + int r2{}; + while ((r2 = archive_read_data_block(a, &buf, &size, &offset)) == + ARCHIVE_OK) { + REQUIRE(archive_write_data_block(out, buf, size, offset) == + ARCHIVE_OK); + } + REQUIRE(r2 == ARCHIVE_EOF); + REQUIRE(archive_write_finish_entry(out) == ARCHIVE_OK); + } + } + REQUIRE(r == ARCHIVE_EOF); + REQUIRE(archive_read_close(a) == ARCHIVE_OK); + REQUIRE(archive_read_free(a) == ARCHIVE_OK); + REQUIRE(archive_write_close(out) == ARCHIVE_OK); + REQUIRE(archive_write_free(out) == ARCHIVE_OK); +} + +void compare_extracted( + std::filesystem::path const& extract_dir = ".") noexcept { + for (auto const& [path, file] : kExpected) { + auto const& [content, type] = file; + switch (type) { + case AE_IFREG: { + REQUIRE(FileSystemManager::IsFile(extract_dir / path)); + auto data = FileSystemManager::ReadFile(extract_dir / path); + REQUIRE(data); + CHECK(*data == content); + } break; + case AE_IFDIR: + CHECK(FileSystemManager::IsDirectory(extract_dir / path)); + break; + default: + CHECK(false); + } + } +} + +void create_files(std::filesystem::path const& destDir = ".") noexcept { + for (auto const& [path, file] : kExpected) { + auto const& [content, type] = file; + switch (type) { + case AE_IFREG: { + CHECK(FileSystemManager::WriteFile(content, destDir / path)); + } break; + case AE_IFDIR: + CHECK(FileSystemManager::CreateDirectory(destDir / path)); + break; + default: + CHECK(false); + } + } +} + +void enable_write_format_and_filter(archive* aw, ArchiveType type) { + switch (type) { + case ArchiveType::Zip: { + REQUIRE(archive_write_set_format_zip(aw) == ARCHIVE_OK); + } break; + case ArchiveType::_7Zip: { + REQUIRE(archive_write_set_format_7zip(aw) == ARCHIVE_OK); + } break; + case ArchiveType::Tar: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + } break; + case ArchiveType::TarGz: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + REQUIRE(archive_write_add_filter_gzip(aw) == ARCHIVE_OK); + } break; + case ArchiveType::TarBz2: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + REQUIRE(archive_write_add_filter_bzip2(aw) == ARCHIVE_OK); + } break; + case ArchiveType::TarXz: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + REQUIRE(archive_write_add_filter_xz(aw) == ARCHIVE_OK); + } break; + case ArchiveType::TarLz: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + REQUIRE(archive_write_add_filter_lzip(aw) == ARCHIVE_OK); + } break; + case ArchiveType::TarLzma: { + REQUIRE(archive_write_set_format_pax_restricted(aw) == ARCHIVE_OK); + REQUIRE(archive_write_add_filter_lzma(aw) == ARCHIVE_OK); + } break; + case ArchiveType::ZipAuto: + case ArchiveType::TarAuto: + return; // unused + } +} + +void enable_read_format_and_filter(archive* ar, ArchiveType type) { + switch (type) { + case ArchiveType::Zip: { + REQUIRE(archive_read_support_format_zip(ar) == ARCHIVE_OK); + } break; + case ArchiveType::_7Zip: { + REQUIRE(archive_read_support_format_7zip(ar) == ARCHIVE_OK); + } break; + case ArchiveType::Tar: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + } break; + case ArchiveType::TarGz: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_gzip(ar) == ARCHIVE_OK); + } break; + case ArchiveType::TarBz2: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_bzip2(ar) == ARCHIVE_OK); + } break; + case ArchiveType::TarXz: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_xz(ar) == ARCHIVE_OK); + } break; + case ArchiveType::TarLz: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_lzip(ar) == ARCHIVE_OK); + } break; + case ArchiveType::TarLzma: { + REQUIRE(archive_read_support_format_tar(ar) == ARCHIVE_OK); + REQUIRE(archive_read_support_filter_lzma(ar) == ARCHIVE_OK); + } break; + case ArchiveType::ZipAuto: + case ArchiveType::TarAuto: + return; // unused + } +} + +} // namespace + +TEST_CASE("Archive read context", "[archive_context]") { + auto* a = archive_read_new(); + REQUIRE(a != nullptr); + CHECK(archive_read_free(a) == ARCHIVE_OK); +} + +TEST_CASE("Archive write context", "[archive_context]") { + auto* a = archive_write_new(); + REQUIRE(a != nullptr); + CHECK(archive_write_free(a) == ARCHIVE_OK); +} + +TEST_CASE("Archive write disk context", "[archive_context]") { + auto* a = archive_write_disk_new(); + REQUIRE(a != nullptr); + CHECK(archive_read_free(a) == ARCHIVE_OK); +} + +TEST_CASE("Read-write archives", "[archive_read_write]") { + // get the scenario + auto test_index = + GENERATE(Catch::Generators::range(0, kTestScenarios.size())); + auto const& scenario = kTestScenarios[test_index]; + + // perform the test + REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, + /*recursively=*/true)); + REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); + auto anchor = FileSystemManager::ChangeDirectory(scenario.test_dir); + + SECTION(std::string("Write ") + scenario.test_name) { + auto* out = archive_write_new(); + REQUIRE(out != nullptr); + enable_write_format_and_filter(out, scenario.type); + write_archive(out, scenario.filename, kExpected); + REQUIRE(archive_write_free(out) == ARCHIVE_OK); + + SECTION(std::string("Read ") + scenario.test_name) { + auto* in = archive_read_new(); + REQUIRE(in != nullptr); + enable_read_format_and_filter(in, scenario.type); + CHECK(read_archive(in, scenario.filename) == kExpected); + REQUIRE(archive_read_free(in) == ARCHIVE_OK); + } + + SECTION(std::string("Extract ") + scenario.test_name + " to disk") { + extract_archive(scenario.filename); + compare_extracted(); + } + + bool tools_exist{true}; + for (auto const& tool : scenario.tools) { + tools_exist &= FileSystemManager::IsExecutable( + std::string("/usr/bin/") + tool); + } + + if (tools_exist) { + SECTION("Extract via system tools") { + REQUIRE( + system((scenario.cmd + " " + scenario.filename).c_str()) == + 0); + compare_extracted(); + } + } + } +} + +TEST_CASE("ArchiveOps", "[archive_ops]") { + // get the scenario + auto test_index = + GENERATE(Catch::Generators::range(0, kTestScenarios.size())); + auto const& scenario = kTestScenarios[test_index]; + + // perform the test + std::optional res{std::nullopt}; + + SECTION(std::string("Write ") + scenario.test_name) { + REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, + /*recursively=*/true)); + REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); + + create_files(scenario.test_dir); + + res = ArchiveOps::CreateArchive( + scenario.type, scenario.filename, scenario.test_dir, "."); + if (res != std::nullopt) { + FAIL(*res); + } + + SECTION(std::string("Extract ") + scenario.test_name + " to disk") { + REQUIRE(FileSystemManager::RemoveDirectory(scenario.test_dir, + /*recursively=*/true)); + REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); + res = ArchiveOps::ExtractArchive( + scenario.type, scenario.filename, "."); + if (res != std::nullopt) { + FAIL(*res); + } + compare_extracted(scenario.test_dir); + } + + bool tools_exist{true}; + for (auto const& tool : scenario.tools) { + tools_exist &= FileSystemManager::IsExecutable( + std::string("/usr/bin/") + tool); + } + if (tools_exist) { + SECTION("Extract via system tools") { + REQUIRE( + FileSystemManager::RemoveDirectory(scenario.test_dir, + /*recursively=*/true)); + REQUIRE(FileSystemManager::CreateDirectory(scenario.test_dir)); + + REQUIRE( + system((scenario.cmd + " " + scenario.filename).c_str()) == + 0); + compare_extracted(scenario.test_dir); + } + } + } +} -- cgit v1.2.3