| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/developer/forensics/utils/archive.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include "src/lib/files/file.h" | |
| #include "src/lib/files/scoped_temp_dir.h" | |
| #include "src/lib/fsl/vmo/file.h" | |
| #include "src/lib/fsl/vmo/sized_vmo.h" | |
| #include "src/lib/fsl/vmo/vector.h" | |
| #include "src/lib/fxl/strings/substitute.h" | |
| #include "third_party/zlib/contrib/minizip/unzip.h" | |
| #include "third_party/zlib/contrib/minizip/zip.h" | |
| namespace forensics { | |
| namespace { | |
| using fuchsia::mem::Buffer; | |
| bool Archive(const std::map<std::string, std::string>& files, Buffer* archive, zipFile* zf) { | |
| for (const auto& [filename, content] : files) { | |
| zip_fileinfo zf_info = {}; | |
| if (const int status = | |
| zipOpenNewFileInZip64(*zf, filename.c_str(), &zf_info, nullptr, 0, nullptr, 0, nullptr, | |
| Z_DEFLATED, Z_DEFAULT_COMPRESSION, /*zip64=*/1); | |
| status != ZIP_OK) { | |
| FX_LOGS(ERROR) << fxl::Substitute("cannot create $0 in output zip archive: ", filename) | |
| << status; | |
| return false; | |
| } | |
| if (const int status = zipWriteInFileInZip(*zf, content.data(), content.size()); | |
| status != ZIP_OK) { | |
| FX_LOGS(ERROR) << fxl::Substitute("cannot write $0 in output zip archive: ", filename) | |
| << status; | |
| return false; | |
| } | |
| if (const int status = zipCloseFileInZip(*zf); status != ZIP_OK) { | |
| FX_LOGS(WARNING) << fxl::Substitute("cannot close $0 in output zip archive: ", filename) | |
| << status; | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| bool Archive(const std::map<std::string, std::string>& files, Buffer* archive) { | |
| // We write the archive to a temporary file because in-memory archiving in minizip is complicated. | |
| files::ScopedTempDir tmp_dir; | |
| std::string archive_filename; | |
| tmp_dir.NewTempFile(&archive_filename); | |
| zipFile zf = zipOpen64(archive_filename.c_str(), APPEND_STATUS_CREATE); | |
| if (zf == nullptr) { | |
| FX_LOGS(ERROR) << "cannot create output zip archive"; | |
| return false; | |
| } | |
| const bool success = Archive(files, archive, &zf); | |
| // We always close the archive regardless of the success status. | |
| if (const int status = zipClose(zf, nullptr); status != ZIP_OK) { | |
| FX_LOGS(WARNING) << "cannot close output zip archive: " << status; | |
| } | |
| if (!success) { | |
| return false; | |
| } | |
| fsl::SizedVmo vmo; | |
| if (!fsl::VmoFromFilename(archive_filename, &vmo)) { | |
| FX_LOGS(ERROR) << "error loading output zip archive into VMO"; | |
| return false; | |
| } | |
| *archive = std::move(vmo).ToTransport(); | |
| return true; | |
| } | |
| namespace { | |
| bool Unpack(unzFile* uf, std::map<std::string, std::string>* files) { | |
| unz_global_info archive_info; | |
| if (const int status = unzGetGlobalInfo(*uf, &archive_info); status != UNZ_OK) { | |
| FX_LOGS(ERROR) << "cannot read input zip archive info: " << status; | |
| return false; | |
| } | |
| const auto num_files = archive_info.number_entry; | |
| if (num_files <= 0) { | |
| FX_LOGS(ERROR) << "input zip archive contains no files"; | |
| return false; | |
| } | |
| for (uint64_t current_file_index = 1; current_file_index <= num_files; ++current_file_index) { | |
| unz_file_info64 file_info; | |
| char filename[256]; | |
| if (const int status = unzGetCurrentFileInfo64(*uf, &file_info, filename, sizeof(filename), | |
| nullptr, 0, nullptr, 0); | |
| status != UNZ_OK) { | |
| FX_LOGS(ERROR) << "cannot read current file info in input zip archive: " << status; | |
| return false; | |
| } | |
| const std::string filename_str = std::string(filename); | |
| if (int status = unzOpenCurrentFile(*uf); status != UNZ_OK) { | |
| FX_LOGS(ERROR) << fxl::Substitute("cannot open $0 in input zip archive: ", filename_str) | |
| << status; | |
| return false; | |
| } | |
| std::vector<uint8_t> data; | |
| data.reserve(static_cast<size_t>(file_info.uncompressed_size)); | |
| const size_t kBufferSize = 512; | |
| std::vector<uint8_t> buffer(kBufferSize); | |
| int num_bytes_or_status; | |
| do { | |
| num_bytes_or_status = unzReadCurrentFile(*uf, buffer.data(), kBufferSize); | |
| if (num_bytes_or_status < 0) { | |
| FX_LOGS(ERROR) << fxl::Substitute("cannot read $0 in input zip archive: ", filename_str) | |
| << num_bytes_or_status; | |
| return false; | |
| } else if (num_bytes_or_status > 0) { | |
| data.insert(data.end(), buffer.data(), buffer.data() + num_bytes_or_status); | |
| } // num_bytes_or_status == 0 means EOF | |
| } while (num_bytes_or_status > 0); | |
| if (const int status = unzCloseCurrentFile(*uf); status != UNZ_OK) { | |
| FX_LOGS(WARNING) << fxl::Substitute("cannot close $0 in input zip archive: ", filename_str) | |
| << status; | |
| } | |
| files->insert({std::string(filename_str), std::string(data.begin(), data.end())}); | |
| if (current_file_index == num_files) { // last file, bail out. | |
| break; | |
| } | |
| if (const int status = unzGoToNextFile(*uf); status != UNZ_OK) { | |
| FX_LOGS(ERROR) << "cannot read next file in input zip archive: " << status; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| bool Unpack(const Buffer& archive, std::map<std::string, std::string>* files) { | |
| // We write the archive to a temporary file because minizip doesn't support unpacking an in-memory | |
| // archive. | |
| files::ScopedTempDir tmp_dir; | |
| std::string archive_filename; | |
| tmp_dir.NewTempFile(&archive_filename); | |
| auto data = std::make_unique<uint8_t[]>(archive.size); | |
| if (const zx_status_t status = archive.vmo.read(data.get(), 0u, archive.size); status != ZX_OK) { | |
| FX_PLOGS(ERROR, status) << "failed to read input zip archive VMO"; | |
| return false; | |
| } | |
| if (!files::WriteFile(archive_filename, reinterpret_cast<const char*>(data.get()), | |
| archive.size)) { | |
| FX_LOGS(ERROR) << "failed to write input zip archive VMO to temporary file"; | |
| return false; | |
| } | |
| unzFile uf = unzOpen64(archive_filename.c_str()); | |
| if (uf == nullptr) { | |
| FX_LOGS(ERROR) << "cannot open input zip archive at " << archive_filename; | |
| return false; | |
| } | |
| const bool success = Unpack(&uf, files); | |
| // We always close the archive regardless of the success status. | |
| if (const int status = unzClose(uf); status != ZIP_OK) { | |
| FX_LOGS(WARNING) << "cannot close input zip archive: " << status; | |
| } | |
| return success; | |
| } | |
| } // namespace forensics |