Home | History | Annotate | Download | only in src
      1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <algorithm>
      6 #include <fstream>
      7 #include <iostream>
      8 #include <sstream>
      9 
     10 #ifdef USE_BRILLO
     11 #include "brillo/flag_helper.h"
     12 #else
     13 #include "gflags/gflags.h"
     14 #endif
     15 
     16 #include "puffin/src/extent_stream.h"
     17 #include "puffin/src/file_stream.h"
     18 #include "puffin/src/include/puffin/common.h"
     19 #include "puffin/src/include/puffin/huffer.h"
     20 #include "puffin/src/include/puffin/puffdiff.h"
     21 #include "puffin/src/include/puffin/puffer.h"
     22 #include "puffin/src/include/puffin/puffpatch.h"
     23 #include "puffin/src/include/puffin/utils.h"
     24 #include "puffin/src/memory_stream.h"
     25 #include "puffin/src/puffin_stream.h"
     26 #include "puffin/src/set_errors.h"
     27 
     28 using puffin::BitExtent;
     29 using puffin::Buffer;
     30 using puffin::ByteExtent;
     31 using puffin::Error;
     32 using puffin::ExtentStream;
     33 using puffin::FileStream;
     34 using puffin::Huffer;
     35 using puffin::MemoryStream;
     36 using puffin::Puffer;
     37 using puffin::PuffinStream;
     38 using puffin::UniqueStreamPtr;
     39 using std::string;
     40 using std::vector;
     41 
     42 namespace {
     43 
     44 constexpr char kExtentDelimeter = ',';
     45 constexpr char kOffsetLengthDelimeter = ':';
     46 
     47 template <typename T>
     48 vector<T> StringToExtents(const string& str) {
     49   vector<T> extents;
     50   if (!str.empty()) {
     51     std::stringstream ss(str);
     52     string extent_str;
     53     while (getline(ss, extent_str, kExtentDelimeter)) {
     54       std::stringstream extent_ss(extent_str);
     55       string offset_str, length_str;
     56       getline(extent_ss, offset_str, kOffsetLengthDelimeter);
     57       getline(extent_ss, length_str, kOffsetLengthDelimeter);
     58       extents.emplace_back(stoull(offset_str), stoull(length_str));
     59     }
     60   }
     61   return extents;
     62 }
     63 
     64 const uint64_t kDefaultPuffCacheSize = 50 * 1024 * 1024;  // 50 MB
     65 
     66 // An enum representing the type of compressed files.
     67 enum class FileType { kDeflate, kZlib, kGzip, kZip, kRaw, kUnknown };
     68 
     69 // Returns a file type based on the input string |file_type| (normally the final
     70 // extension of the file).
     71 FileType StringToFileType(const string& file_type) {
     72   if (file_type == "raw") {
     73     return FileType::kRaw;
     74   }
     75   if (file_type == "deflate") {
     76     return FileType::kDeflate;
     77   } else if (file_type == "zlib") {
     78     return FileType::kZlib;
     79   } else if (file_type == "gzip" || file_type == "gz" || file_type == "tgz") {
     80     return FileType::kGzip;
     81   } else if (file_type == "zip" || file_type == "apk" || file_type == "jar") {
     82     return FileType::kZip;
     83   }
     84   return FileType::kUnknown;
     85 }
     86 
     87 // Finds the location of deflates in |stream|. If |file_type_to_override| is
     88 // non-empty, it infers the file type based on that, otherwise, it infers the
     89 // file type based on the final extension of |file_name|. It returns false if
     90 // file type cannot be inferred from any of the input arguments. |deflates|
     91 // is filled with byte-aligned location of deflates.
     92 bool LocateDeflatesBasedOnFileType(const UniqueStreamPtr& stream,
     93                                    const string& file_name,
     94                                    const string& file_type_to_override,
     95                                    vector<ByteExtent>* deflates) {
     96   auto file_type = FileType::kUnknown;
     97 
     98   auto last_dot = file_name.find_last_of(".");
     99   if (last_dot == string::npos) {
    100     // Could not find a dot so we assume there is no extension.
    101     return false;
    102   }
    103   auto extension = file_name.substr(last_dot + 1);
    104   file_type = StringToFileType(extension);
    105 
    106   if (!file_type_to_override.empty()) {
    107     auto override_file_type = StringToFileType(file_type_to_override);
    108     if (override_file_type == FileType::kUnknown) {
    109       LOG(ERROR) << "Overriden file type " << file_type_to_override
    110                  << " does not exist.";
    111       return false;
    112     }
    113     if (file_type != FileType::kUnknown && file_type != override_file_type) {
    114       LOG(WARNING) << "Based on the file name, the file type is " << extension
    115                    << ", But the overriden file type is "
    116                    << file_type_to_override << ". Is this intentional?";
    117     }
    118     file_type = override_file_type;
    119   }
    120 
    121   if (file_type == FileType::kRaw) {
    122     // Do not need to populate |deflates|.
    123     return true;
    124   }
    125 
    126   uint64_t stream_size;
    127   TEST_AND_RETURN_FALSE(stream->GetSize(&stream_size));
    128   if (file_type == FileType::kDeflate) {
    129     // Assume the whole stream is a deflate block.
    130     *deflates = {ByteExtent(0, stream_size)};
    131     return true;
    132   }
    133 
    134   Buffer data(stream_size);
    135   TEST_AND_RETURN_FALSE(stream->Read(data.data(), data.size()));
    136   switch (file_type) {
    137     case FileType::kZlib:
    138       TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInZlib(data, deflates));
    139       break;
    140     case FileType::kGzip:
    141       TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInGzip(data, deflates));
    142       break;
    143     case FileType::kZip:
    144       TEST_AND_RETURN_FALSE(puffin::LocateDeflatesInZipArchive(data, deflates));
    145       break;
    146     default:
    147       LOG(ERROR) << "Unknown file type: (" << file_type_to_override << ") nor ("
    148                  << extension << ").";
    149       return false;
    150   }
    151   // Return the stream to its zero offset in case we used it.
    152   TEST_AND_RETURN_FALSE(stream->Seek(0));
    153 
    154   return true;
    155 }
    156 
    157 }  // namespace
    158 
    159 #define SETUP_FLAGS                                                        \
    160   DEFINE_string(src_file, "", "Source file");                              \
    161   DEFINE_string(dst_file, "", "Target file");                              \
    162   DEFINE_string(patch_file, "", "patch file");                             \
    163   DEFINE_string(                                                           \
    164       src_deflates_byte, "",                                               \
    165       "Source deflate byte locations in the format offset:length,...");    \
    166   DEFINE_string(                                                           \
    167       dst_deflates_byte, "",                                               \
    168       "Target deflate byte locations in the format offset:length,...");    \
    169   DEFINE_string(                                                           \
    170       src_deflates_bit, "",                                                \
    171       "Source deflate bit locations in the format offset:length,...");     \
    172   DEFINE_string(                                                           \
    173       dst_deflates_bit, "",                                                \
    174       "Target deflatebit locations in the format offset:length,...");      \
    175   DEFINE_string(src_puffs, "",                                             \
    176                 "Source puff locations in the format offset:length,...");  \
    177   DEFINE_string(dst_puffs, "",                                             \
    178                 "Target puff locations in the format offset:length,...");  \
    179   DEFINE_string(src_extents, "",                                           \
    180                 "Source extents in the format of offset:length,...");      \
    181   DEFINE_string(dst_extents, "",                                           \
    182                 "Target extents in the format of offset:length,...");      \
    183   DEFINE_string(operation, "",                                             \
    184                 "Type of the operation: puff, huff, puffdiff, puffpatch, " \
    185                 "puffhuff");                                               \
    186   DEFINE_string(src_file_type, "",                                         \
    187                 "Type of the input source file: deflate, gzip, "           \
    188                 "zlib or zip");                                            \
    189   DEFINE_string(dst_file_type, "",                                         \
    190                 "Same as src_file_type but for the target file");          \
    191   DEFINE_bool(verbose, false,                                              \
    192               "Logs all the given parameters including internally "        \
    193               "generated ones");                                           \
    194   DEFINE_uint64(cache_size, kDefaultPuffCacheSize,                         \
    195                 "Maximum size to cache the puff stream. Used in puffpatch");
    196 
    197 #ifndef USE_BRILLO
    198 SETUP_FLAGS;
    199 #endif
    200 
    201 // Main entry point to the application.
    202 int main(int argc, char** argv) {
    203 #ifdef USE_BRILLO
    204   SETUP_FLAGS;
    205   brillo::FlagHelper::Init(argc, argv, "Puffin tool");
    206 #else
    207   // google::InitGoogleLogging(argv[0]);
    208   google::ParseCommandLineFlags(&argc, &argv, true);
    209 #endif
    210 
    211   TEST_AND_RETURN_VALUE(!FLAGS_operation.empty(), -1);
    212   TEST_AND_RETURN_VALUE(!FLAGS_src_file.empty(), -1);
    213   TEST_AND_RETURN_VALUE(!FLAGS_dst_file.empty(), -1);
    214 
    215   auto src_deflates_byte = StringToExtents<ByteExtent>(FLAGS_src_deflates_byte);
    216   auto dst_deflates_byte = StringToExtents<ByteExtent>(FLAGS_dst_deflates_byte);
    217   auto src_deflates_bit = StringToExtents<BitExtent>(FLAGS_src_deflates_bit);
    218   auto dst_deflates_bit = StringToExtents<BitExtent>(FLAGS_dst_deflates_bit);
    219   auto src_puffs = StringToExtents<ByteExtent>(FLAGS_src_puffs);
    220   auto dst_puffs = StringToExtents<ByteExtent>(FLAGS_dst_puffs);
    221   auto src_extents = StringToExtents<ByteExtent>(FLAGS_src_extents);
    222   auto dst_extents = StringToExtents<ByteExtent>(FLAGS_dst_extents);
    223 
    224   auto src_stream = FileStream::Open(FLAGS_src_file, true, false);
    225   TEST_AND_RETURN_VALUE(src_stream, -1);
    226   if (!src_extents.empty()) {
    227     src_stream =
    228         ExtentStream::CreateForRead(std::move(src_stream), src_extents);
    229     TEST_AND_RETURN_VALUE(src_stream, -1);
    230   }
    231 
    232   if (FLAGS_operation == "puff" || FLAGS_operation == "puffhuff") {
    233     TEST_AND_RETURN_VALUE(
    234         LocateDeflatesBasedOnFileType(src_stream, FLAGS_src_file,
    235                                       FLAGS_src_file_type, &src_deflates_byte),
    236         -1);
    237 
    238     if (src_deflates_bit.empty() && src_deflates_byte.empty()) {
    239       LOG(WARNING) << "You should pass source deflates, is this intentional?";
    240     }
    241     if (src_deflates_bit.empty()) {
    242       TEST_AND_RETURN_VALUE(FindDeflateSubBlocks(src_stream, src_deflates_byte,
    243                                                  &src_deflates_bit),
    244                             -1);
    245     }
    246     TEST_AND_RETURN_VALUE(dst_puffs.empty(), -1);
    247     uint64_t dst_puff_size;
    248     TEST_AND_RETURN_VALUE(FindPuffLocations(src_stream, src_deflates_bit,
    249                                             &dst_puffs, &dst_puff_size),
    250                           -1);
    251 
    252     auto dst_stream = FileStream::Open(FLAGS_dst_file, false, true);
    253     TEST_AND_RETURN_VALUE(dst_stream, -1);
    254     auto puffer = std::make_shared<Puffer>();
    255     auto reader =
    256         PuffinStream::CreateForPuff(std::move(src_stream), puffer,
    257                                     dst_puff_size, src_deflates_bit, dst_puffs);
    258 
    259     Buffer puff_buffer;
    260     auto writer = FLAGS_operation == "puffhuff"
    261                       ? MemoryStream::CreateForWrite(&puff_buffer)
    262                       : std::move(dst_stream);
    263 
    264     Buffer buffer(1024 * 1024);
    265     uint64_t bytes_wrote = 0;
    266     while (bytes_wrote < dst_puff_size) {
    267       auto write_size = std::min(static_cast<uint64_t>(buffer.size()),
    268                                  dst_puff_size - bytes_wrote);
    269       TEST_AND_RETURN_VALUE(reader->Read(buffer.data(), write_size), -1);
    270       TEST_AND_RETURN_VALUE(writer->Write(buffer.data(), write_size), -1);
    271       bytes_wrote += write_size;
    272     }
    273 
    274     // puffhuff operation puffs a stream and huffs it back to the target stream
    275     // to make sure we can get to the original stream.
    276     if (FLAGS_operation == "puffhuff") {
    277       src_puffs = dst_puffs;
    278       dst_deflates_byte = src_deflates_byte;
    279       dst_deflates_bit = src_deflates_bit;
    280 
    281       auto read_puff_stream = MemoryStream::CreateForRead(puff_buffer);
    282       auto huffer = std::make_shared<Huffer>();
    283       auto huff_writer = PuffinStream::CreateForHuff(
    284           std::move(dst_stream), huffer, dst_puff_size, dst_deflates_bit,
    285           src_puffs);
    286 
    287       uint64_t bytes_read = 0;
    288       while (bytes_read < dst_puff_size) {
    289         auto read_size = std::min(static_cast<uint64_t>(buffer.size()),
    290                                   dst_puff_size - bytes_read);
    291         TEST_AND_RETURN_VALUE(read_puff_stream->Read(buffer.data(), read_size),
    292                               -1);
    293         TEST_AND_RETURN_VALUE(huff_writer->Write(buffer.data(), read_size), -1);
    294         bytes_read += read_size;
    295       }
    296     }
    297   } else if (FLAGS_operation == "huff") {
    298     if (dst_deflates_bit.empty() && src_puffs.empty()) {
    299       LOG(WARNING) << "You should pass source puffs and destination deflates"
    300                    << ", is this intentional?";
    301     }
    302     TEST_AND_RETURN_VALUE(src_puffs.size() == dst_deflates_bit.size(), -1);
    303     uint64_t src_stream_size;
    304     TEST_AND_RETURN_VALUE(src_stream->GetSize(&src_stream_size), -1);
    305     auto dst_file = FileStream::Open(FLAGS_dst_file, false, true);
    306     TEST_AND_RETURN_VALUE(dst_file, -1);
    307 
    308     auto huffer = std::make_shared<Huffer>();
    309     auto dst_stream = PuffinStream::CreateForHuff(std::move(dst_file), huffer,
    310                                                   src_stream_size,
    311                                                   dst_deflates_bit, src_puffs);
    312 
    313     Buffer buffer(1024 * 1024);
    314     uint64_t bytes_read = 0;
    315     while (bytes_read < src_stream_size) {
    316       auto read_size = std::min(static_cast<uint64_t>(buffer.size()),
    317                                 src_stream_size - bytes_read);
    318       TEST_AND_RETURN_VALUE(src_stream->Read(buffer.data(), read_size), -1);
    319       TEST_AND_RETURN_VALUE(dst_stream->Write(buffer.data(), read_size), -1);
    320       bytes_read += read_size;
    321     }
    322   } else if (FLAGS_operation == "puffdiff") {
    323     auto dst_stream = FileStream::Open(FLAGS_dst_file, true, false);
    324     TEST_AND_RETURN_VALUE(dst_stream, -1);
    325 
    326     TEST_AND_RETURN_VALUE(
    327         LocateDeflatesBasedOnFileType(src_stream, FLAGS_src_file,
    328                                       FLAGS_src_file_type, &src_deflates_byte),
    329         -1);
    330     TEST_AND_RETURN_VALUE(
    331         LocateDeflatesBasedOnFileType(dst_stream, FLAGS_dst_file,
    332                                       FLAGS_dst_file_type, &dst_deflates_byte),
    333         -1);
    334 
    335     if (src_deflates_bit.empty() && src_deflates_byte.empty()) {
    336       LOG(WARNING) << "You should pass source deflates, is this intentional?";
    337     }
    338     if (dst_deflates_bit.empty() && dst_deflates_byte.empty()) {
    339       LOG(WARNING) << "You should pass target deflates, is this intentional?";
    340     }
    341     if (!dst_extents.empty()) {
    342       dst_stream =
    343           ExtentStream::CreateForWrite(std::move(dst_stream), dst_extents);
    344       TEST_AND_RETURN_VALUE(dst_stream, -1);
    345     }
    346 
    347     if (src_deflates_bit.empty()) {
    348       TEST_AND_RETURN_VALUE(FindDeflateSubBlocks(src_stream, src_deflates_byte,
    349                                                  &src_deflates_bit),
    350                             -1);
    351     }
    352 
    353     if (dst_deflates_bit.empty()) {
    354       TEST_AND_RETURN_VALUE(FindDeflateSubBlocks(dst_stream, dst_deflates_byte,
    355                                                  &dst_deflates_bit),
    356                             -1);
    357     }
    358 
    359     Buffer puffdiff_delta;
    360     TEST_AND_RETURN_VALUE(
    361         puffin::PuffDiff(std::move(src_stream), std::move(dst_stream),
    362                          src_deflates_bit, dst_deflates_bit, "/tmp/patch.tmp",
    363                          &puffdiff_delta),
    364         -1);
    365     if (FLAGS_verbose) {
    366       LOG(INFO) << "patch_size: " << puffdiff_delta.size();
    367     }
    368     auto patch_stream = FileStream::Open(FLAGS_patch_file, false, true);
    369     TEST_AND_RETURN_VALUE(patch_stream, -1);
    370     TEST_AND_RETURN_VALUE(
    371         patch_stream->Write(puffdiff_delta.data(), puffdiff_delta.size()), -1);
    372   } else if (FLAGS_operation == "puffpatch") {
    373     auto patch_stream = FileStream::Open(FLAGS_patch_file, true, false);
    374     TEST_AND_RETURN_VALUE(patch_stream, -1);
    375     uint64_t patch_size;
    376     TEST_AND_RETURN_VALUE(patch_stream->GetSize(&patch_size), -1);
    377 
    378     Buffer puffdiff_delta(patch_size);
    379     TEST_AND_RETURN_VALUE(
    380         patch_stream->Read(puffdiff_delta.data(), puffdiff_delta.size()), -1);
    381     auto dst_stream = FileStream::Open(FLAGS_dst_file, false, true);
    382     TEST_AND_RETURN_VALUE(dst_stream, -1);
    383     if (!dst_extents.empty()) {
    384       dst_stream =
    385           ExtentStream::CreateForWrite(std::move(dst_stream), dst_extents);
    386       TEST_AND_RETURN_VALUE(dst_stream, -1);
    387     }
    388     // Apply the patch. Use 50MB cache, it should be enough for most of the
    389     // operations.
    390     TEST_AND_RETURN_VALUE(
    391         puffin::PuffPatch(std::move(src_stream), std::move(dst_stream),
    392                           puffdiff_delta.data(), puffdiff_delta.size(),
    393                           FLAGS_cache_size),  // max_cache_size
    394         -1);
    395   }
    396 
    397   if (FLAGS_verbose) {
    398     LOG(INFO) << "src_deflates_byte: "
    399               << puffin::ExtentsToString(src_deflates_byte);
    400     LOG(INFO) << "dst_deflates_byte: "
    401               << puffin::ExtentsToString(dst_deflates_byte);
    402     LOG(INFO) << "src_deflates_bit: "
    403               << puffin::ExtentsToString(src_deflates_bit);
    404     LOG(INFO) << "dst_deflates_bit: "
    405               << puffin::ExtentsToString(dst_deflates_bit);
    406     LOG(INFO) << "src_puffs: " << puffin::ExtentsToString(src_puffs);
    407     LOG(INFO) << "dst_puffs: " << puffin::ExtentsToString(dst_puffs);
    408     LOG(INFO) << "src_extents: " << puffin::ExtentsToString(src_extents);
    409     LOG(INFO) << "dst_extents: " << puffin::ExtentsToString(dst_extents);
    410   }
    411   return 0;
    412 }
    413