Home | History | Annotate | Download | only in rezip
      1 // Copyright 2014 The Chromium 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 // rezip is a tool which is used to modify zip files. It reads in a
      6 // zip file and outputs a new zip file after applying various
      7 // transforms. The tool is used in the Android Chromium build process
      8 // to modify an APK file (which are zip files). The main application
      9 // of this is to modify the APK so that the shared library is no
     10 // longer compressed. Ironically, this saves both transmission and
     11 // device drive space. It saves transmission space because
     12 // uncompressed libraries make much smaller deltas with previous
     13 // versions. It saves device drive space because it is no longer
     14 // necessary to have both a compressed and uncompressed shared library
     15 // on the device. To achieve this the uncompressed library is opened
     16 // directly from within the APK using the "crazy" linker.
     17 
     18 #include <assert.h>
     19 #include <string.h>
     20 
     21 #include <iostream>
     22 #include <sstream>
     23 #include <string>
     24 
     25 #include "third_party/zlib/contrib/minizip/unzip.h"
     26 #include "third_party/zlib/contrib/minizip/zip.h"
     27 
     28 const int kMaxFilenameInZip = 256;
     29 const int kMaxExtraFieldInZip = 8192;
     30 const int kBufferSize = 4096;
     31 // Note do not use sysconf(_SC_PAGESIZE) here as that will give you the
     32 // page size of the host, this should be the page size of the target.
     33 const int kPageSizeOnDevice = 4096;
     34 
     35 // This is done to avoid having to make a dependency on all of base.
     36 class LogStream {
     37  public:
     38   ~LogStream() {
     39     stream_.flush();
     40     std::cerr << stream_.str() << std::endl;
     41   }
     42   std::ostream& stream() {
     43     return stream_;
     44   }
     45  private:
     46   std::ostringstream stream_;
     47 };
     48 
     49 #define LOG(tag) (LogStream().stream() << #tag << ":")
     50 
     51 // Copy the data from the currently opened file in the zipfile we are unzipping
     52 // into the currently opened file of the zipfile we are zipping.
     53 static bool CopySubfile(unzFile in_file,
     54                         zipFile out_file,
     55                         const char* in_zip_filename,
     56                         const char* out_zip_filename,
     57                         const char* in_filename,
     58                         const char* out_filename) {
     59   char buf[kBufferSize];
     60 
     61   int bytes = 0;
     62   while (true) {
     63     bytes = unzReadCurrentFile(in_file, buf, sizeof(buf));
     64     if (bytes < 0) {
     65       LOG(ERROR) << "failed to read from " << in_filename << " in zipfile "
     66                  << in_zip_filename;
     67       return false;
     68     }
     69 
     70     if (bytes == 0) {
     71       break;
     72     }
     73 
     74     if (ZIP_OK != zipWriteInFileInZip(out_file, buf, bytes)) {
     75       LOG(ERROR) << "failed to write from " << out_filename << " in zipfile "
     76                  << out_zip_filename;
     77       return false;
     78     }
     79   }
     80 
     81   return true;
     82 }
     83 
     84 static zip_fileinfo BuildOutInfo(const unz_file_info& in_info) {
     85   zip_fileinfo out_info;
     86   out_info.tmz_date.tm_sec = in_info.tmu_date.tm_sec;
     87   out_info.tmz_date.tm_min = in_info.tmu_date.tm_min;
     88   out_info.tmz_date.tm_hour = in_info.tmu_date.tm_hour;
     89   out_info.tmz_date.tm_mday = in_info.tmu_date.tm_mday;
     90   out_info.tmz_date.tm_mon = in_info.tmu_date.tm_mon;
     91   out_info.tmz_date.tm_year = in_info.tmu_date.tm_year;
     92 
     93   out_info.dosDate = in_info.dosDate;
     94   out_info.internal_fa = in_info.internal_fa;
     95   out_info.external_fa = in_info.external_fa;
     96   return out_info;
     97 }
     98 
     99 // RAII pattern for closing the unzip file.
    100 class ScopedUnzip {
    101  public:
    102   ScopedUnzip(const char* z_filename)
    103       : z_file_(NULL), z_filename_(z_filename) {}
    104 
    105   unzFile OpenOrDie() {
    106     z_file_ = unzOpen(z_filename_);
    107     if (z_file_ == NULL) {
    108       LOG(ERROR) << "failed to open zipfile " << z_filename_;
    109       exit(1);
    110     }
    111     return z_file_;
    112   }
    113 
    114   ~ScopedUnzip() {
    115     if (z_file_ != NULL && unzClose(z_file_) != UNZ_OK) {
    116       LOG(ERROR) << "failed to close input zipfile " << z_filename_;
    117       exit(1);
    118     }
    119   }
    120 
    121  private:
    122   const char* z_filename_;
    123   unzFile z_file_;
    124 };
    125 
    126 // RAII pattern for closing the out zip file.
    127 class ScopedZip {
    128  public:
    129   ScopedZip(const char* z_filename)
    130       : z_file_(NULL), z_filename_(z_filename) {}
    131 
    132   zipFile OpenOrDie() {
    133     z_file_ = zipOpen(z_filename_, APPEND_STATUS_CREATE);
    134     if (z_file_ == NULL) {
    135       LOG(ERROR) << "failed to open zipfile " << z_filename_;
    136       exit(1);
    137     }
    138     return z_file_;
    139   }
    140 
    141   ~ScopedZip() {
    142     if (z_file_ != NULL && zipClose(z_file_, NULL) != ZIP_OK) {
    143       LOG(ERROR) << "failed to close output zipfile" << z_filename_;
    144       exit(1);
    145     }
    146   }
    147 
    148  private:
    149   const char* z_filename_;
    150   zipFile z_file_;
    151 };
    152 
    153 typedef std::string (*RenameFun)(const char* in_filename);
    154 typedef int (*AlignFun)(const char* in_filename,
    155                         unzFile in_file,
    156                         char* extra_buffer,
    157                         int size);
    158 typedef bool (*InflatePredicateFun)(const char* filename);
    159 
    160 static bool IsPrefixLibraryFilename(const char* filename,
    161                                     const char* base_prefix) {
    162   // We are basically matching "lib/[^/]*/<base_prefix>lib.*[.]so".
    163   // However, we don't have C++11 regex, so we just handroll the test.
    164   // Also we exclude "libchromium_android_linker.so" as a match.
    165   const std::string filename_str = filename;
    166   const std::string prefix = "lib/";
    167   const std::string suffix = ".so";
    168 
    169   if (filename_str.length() < suffix.length() + prefix.length()) {
    170     // too short
    171     return false;
    172   }
    173 
    174   if (filename_str.compare(0, prefix.size(), prefix) != 0) {
    175     // does not start with "lib/"
    176     return false;
    177   }
    178 
    179   if (filename_str.compare(filename_str.length() - suffix.length(),
    180                            suffix.length(),
    181                            suffix) != 0) {
    182     // does not end with ".so"
    183     return false;
    184   }
    185 
    186   const size_t last_slash = filename_str.find_last_of('/');
    187   if (last_slash < prefix.length()) {
    188     // Only one slash
    189     return false;
    190   }
    191 
    192   const size_t second_slash = filename_str.find_first_of('/', prefix.length());
    193   if (second_slash != last_slash) {
    194     // filename_str contains more than two slashes.
    195     return false;
    196   }
    197 
    198   const std::string libprefix = std::string(base_prefix) + "lib";
    199   if (filename_str.compare(last_slash + 1, libprefix.length(), libprefix) !=
    200       0) {
    201     // basename piece does not start with <base_prefix>"lib"
    202     return false;
    203   }
    204 
    205   const std::string linker = "libchromium_android_linker.so";
    206   if (last_slash + 1 + linker.length() == filename_str.length() &&
    207       filename_str.compare(last_slash + 1, linker.length(), linker) == 0) {
    208     // Do not match the linker.
    209     return false;
    210   }
    211   return true;
    212 }
    213 
    214 static bool IsLibraryFilename(const char* filename) {
    215   return IsPrefixLibraryFilename(filename, "");
    216 }
    217 
    218 static bool IsCrazyLibraryFilename(const char* filename) {
    219   return IsPrefixLibraryFilename(filename, "crazy.");
    220 }
    221 
    222 static std::string RenameLibraryForCrazyLinker(const char* in_filename) {
    223   if (!IsLibraryFilename(in_filename)) {
    224     // Don't rename
    225     return in_filename;
    226   }
    227 
    228   std::string filename_str = in_filename;
    229   size_t last_slash = filename_str.find_last_of('/');
    230   if (last_slash == std::string::npos ||
    231       last_slash == filename_str.length() - 1) {
    232     return in_filename;
    233   }
    234 
    235   // We rename the library, so that the Android Package Manager
    236   // no longer extracts the library.
    237   const std::string basename_prefix = "crazy.";
    238   return filename_str.substr(0, last_slash + 1) + basename_prefix +
    239          filename_str.substr(last_slash + 1);
    240 }
    241 
    242 // For any file which matches the crazy library pattern "lib/../crazy.lib*.so"
    243 // add sufficient padding to the header that the start of the file will be
    244 // page aligned on the target device.
    245 static int PageAlignCrazyLibrary(const char* in_filename,
    246                                  unzFile in_file,
    247                                  char* extra_buffer,
    248                                  int extra_size) {
    249   if (!IsCrazyLibraryFilename(in_filename)) {
    250     return extra_size;
    251   }
    252   const ZPOS64_T pos = unzGetCurrentFileZStreamPos64(in_file);
    253   const int padding = kPageSizeOnDevice - (pos % kPageSizeOnDevice);
    254   if (padding == kPageSizeOnDevice) {
    255     return extra_size;
    256   }
    257 
    258   assert(extra_size < kMaxExtraFieldInZip - padding);
    259   memset(extra_buffer + extra_size, 0, padding);
    260   return extra_size + padding;
    261 }
    262 
    263 // As only the read side API provides offsets, we check that we added the
    264 // correct amount of padding by reading the zip file we just generated.
    265 static bool CheckPageAlign(const char* out_zip_filename) {
    266   ScopedUnzip scoped_unzip(out_zip_filename);
    267   unzFile in_file = scoped_unzip.OpenOrDie();
    268 
    269   int err = 0;
    270   bool checked = false;
    271   while (true) {
    272     char in_filename[kMaxFilenameInZip + 1];
    273     // Get info and extra field for current file.
    274     unz_file_info in_info;
    275     err = unzGetCurrentFileInfo(in_file,
    276                                 &in_info,
    277                                 in_filename,
    278                                 sizeof(in_filename) - 1,
    279                                 NULL,
    280                                 0,
    281                                 NULL,
    282                                 0);
    283     if (err != UNZ_OK) {
    284       LOG(ERROR) << "failed to get filename" << out_zip_filename;
    285       return false;
    286     }
    287     assert(in_info.size_filename <= kMaxFilenameInZip);
    288     in_filename[in_info.size_filename] = '\0';
    289 
    290     if (IsCrazyLibraryFilename(in_filename)) {
    291       err = unzOpenCurrentFile(in_file);
    292       if (err != UNZ_OK) {
    293         LOG(ERROR) << "failed to open subfile" << out_zip_filename << " "
    294                    << in_filename;
    295         return false;
    296       }
    297 
    298       const ZPOS64_T pos = unzGetCurrentFileZStreamPos64(in_file);
    299       const int alignment = pos % kPageSizeOnDevice;
    300       checked = (alignment == 0);
    301       if (!checked) {
    302         LOG(ERROR) << "Failed to page align library " << in_filename
    303                    << ", position " << pos << " alignment " << alignment;
    304       }
    305 
    306       err = unzCloseCurrentFile(in_file);
    307       if (err != UNZ_OK) {
    308         LOG(ERROR) << "failed to close subfile" << out_zip_filename << " "
    309                    << in_filename;
    310         return false;
    311       }
    312     }
    313 
    314     const int next = unzGoToNextFile(in_file);
    315     if (next == UNZ_END_OF_LIST_OF_FILE) {
    316       break;
    317     }
    318     if (next != UNZ_OK) {
    319       LOG(ERROR) << "failed to go to next file" << out_zip_filename;
    320       return false;
    321     }
    322   }
    323   return checked;
    324 }
    325 
    326 // Copy files from one archive to another applying alignment, rename and
    327 // inflate transformations if given.
    328 static bool Rezip(const char* in_zip_filename,
    329                   const char* out_zip_filename,
    330                   AlignFun align_fun,
    331                   RenameFun rename_fun,
    332                   InflatePredicateFun inflate_predicate_fun) {
    333   ScopedUnzip scoped_unzip(in_zip_filename);
    334   unzFile in_file = scoped_unzip.OpenOrDie();
    335 
    336   ScopedZip scoped_zip(out_zip_filename);
    337   zipFile out_file = scoped_zip.OpenOrDie();
    338   if (unzGoToFirstFile(in_file) != UNZ_OK) {
    339     LOG(ERROR) << "failed to go to first file in " << in_zip_filename;
    340     return false;
    341   }
    342 
    343   int err = 0;
    344   while (true) {
    345     char in_filename[kMaxFilenameInZip + 1];
    346     // Get info and extra field for current file.
    347     char extra_buffer[kMaxExtraFieldInZip];
    348     unz_file_info in_info;
    349     err = unzGetCurrentFileInfo(in_file,
    350                                 &in_info,
    351                                 in_filename,
    352                                 sizeof(in_filename) - 1,
    353                                 &extra_buffer,
    354                                 sizeof(extra_buffer),
    355                                 NULL,
    356                                 0);
    357     if (err != UNZ_OK) {
    358       LOG(ERROR) << "failed to get filename " << in_zip_filename;
    359       return false;
    360     }
    361     assert(in_info.size_filename <= kMaxFilenameInZip);
    362     in_filename[in_info.size_filename] = '\0';
    363 
    364     std::string out_filename = in_filename;
    365     if (rename_fun != NULL) {
    366       out_filename = rename_fun(in_filename);
    367     }
    368 
    369     bool inflate = false;
    370     if (inflate_predicate_fun != NULL) {
    371       inflate = inflate_predicate_fun(in_filename);
    372     }
    373 
    374     // Open the current file.
    375     int method = 0;
    376     int level = 0;
    377     int raw = !inflate;
    378     err = unzOpenCurrentFile2(in_file, &method, &level, raw);
    379     if (inflate) {
    380       method = Z_NO_COMPRESSION;
    381       level = 0;
    382     }
    383 
    384     if (err != UNZ_OK) {
    385       LOG(ERROR) << "failed to open subfile " << in_zip_filename << " "
    386                  << in_filename;
    387       return false;
    388     }
    389 
    390     // Get the extra field from the local header.
    391     char local_extra_buffer[kMaxExtraFieldInZip];
    392     int local_extra_size = unzGetLocalExtrafield(
    393         in_file, &local_extra_buffer, sizeof(local_extra_buffer));
    394 
    395     if (align_fun != NULL) {
    396       local_extra_size =
    397           align_fun(in_filename, in_file, local_extra_buffer, local_extra_size);
    398     }
    399 
    400     const char* local_extra = local_extra_size > 0 ? local_extra_buffer : NULL;
    401     const char* extra = in_info.size_file_extra > 0 ? extra_buffer : NULL;
    402 
    403     // Build the output info structure from the input info structure.
    404     const zip_fileinfo out_info = BuildOutInfo(in_info);
    405 
    406     const int ret = zipOpenNewFileInZip4(out_file,
    407                                          out_filename.c_str(),
    408                                          &out_info,
    409                                          local_extra,
    410                                          local_extra_size,
    411                                          extra,
    412                                          in_info.size_file_extra,
    413                                          /* comment */ NULL,
    414                                          method,
    415                                          level,
    416                                          /* raw */ 1,
    417                                          /* windowBits */ 0,
    418                                          /* memLevel */ 0,
    419                                          /* strategy */ 0,
    420                                          /* password */ NULL,
    421                                          /* crcForCrypting */ 0,
    422                                          in_info.version,
    423                                          /* flagBase */ 0);
    424 
    425     if (ZIP_OK != ret) {
    426       LOG(ERROR) << "failed to open subfile " << out_zip_filename << " "
    427                  << out_filename;
    428       return false;
    429     }
    430 
    431     if (!CopySubfile(in_file,
    432                      out_file,
    433                      in_zip_filename,
    434                      out_zip_filename,
    435                      in_filename,
    436                      out_filename.c_str())) {
    437       return false;
    438     }
    439 
    440     if (ZIP_OK != zipCloseFileInZipRaw(
    441                       out_file, in_info.uncompressed_size, in_info.crc)) {
    442       LOG(ERROR) << "failed to close subfile " << out_zip_filename << " "
    443                  << out_filename;
    444       return false;
    445     }
    446 
    447     err = unzCloseCurrentFile(in_file);
    448     if (err != UNZ_OK) {
    449       LOG(ERROR) << "failed to close subfile " << in_zip_filename << " "
    450                  << in_filename;
    451       return false;
    452     }
    453     const int next = unzGoToNextFile(in_file);
    454     if (next == UNZ_END_OF_LIST_OF_FILE) {
    455       break;
    456     }
    457     if (next != UNZ_OK) {
    458       LOG(ERROR) << "failed to go to next file" << in_zip_filename;
    459       return false;
    460     }
    461   }
    462 
    463   return true;
    464 }
    465 
    466 int main(int argc, const char* argv[]) {
    467   if (argc != 4) {
    468     LOG(ERROR) << "Usage: <action> <in_zipfile> <out_zipfile>";
    469     LOG(ERROR) << " <action> is 'inflatealign', 'dropdescriptors' or 'rename'";
    470     LOG(ERROR) << " 'inflatealign'";
    471     LOG(ERROR) << "   inflate and page aligns files of the form "
    472         "lib/*/crazy.lib*.so";
    473     LOG(ERROR) << " 'dropdescriptors':";
    474     LOG(ERROR) << "   remove zip data descriptors from the zip file";
    475     LOG(ERROR) << " 'rename':";
    476     LOG(ERROR) << "   renames files of the form lib/*/lib*.so to "
    477         "lib/*/crazy.lib*.so. Note libchromium_android_linker.so is "
    478         "not renamed as the crazy linker can not load itself.";
    479     exit(1);
    480   }
    481 
    482   const char* action = argv[1];
    483   const char* in_zip_filename = argv[2];
    484   const char* out_zip_filename = argv[3];
    485 
    486   InflatePredicateFun inflate_predicate_fun = NULL;
    487   AlignFun align_fun = NULL;
    488   RenameFun rename_fun = NULL;
    489   bool check_page_align = false;
    490   if (strcmp("inflatealign", action) == 0) {
    491     inflate_predicate_fun = &IsCrazyLibraryFilename;
    492     align_fun = &PageAlignCrazyLibrary;
    493     check_page_align = true;
    494   } else if (strcmp("rename", action) == 0) {
    495     rename_fun = &RenameLibraryForCrazyLinker;
    496   } else if (strcmp("dropdescriptors", action) == 0) {
    497     // Minizip does not know about data descriptors, so the default
    498     // copying action will drop the descriptors. This should be fine
    499     // as data descriptors are redundant information.
    500     // Note we need to explicitly drop the descriptors before trying to
    501     // do alignment otherwise we will miscalculate the position because
    502     // we don't know about the data descriptors.
    503   } else {
    504     LOG(ERROR) << "Usage: <action> should be 'inflatealign', "
    505                   "'dropdescriptors' or 'rename'";
    506     exit(1);
    507   }
    508 
    509   if (!Rezip(in_zip_filename,
    510              out_zip_filename,
    511              align_fun,
    512              rename_fun,
    513              inflate_predicate_fun)) {
    514     exit(1);
    515   }
    516   if (check_page_align && !CheckPageAlign(out_zip_filename)) {
    517     exit(1);
    518   }
    519   return 0;
    520 }
    521