blob: c5ffdfe1f94f9736dc20c3b61bfbcec4fe12cb8d [file] [log] [blame]
// 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/feedback/utils/archive.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 "src/lib/syslog/cpp/logger.h"
#include "third_party/zlib/contrib/minizip/unzip.h"
#include "third_party/zlib/contrib/minizip/zip.h"
namespace feedback {
namespace {
using fuchsia::feedback::Attachment;
using fuchsia::mem::Buffer;
bool Archive(const std::vector<Attachment>& attachments, Buffer* archive, zipFile* zf) {
for (const auto& attachment : attachments) {
const std::string& filename = attachment.key;
zip_fileinfo zf_info = {};
if (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;
}
std::vector<uint8_t> content;
if (!fsl::VectorFromVmo(attachment.value, &content)) {
FX_LOGS(ERROR) << "failed to read VMO for " << filename;
return false;
}
if (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 (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::vector<Attachment>& attachments, 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;
}
bool success = Archive(attachments, archive, &zf);
// We always close the archive regardless of the success status.
if (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(const Buffer& archive, std::vector<Attachment>* attachments, unzFile* uf) {
unz_global_info archive_info;
if (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 (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 (int status = unzCloseCurrentFile(*uf); status != UNZ_OK) {
FX_LOGS(WARNING) << fxl::Substitute("cannot close $0 in input zip archive: ", filename_str)
<< status;
}
fsl::SizedVmo sized_vmo;
if (!fsl::VmoFromVector(data, &sized_vmo)) {
FX_LOGS(ERROR) << "cannot write output VMO for " << filename_str;
return false;
}
Attachment attachment;
attachment.key = std::move(filename_str);
attachment.value = std::move(sized_vmo).ToTransport();
attachments->push_back(std::move(attachment));
if (current_file_index == num_files) { // last file, bail out.
break;
}
if (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::vector<Attachment>* attachments) {
// 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 (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;
}
bool success = Unpack(archive, attachments, &uf);
// We always close the archive regardless of the success status.
if (int status = unzClose(uf); status != ZIP_OK) {
FX_LOGS(WARNING) << "cannot close input zip archive: " << status;
}
return success;
}
} // namespace feedback