| // Copyright 2017 The Chromium OS 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 <algorithm> |
| #include <fstream> |
| #include <iostream> |
| #include <sstream> |
| |
| #ifdef USE_BRILLO |
| #include "brillo/flag_helper.h" |
| #else |
| #include "gflags/gflags.h" |
| #endif |
| |
| #include "puffin/src/extent_stream.h" |
| #include "puffin/src/file_stream.h" |
| #include "puffin/src/include/puffin/common.h" |
| #include "puffin/src/include/puffin/huffer.h" |
| #include "puffin/src/include/puffin/puffdiff.h" |
| #include "puffin/src/include/puffin/puffer.h" |
| #include "puffin/src/include/puffin/puffpatch.h" |
| #include "puffin/src/include/puffin/utils.h" |
| #include "puffin/src/logging.h" |
| #include "puffin/src/memory_stream.h" |
| #include "puffin/src/puffin_stream.h" |
| |
| using puffin::BitExtent; |
| using puffin::Buffer; |
| using puffin::ByteExtent; |
| using puffin::ExtentStream; |
| using puffin::FileStream; |
| using puffin::Huffer; |
| using puffin::MemoryStream; |
| using puffin::Puffer; |
| using puffin::PuffinStream; |
| using puffin::UniqueStreamPtr; |
| using std::string; |
| using std::stringstream; |
| using std::vector; |
| |
| namespace { |
| |
| constexpr char kExtentDelimeter = ','; |
| constexpr char kOffsetLengthDelimeter = ':'; |
| |
| template <typename T> |
| vector<T> StringToExtents(const string& str) { |
| vector<T> extents; |
| if (!str.empty()) { |
| stringstream ss(str); |
| string extent_str; |
| while (getline(ss, extent_str, kExtentDelimeter)) { |
| stringstream extent_ss(extent_str); |
| string offset_str, length_str; |
| getline(extent_ss, offset_str, kOffsetLengthDelimeter); |
| getline(extent_ss, length_str, kOffsetLengthDelimeter); |
| extents.emplace_back(stoull(offset_str), stoull(length_str)); |
| } |
| } |
| return extents; |
| } |
| |
| const uint64_t kDefaultPuffCacheSize = 50 * 1024 * 1024; // 50 MB |
| |
| // An enum representing the type of compressed files. |
| enum class FileType { kDeflate, kZlib, kGzip, kZip, kRaw, kUnknown }; |
| |
| // Returns a file type based on the input string |file_type| (normally the final |
| // extension of the file). |
| FileType StringToFileType(const string& file_type) { |
| if (file_type == "raw") { |
| return FileType::kRaw; |
| } |
| if (file_type == "deflate") { |
| return FileType::kDeflate; |
| } else if (file_type == "zlib") { |
| return FileType::kZlib; |
| } else if (file_type == "gzip" || file_type == "gz" || file_type == "tgz") { |
| return FileType::kGzip; |
| } else if (file_type == "zip" || file_type == "apk" || file_type == "jar") { |
| return FileType::kZip; |
| } |
| return FileType::kUnknown; |
| } |
| |
| // Finds the location of deflates in |stream|. If |file_type_to_override| is |
| // non-empty, it infers the file type based on that, otherwise, it infers the |
| // file type based on the final extension of |file_name|. It returns false if |
| // file type cannot be inferred from any of the input arguments. |deflates| |
| // is filled with byte-aligned location of deflates. |
| bool LocateDeflatesBasedOnFileType(const UniqueStreamPtr& stream, |
| const string& file_name, |
| const string& file_type_to_override, |
| vector<BitExtent>* deflates) { |
| auto file_type = FileType::kUnknown; |
| |
| auto last_dot = file_name.find_last_of("."); |
| if (last_dot == string::npos) { |
| // Could not find a dot so we assume there is no extension. |
| return false; |
| } |
| auto extension = file_name.substr(last_dot + 1); |
| file_type = StringToFileType(extension); |
| |
| if (!file_type_to_override.empty()) { |
| auto override_file_type = StringToFileType(file_type_to_override); |
| if (override_file_type == FileType::kUnknown) { |
| LOG(ERROR) << "Overriden file type " << file_type_to_override |
| << " does not exist."; |
| return false; |
| } |
| if (file_type != FileType::kUnknown && file_type != override_file_type) { |
| LOG(WARNING) << "Based on the file name, the file type is " << extension |
| << ", But the overriden file type is " |
| << file_type_to_override << ". Is this intentional?"; |
| } |
| file_type = override_file_type; |
| } |
| |
| if (file_type == FileType::kRaw) { |
| // Do not need to populate |deflates|. |
| return true; |
| } |
| |
| uint64_t stream_size; |
| TEST_AND_RETURN_FALSE(stream->GetSize(&stream_size)); |
| Buffer data(stream_size); |
| TEST_AND_RETURN_FALSE(stream->Read(data.data(), data.size())); |
| switch (file_type) { |
| case FileType::kDeflate: |
| TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInDeflateStream( |
| data.data(), data.size(), 0, deflates, nullptr)); |
| break; |
| case FileType::kZlib: |
| TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInZlib(data, deflates)); |
| break; |
| case FileType::kGzip: |
| TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInGzip(data, deflates)); |
| break; |
| case FileType::kZip: |
| TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInZipArchive(data, deflates)); |
| break; |
| default: |
| LOG(ERROR) << "Unknown file type: (" << file_type_to_override << ") nor (" |
| << extension << ")."; |
| return false; |
| } |
| // Return the stream to its zero offset in case we used it. |
| TEST_AND_RETURN_FALSE(stream->Seek(0)); |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| #define SETUP_FLAGS \ |
| DEFINE_string(src_file, "", "Source file"); \ |
| DEFINE_string(dst_file, "", "Target file"); \ |
| DEFINE_string(patch_file, "", "patch file"); \ |
| DEFINE_string( \ |
| src_deflates_byte, "", \ |
| "Source deflate byte locations in the format offset:length,..."); \ |
| DEFINE_string( \ |
| dst_deflates_byte, "", \ |
| "Target deflate byte locations in the format offset:length,..."); \ |
| DEFINE_string( \ |
| src_deflates_bit, "", \ |
| "Source deflate bit locations in the format offset:length,..."); \ |
| DEFINE_string( \ |
| dst_deflates_bit, "", \ |
| "Target deflatebit locations in the format offset:length,..."); \ |
| DEFINE_string(src_puffs, "", \ |
| "Source puff locations in the format offset:length,..."); \ |
| DEFINE_string(dst_puffs, "", \ |
| "Target puff locations in the format offset:length,..."); \ |
| DEFINE_string(src_extents, "", \ |
| "Source extents in the format of offset:length,..."); \ |
| DEFINE_string(dst_extents, "", \ |
| "Target extents in the format of offset:length,..."); \ |
| DEFINE_string(operation, "", \ |
| "Type of the operation: puff, huff, puffdiff, puffpatch, " \ |
| "puffhuff"); \ |
| DEFINE_string(src_file_type, "", \ |
| "Type of the input source file: deflate, gzip, " \ |
| "zlib or zip"); \ |
| DEFINE_string(dst_file_type, "", \ |
| "Same as src_file_type but for the target file"); \ |
| DEFINE_bool(verbose, false, \ |
| "Logs all the given parameters including internally " \ |
| "generated ones"); \ |
| DEFINE_uint64(cache_size, kDefaultPuffCacheSize, \ |
| "Maximum size to cache the puff stream. Used in puffpatch"); |
| |
| #ifndef USE_BRILLO |
| SETUP_FLAGS; |
| #endif |
| |
| // Main entry point to the application. |
| bool Main(int argc, char** argv) { |
| #ifdef USE_BRILLO |
| SETUP_FLAGS; |
| brillo::FlagHelper::Init(argc, argv, "Puffin tool"); |
| #else |
| // google::InitGoogleLogging(argv[0]); |
| google::ParseCommandLineFlags(&argc, &argv, true); |
| #endif |
| |
| TEST_AND_RETURN_FALSE(!FLAGS_operation.empty()); |
| TEST_AND_RETURN_FALSE(!FLAGS_src_file.empty()); |
| TEST_AND_RETURN_FALSE(!FLAGS_dst_file.empty()); |
| |
| auto src_deflates_byte = StringToExtents<ByteExtent>(FLAGS_src_deflates_byte); |
| auto dst_deflates_byte = StringToExtents<ByteExtent>(FLAGS_dst_deflates_byte); |
| auto src_deflates_bit = StringToExtents<BitExtent>(FLAGS_src_deflates_bit); |
| auto dst_deflates_bit = StringToExtents<BitExtent>(FLAGS_dst_deflates_bit); |
| auto src_puffs = StringToExtents<ByteExtent>(FLAGS_src_puffs); |
| auto dst_puffs = StringToExtents<ByteExtent>(FLAGS_dst_puffs); |
| auto src_extents = StringToExtents<ByteExtent>(FLAGS_src_extents); |
| auto dst_extents = StringToExtents<ByteExtent>(FLAGS_dst_extents); |
| |
| auto src_stream = FileStream::Open(FLAGS_src_file, true, false); |
| TEST_AND_RETURN_FALSE(src_stream); |
| if (!src_extents.empty()) { |
| src_stream = |
| ExtentStream::CreateForRead(std::move(src_stream), src_extents); |
| TEST_AND_RETURN_FALSE(src_stream); |
| } |
| |
| if (FLAGS_operation == "puff" || FLAGS_operation == "puffhuff") { |
| TEST_AND_RETURN_FALSE(LocateDeflatesBasedOnFileType( |
| src_stream, FLAGS_src_file, FLAGS_src_file_type, &src_deflates_bit)); |
| |
| if (src_deflates_bit.empty() && src_deflates_byte.empty()) { |
| LOG(WARNING) << "You should pass source deflates, is this intentional?"; |
| } |
| if (src_deflates_bit.empty()) { |
| TEST_AND_RETURN_FALSE(FindDeflateSubBlocks(src_stream, src_deflates_byte, |
| &src_deflates_bit)); |
| } |
| TEST_AND_RETURN_FALSE(dst_puffs.empty()); |
| uint64_t dst_puff_size; |
| TEST_AND_RETURN_FALSE(FindPuffLocations(src_stream, src_deflates_bit, |
| &dst_puffs, &dst_puff_size)); |
| |
| auto dst_stream = FileStream::Open(FLAGS_dst_file, false, true); |
| TEST_AND_RETURN_FALSE(dst_stream); |
| auto puffer = std::make_shared<Puffer>(); |
| auto reader = |
| PuffinStream::CreateForPuff(std::move(src_stream), puffer, |
| dst_puff_size, src_deflates_bit, dst_puffs); |
| |
| Buffer puff_buffer; |
| auto writer = FLAGS_operation == "puffhuff" |
| ? MemoryStream::CreateForWrite(&puff_buffer) |
| : std::move(dst_stream); |
| |
| Buffer buffer(1024 * 1024); |
| uint64_t bytes_wrote = 0; |
| while (bytes_wrote < dst_puff_size) { |
| auto write_size = std::min(static_cast<uint64_t>(buffer.size()), |
| dst_puff_size - bytes_wrote); |
| TEST_AND_RETURN_FALSE(reader->Read(buffer.data(), write_size)); |
| TEST_AND_RETURN_FALSE(writer->Write(buffer.data(), write_size)); |
| bytes_wrote += write_size; |
| } |
| |
| // puffhuff operation puffs a stream and huffs it back to the target stream |
| // to make sure we can get to the original stream. |
| if (FLAGS_operation == "puffhuff") { |
| src_puffs = dst_puffs; |
| dst_deflates_byte = src_deflates_byte; |
| dst_deflates_bit = src_deflates_bit; |
| |
| auto read_puff_stream = MemoryStream::CreateForRead(puff_buffer); |
| auto huffer = std::make_shared<Huffer>(); |
| auto huff_writer = PuffinStream::CreateForHuff( |
| std::move(dst_stream), huffer, dst_puff_size, dst_deflates_bit, |
| src_puffs); |
| |
| uint64_t bytes_read = 0; |
| while (bytes_read < dst_puff_size) { |
| auto read_size = std::min(static_cast<uint64_t>(buffer.size()), |
| dst_puff_size - bytes_read); |
| TEST_AND_RETURN_FALSE(read_puff_stream->Read(buffer.data(), read_size)); |
| TEST_AND_RETURN_FALSE(huff_writer->Write(buffer.data(), read_size)); |
| bytes_read += read_size; |
| } |
| } |
| } else if (FLAGS_operation == "huff") { |
| if (dst_deflates_bit.empty() && src_puffs.empty()) { |
| LOG(WARNING) << "You should pass source puffs and destination deflates" |
| << ", is this intentional?"; |
| } |
| TEST_AND_RETURN_FALSE(src_puffs.size() == dst_deflates_bit.size()); |
| uint64_t src_stream_size; |
| TEST_AND_RETURN_FALSE(src_stream->GetSize(&src_stream_size)); |
| auto dst_file = FileStream::Open(FLAGS_dst_file, false, true); |
| TEST_AND_RETURN_FALSE(dst_file); |
| |
| auto huffer = std::make_shared<Huffer>(); |
| auto dst_stream = PuffinStream::CreateForHuff(std::move(dst_file), huffer, |
| src_stream_size, |
| dst_deflates_bit, src_puffs); |
| |
| Buffer buffer(1024 * 1024); |
| uint64_t bytes_read = 0; |
| while (bytes_read < src_stream_size) { |
| auto read_size = std::min(static_cast<uint64_t>(buffer.size()), |
| src_stream_size - bytes_read); |
| TEST_AND_RETURN_FALSE(src_stream->Read(buffer.data(), read_size)); |
| TEST_AND_RETURN_FALSE(dst_stream->Write(buffer.data(), read_size)); |
| bytes_read += read_size; |
| } |
| } else if (FLAGS_operation == "puffdiff") { |
| auto dst_stream = FileStream::Open(FLAGS_dst_file, true, false); |
| TEST_AND_RETURN_FALSE(dst_stream); |
| |
| TEST_AND_RETURN_FALSE(LocateDeflatesBasedOnFileType( |
| src_stream, FLAGS_src_file, FLAGS_src_file_type, &src_deflates_bit)); |
| TEST_AND_RETURN_FALSE(LocateDeflatesBasedOnFileType( |
| dst_stream, FLAGS_dst_file, FLAGS_dst_file_type, &dst_deflates_bit)); |
| |
| if (src_deflates_bit.empty() && src_deflates_byte.empty()) { |
| LOG(WARNING) << "You should pass source deflates, is this intentional?"; |
| } |
| if (dst_deflates_bit.empty() && dst_deflates_byte.empty()) { |
| LOG(WARNING) << "You should pass target deflates, is this intentional?"; |
| } |
| if (!dst_extents.empty()) { |
| dst_stream = |
| ExtentStream::CreateForWrite(std::move(dst_stream), dst_extents); |
| TEST_AND_RETURN_FALSE(dst_stream); |
| } |
| |
| if (src_deflates_bit.empty()) { |
| TEST_AND_RETURN_FALSE(FindDeflateSubBlocks(src_stream, src_deflates_byte, |
| &src_deflates_bit)); |
| } |
| |
| if (dst_deflates_bit.empty()) { |
| TEST_AND_RETURN_FALSE(FindDeflateSubBlocks(dst_stream, dst_deflates_byte, |
| &dst_deflates_bit)); |
| } |
| |
| // TODO(xunchang) add flags to select the bsdiff compressors. |
| Buffer puffdiff_delta; |
| TEST_AND_RETURN_FALSE(puffin::PuffDiff( |
| std::move(src_stream), std::move(dst_stream), src_deflates_bit, |
| dst_deflates_bit, |
| {bsdiff::CompressorType::kBZ2, bsdiff::CompressorType::kBrotli}, |
| "/tmp/patch.tmp", &puffdiff_delta)); |
| if (FLAGS_verbose) { |
| LOG(INFO) << "patch_size: " << puffdiff_delta.size(); |
| } |
| auto patch_stream = FileStream::Open(FLAGS_patch_file, false, true); |
| TEST_AND_RETURN_FALSE(patch_stream); |
| TEST_AND_RETURN_FALSE( |
| patch_stream->Write(puffdiff_delta.data(), puffdiff_delta.size())); |
| } else if (FLAGS_operation == "puffpatch") { |
| auto patch_stream = FileStream::Open(FLAGS_patch_file, true, false); |
| TEST_AND_RETURN_FALSE(patch_stream); |
| uint64_t patch_size; |
| TEST_AND_RETURN_FALSE(patch_stream->GetSize(&patch_size)); |
| |
| Buffer puffdiff_delta(patch_size); |
| TEST_AND_RETURN_FALSE( |
| patch_stream->Read(puffdiff_delta.data(), puffdiff_delta.size())); |
| auto dst_stream = FileStream::Open(FLAGS_dst_file, false, true); |
| TEST_AND_RETURN_FALSE(dst_stream); |
| if (!dst_extents.empty()) { |
| dst_stream = |
| ExtentStream::CreateForWrite(std::move(dst_stream), dst_extents); |
| TEST_AND_RETURN_FALSE(dst_stream); |
| } |
| // Apply the patch. Use 50MB cache, it should be enough for most of the |
| // operations. |
| TEST_AND_RETURN_FALSE(puffin::PuffPatch( |
| std::move(src_stream), std::move(dst_stream), puffdiff_delta.data(), |
| puffdiff_delta.size(), FLAGS_cache_size)); |
| } |
| |
| if (FLAGS_verbose) { |
| LOG(INFO) << "src_deflates_byte: " |
| << puffin::ExtentsToString(src_deflates_byte); |
| LOG(INFO) << "dst_deflates_byte: " |
| << puffin::ExtentsToString(dst_deflates_byte); |
| LOG(INFO) << "src_deflates_bit: " |
| << puffin::ExtentsToString(src_deflates_bit); |
| LOG(INFO) << "dst_deflates_bit: " |
| << puffin::ExtentsToString(dst_deflates_bit); |
| LOG(INFO) << "src_puffs: " << puffin::ExtentsToString(src_puffs); |
| LOG(INFO) << "dst_puffs: " << puffin::ExtentsToString(dst_puffs); |
| LOG(INFO) << "src_extents: " << puffin::ExtentsToString(src_extents); |
| LOG(INFO) << "dst_extents: " << puffin::ExtentsToString(dst_extents); |
| } |
| return true; |
| } |
| |
| int main(int argc, char** argv) { |
| if (!Main(argc, argv)) { |
| return 1; |
| } |
| return 0; |
| } |