• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
~LogStream()38   ~LogStream() {
39     stream_.flush();
40     std::cerr << stream_.str() << std::endl;
41   }
stream()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.
CopySubfile(unzFile in_file,zipFile out_file,const char * in_zip_filename,const char * out_zip_filename,const char * in_filename,const char * out_filename)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 
BuildOutInfo(const unz_file_info & in_info)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:
ScopedUnzip(const char * z_filename)102   ScopedUnzip(const char* z_filename)
103       : z_file_(NULL), z_filename_(z_filename) {}
104 
OpenOrDie()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 
~ScopedUnzip()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:
ScopedZip(const char * z_filename)129   ScopedZip(const char* z_filename)
130       : z_file_(NULL), z_filename_(z_filename) {}
131 
OpenOrDie()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 
~ScopedZip()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 
IsPrefixLibraryFilename(const char * filename,const char * base_prefix)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 
IsLibraryFilename(const char * filename)214 static bool IsLibraryFilename(const char* filename) {
215   return IsPrefixLibraryFilename(filename, "");
216 }
217 
IsCrazyLibraryFilename(const char * filename)218 static bool IsCrazyLibraryFilename(const char* filename) {
219   return IsPrefixLibraryFilename(filename, "crazy.");
220 }
221 
RenameLibraryForCrazyLinker(const char * in_filename)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.
PageAlignCrazyLibrary(const char * in_filename,unzFile in_file,char * extra_buffer,int extra_size)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.
CheckPageAlign(const char * out_zip_filename)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.
Rezip(const char * in_zip_filename,const char * out_zip_filename,AlignFun align_fun,RenameFun rename_fun,InflatePredicateFun inflate_predicate_fun)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 
main(int argc,const char * argv[])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