/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "ArchiveReader.hpp" #include "ArchiveConfiguration.hpp" #include "LibArchiveHelper.hpp" #include "Base/Utils.hpp" #include #include #include #include namespace OpenVulkano { namespace { constexpr int BUFFER_SIZE = 16384; } ArchiveReader::ArchiveReader(const std::shared_ptr& logger) : ArchiveBase(archive_read_new(), nullptr, logger) {} ArchiveReader::ArchiveReader(const std::string& archiveFile, const std::shared_ptr& logger) : ArchiveReader(archiveFile.c_str(), logger) {} ArchiveReader::ArchiveReader(const char* archiveFile, const std::shared_ptr& logger) : ArchiveReader(logger) { Open(archiveFile); } ArchiveReader::ArchiveReader(const void* archiveBuffer, const size_t size, const std::shared_ptr& logger) : ArchiveReader(logger) { OpenMemory(archiveBuffer, size); } ArchiveReader::~ArchiveReader() = default; void ArchiveReader::PrepOpen() { if (m_open) { ChkErr(archive_read_close(m_archive)); ChkErr(archive_free(m_archive)); m_archive = archive_read_new(); } ChkErr(archive_read_support_filter_all(m_archive)); ChkErr(archive_read_support_format_all(m_archive)); m_open = true; } bool ArchiveReader::Open(const char* archiveFile) { PrepOpen(); ChkErr(archive_read_open_filename(m_archive, archiveFile, BUFFER_SIZE)); ReadNextHeader(); return HasNext(); } bool ArchiveReader::Open(const std::string& archiveFile) { return Open(archiveFile.c_str()); } bool ArchiveReader::OpenMemory(const void* archiveBuffer, size_t size) { PrepOpen(); ChkErr(archive_read_open_memory(m_archive, archiveBuffer, size)); ReadNextHeader(); return HasNext(); } bool ArchiveReader::Open(const std::vector& archiveFiles) { if (archiveFiles.empty()) return false; ArchiveConfiguration ac = ArchiveConfiguration::FromFileName(archiveFiles.front().c_str()); if (ac.type == ArchiveType::TAR) // TODO handle all archive types correctly { // Queue based approach for all archive types that do not natively support split archives for(const std::string& file : archiveFiles) { m_archivesToRead.push(file); } bool state = Open(m_archivesToRead.front()); m_archivesToRead.pop(); return state; } else { // This might only be used for archive types that support split archives (like zip, rar, 7zip, ...) auto files = Utils::toCString(archiveFiles); files.push_back(nullptr); PrepOpen(); ChkErr(archive_read_open_filenames(m_archive, files.data(), BUFFER_SIZE)); ReadNextHeader(); return HasNext(); } } bool ArchiveReader::Open(const std::filesystem::path& dir, const std::string& fileNamePattern) { std::vector files; std::regex fileRegex(fileNamePattern); for(const std::filesystem::directory_entry& dirEntry : std::filesystem::directory_iterator(dir)) { std::string entryPath = dirEntry.path().string(); if (std::regex_match(entryPath, fileRegex)) { files.push_back(std::move(entryPath)); } } std::sort(files.begin(), files.end()); return Open(files); } void ArchiveReader::ReadNextHeader() { if (m_eof || !m_open) { m_archiveEntry = nullptr; return; } int result = archive_read_next_header(m_archive, &m_archiveEntry); if (!ChkErr(result)) { m_archiveEntry = nullptr; } if (result == ARCHIVE_EOF) { m_archiveEntry = nullptr; if (!m_archivesToRead.empty()) { std::string next = std::move(m_archivesToRead.front()); m_archivesToRead.pop(); Open(next); } else m_eof = true; } } size_t ArchiveReader::ExtractRemaining(std::string_view targetDir) { size_t count = 0; while (ExtractNext(targetDir)) { count++; } return count; } bool ArchiveReader::HasNext() const { return m_archiveEntry != nullptr; } void ArchiveReader::SkipNext() { ReadNextHeader(); } bool ArchiveReader::SkipTill(std::filesystem::file_type type) { mode_t typeId = LibArchiveHelper::GetFileType(type); if (typeId == AE_IFMT) throw std::invalid_argument("Invalid file type"); while(HasNext() && archive_entry_filetype(m_archiveEntry) != typeId) { SkipNext(); } return HasNext(); } FileDescription ArchiveReader::GetNextDescription() const { return { LibArchiveHelper::GetFileType(archive_entry_filetype(m_archiveEntry)), std::string(archive_entry_pathname_utf8(m_archiveEntry)), archive_entry_size_is_set(m_archiveEntry) ? static_cast(archive_entry_size(m_archiveEntry)) : SIZE_MAX, static_cast(archive_entry_perm(m_archiveEntry)) & std::filesystem::perms::mask, archive_entry_ctime(m_archiveEntry), archive_entry_mtime(m_archiveEntry) }; } std::optional ArchiveReader::GetNextDirectory() { if (SkipTill(std::filesystem::file_type::directory)) { return GetNextDescription(); } return std::nullopt; } std::optional ArchiveReader::ExtractNext(std::string_view targetDir) { if (SkipTill(std::filesystem::file_type::regular)) { FileDescription fileDescription = GetNextDescription(); std::string outputFileName; outputFileName.reserve(targetDir.size() + 1 + fileDescription.path.size()); outputFileName.append(targetDir).append("/").append(fileDescription.path); std::unique_ptr a(archive_write_disk_new(), archive_write_free); std::unique_ptr entry(archive_entry_clone(m_archiveEntry), archive_entry_free); archive_entry_set_pathname(entry.get(), outputFileName.c_str()); ChkErr(archive_write_disk_set_options(a.get(), ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS)); ChkErr(archive_write_header(a.get(), entry.get())); int result = LibArchiveHelper::CopyArchiveData(m_archive, a.get()); ChkErr(result); LibArchiveHelper::CheckError(result, a.get(), m_logger); ChkErr(archive_write_finish_entry(a.get())); ReadNextHeader(); return fileDescription; } return std::nullopt; } std::optional>> ArchiveReader::GetNextFile() { if (SkipTill(std::filesystem::file_type::regular)) { std::pair> file = { GetNextDescription(), Array() }; file.second = Array(file.first.size); ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); ReadNextHeader(); return file; } return std::nullopt; } std::optional>> ArchiveReader::GetNextFileAsVector() { if (SkipTill(std::filesystem::file_type::regular)) { std::pair> file = { GetNextDescription(), std::vector() }; file.second.resize(file.first.size); ChkErr(archive_read_data(m_archive, file.second.data(), file.second.size())); ReadNextHeader(); return file; } return std::nullopt; } bool ArchiveReader::GetNextFileAsStream(const std::function& streamReader) { if (SkipTill(std::filesystem::file_type::regular)) { FileDescription fileDescription = GetNextDescription(); LibArchiveEntryStreamBuffer streamBuffer(m_archive); std::istream stream(&streamBuffer); streamReader(fileDescription, stream); ReadNextHeader(); return true; } return false; } std::optional ArchiveReader::StreamNextFile(std::ostream& stream) { if (SkipTill(std::filesystem::file_type::regular)) { const void *buffer; size_t size; int64_t offset, r; while ((r = archive_read_data_block(m_archive, &buffer, &size, &offset)) != ARCHIVE_EOF) { ChkErr(r); stream.write(static_cast(buffer), size); } FileDescription fileDescription = GetNextDescription(); ReadNextHeader(); return fileDescription; } return std::nullopt; } }