blob: 64c837f00884736fc143161c2fa00a3542e185d7 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// 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 "cobalt/updater/unzipper.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "starboard/event.h"
#include "starboard/memory.h"
#include "starboard/system.h"
#include "starboard/time.h"
#include "third_party/lz4_lib/lz4frame.h"
#include "third_party/zlib/google/zip.h"
#include "third_party/zlib/google/zip_reader.h"
namespace cobalt {
namespace updater {
namespace {
constexpr size_t kChunkSize = 8 * 1024;
constexpr LZ4F_preferences_t kPrefs = {
{LZ4F_max256KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame,
0 /* unknown content size */, 0 /* no dictID */, LZ4F_noBlockChecksum},
0,
/* compression level; 0 == default */ 0,
/* autoflush */ 0,
/* favor decompression speed */ {0, 0, 0},
/* reserved, must be set to 0 */};
class LZ4WriterDelegate : public zip::WriterDelegate {
public:
explicit LZ4WriterDelegate(const base::FilePath& output_file_path)
: output_file_path_(output_file_path) {}
bool PrepareOutput() override {
if (!base::CreateDirectory(output_file_path_.DirName())) {
return false;
}
output_file_path_ = output_file_path_.ReplaceExtension("lz4");
LOG(INFO) << "LZ4WriterDelegate::PrepareOutput: output_file_path_="
<< output_file_path_.value();
file_.Initialize(output_file_path_,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file_.IsValid()) {
LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: invalid file"
<< output_file_path_.value();
return false;
}
LZ4F_errorCode_t res = LZ4F_createCompressionContext(&ctx_, LZ4F_VERSION);
if (LZ4F_isError(res)) {
LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: lz4 error code= " << res;
return false;
}
out_capacity_ = LZ4F_compressBound(kChunkSize, &kPrefs);
LOG(INFO) << "out_capacity=" << out_capacity_;
out_buffer_.resize(out_capacity_);
if (!WriteHeader()) {
LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: Failed to write header";
return false;
}
return true;
}
bool WriteBytes(const char* data, int num_bytes) override {
LOG(INFO) << "LZ4WriterDelegate::WriteBytes: num_bytes=" << num_bytes
<< " out_capacity=" << out_capacity_;
size_t compressed_size = LZ4F_compressUpdate(
ctx_, out_buffer_.data(), out_buffer_.size(), data, num_bytes, nullptr);
if (LZ4F_isError(compressed_size)) {
LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: Compression failed: error "
<< (unsigned)compressed_size;
return false;
}
LOG(INFO) << "LZ4WriterDelegate::WriteBytes: Writing "
<< (unsigned)compressed_size << " bytes "
<< "(if zero, input data just buffered to LZ4 internal buffer)";
if (compressed_size !=
file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: failed to write "
<< compressed_size << " bytes";
return false;
}
return true;
}
void SetTimeModified(const base::Time& time) override {
size_t compressed_size =
LZ4F_compressEnd(ctx_, out_buffer_.data(), out_buffer_.size(), nullptr);
if (LZ4F_isError(compressed_size)) {
LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: Failed to end "
"compression: error "
<< (unsigned)compressed_size;
return;
}
LOG(INFO) << "Writing " << (unsigned)compressed_size << " bytes";
if (compressed_size !=
file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: failed to write "
<< compressed_size << " bytes";
return;
}
success_ = true;
}
bool IsSuccessful() override { return success_; }
~LZ4WriterDelegate() {
if (ctx_) {
LZ4F_freeCompressionContext(ctx_);
}
file_.Close();
}
private:
bool WriteHeader() {
size_t header_size = LZ4F_compressBegin(ctx_, out_buffer_.data(),
out_buffer_.size(), &kPrefs);
if (LZ4F_isError(header_size)) {
LOG(ERROR) << "Failed to start compression: error "
<< (unsigned)header_size;
return false;
}
LOG(INFO) << "Header size " << (unsigned)header_size;
return header_size ==
file_.WriteAtCurrentPos(out_buffer_.data(), header_size);
}
base::FilePath output_file_path_;
base::File file_;
LZ4F_compressionContext_t ctx_ = nullptr;
std::vector<char> out_buffer_;
size_t out_capacity_ = 64 * 1024;
bool success_ = false;
};
class UnzipperImpl : public update_client::Unzipper {
public:
explicit UnzipperImpl(bool updater_compression)
: compress_update_(updater_compression) {}
void Unzip(const base::FilePath& zip_path, const base::FilePath& output_path,
UnzipCompleteCallback callback) override {
if (compress_update_) {
UnzipAndLZ4Compress(zip_path, output_path, std::move(callback));
} else {
UnzipToPath(zip_path, output_path, std::move(callback));
}
}
void UnzipAndLZ4Compress(const base::FilePath& zip_path,
const base::FilePath& output_path,
UnzipCompleteCallback callback) {
SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
// Filter to unzip just libcobalt.so
auto filter = base::BindRepeating([](const base::FilePath& path) {
return (path.BaseName().value() == "libcobalt.so");
});
auto directory_creator = base::BindRepeating(
[](const base::FilePath& extract_dir,
const base::FilePath& entry_path) {
return base::CreateDirectory(extract_dir.Append(entry_path));
},
output_path);
auto writer_factory = base::BindRepeating(
[](const base::FilePath& extract_dir, const base::FilePath& entry_path)
-> std::unique_ptr<zip::WriterDelegate> {
return std::make_unique<LZ4WriterDelegate>(
extract_dir.Append(entry_path));
},
output_path);
if (!zip::UnzipWithFilterAndWriters(zip_path, writer_factory,
directory_creator, filter, true)) {
LOG(ERROR) << "Failed to unzip libcobalt.so";
std::move(callback).Run(false);
return;
} else {
LOG(INFO) << "Successfully unzipped and lz4 compressed libcobalt.so";
}
// Filter to unzip the rest of the CRX package.
filter = base::BindRepeating([](const base::FilePath& path) {
if (path.BaseName().value() == "libcobalt.so") {
return false;
}
return true;
});
if (!zip::UnzipWithFilterCallback(zip_path, output_path, filter, true)) {
LOG(ERROR) << "Failed to unzip the CRX package";
std::move(callback).Run(false);
return;
} else {
LOG(INFO) << "Successfully unzipped the CRX package";
}
std::move(callback).Run(true);
SbTimeMonotonic time_unzip_took =
SbTimeGetMonotonicNow() - time_before_unzip;
LOG(INFO) << "Unzip file path = " << zip_path;
LOG(INFO) << "output_path = " << output_path;
LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
<< " milliseconds.";
}
void UnzipToPath(const base::FilePath& zip_path,
const base::FilePath& output_path,
UnzipCompleteCallback callback) {
SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
std::move(callback).Run(zip::Unzip(zip_path, output_path));
SbTimeMonotonic time_unzip_took =
SbTimeGetMonotonicNow() - time_before_unzip;
LOG(INFO) << "Unzip file path = " << zip_path;
LOG(INFO) << "output_path = " << output_path;
LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
<< " milliseconds.";
}
private:
bool compress_update_ = false;
};
} // namespace
UnzipperFactory::UnzipperFactory(bool compress_update)
: compress_update_(compress_update) {}
std::unique_ptr<update_client::Unzipper> UnzipperFactory::Create() const {
return std::make_unique<UnzipperImpl>(compress_update_);
}
UnzipperFactory::~UnzipperFactory() = default;
} // namespace updater
} // namespace cobalt