• Home
  • History
  • Annotate
  • 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.
265 // Also enforce that only one library is in the APK.
CheckPageAlignAndOnlyOneLibrary(const char * out_zip_filename)266 static bool CheckPageAlignAndOnlyOneLibrary(const char* out_zip_filename) {
267   ScopedUnzip scoped_unzip(out_zip_filename);
268   unzFile in_file = scoped_unzip.OpenOrDie();
269 
270   int err = 0;
271   int count = 0;
272   bool checked = false;
273   while (true) {
274     char in_filename[kMaxFilenameInZip + 1];
275     // Get info and extra field for current file.
276     unz_file_info in_info;
277     err = unzGetCurrentFileInfo(in_file,
278                                 &in_info,
279                                 in_filename,
280                                 sizeof(in_filename) - 1,
281                                 NULL,
282                                 0,
283                                 NULL,
284                                 0);
285     if (err != UNZ_OK) {
286       LOG(ERROR) << "failed to get filename" << out_zip_filename;
287       return false;
288     }
289     assert(in_info.size_filename <= kMaxFilenameInZip);
290     in_filename[in_info.size_filename] = '\0';
291 
292     if (IsCrazyLibraryFilename(in_filename)) {
293       count++;
294       if (count > 1) {
295         LOG(ERROR)
296             << "Found more than one library in " << out_zip_filename << "\n"
297             << "Multiple libraries are not supported for APKs that use "
298             << "'load_library_from_zip_file'.\n"
299             << "See crbug/388223.\n"
300             << "Note, check that your build is clean.\n"
301             << "An unclean build can incorrectly incorporate old "
302             << "libraries in the APK.";
303         return false;
304       }
305       err = unzOpenCurrentFile(in_file);
306       if (err != UNZ_OK) {
307         LOG(ERROR) << "failed to open subfile" << out_zip_filename << " "
308                    << in_filename;
309         return false;
310       }
311 
312       const ZPOS64_T pos = unzGetCurrentFileZStreamPos64(in_file);
313       const int alignment = pos % kPageSizeOnDevice;
314       checked = (alignment == 0);
315       if (!checked) {
316         LOG(ERROR) << "Failed to page align library " << in_filename
317                    << ", position " << pos << " alignment " << alignment;
318       }
319 
320       err = unzCloseCurrentFile(in_file);
321       if (err != UNZ_OK) {
322         LOG(ERROR) << "failed to close subfile" << out_zip_filename << " "
323                    << in_filename;
324         return false;
325       }
326     }
327 
328     const int next = unzGoToNextFile(in_file);
329     if (next == UNZ_END_OF_LIST_OF_FILE) {
330       break;
331     }
332     if (next != UNZ_OK) {
333       LOG(ERROR) << "failed to go to next file" << out_zip_filename;
334       return false;
335     }
336   }
337   return checked;
338 }
339 
340 // Copy files from one archive to another applying alignment, rename and
341 // 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)342 static bool Rezip(const char* in_zip_filename,
343                   const char* out_zip_filename,
344                   AlignFun align_fun,
345                   RenameFun rename_fun,
346                   InflatePredicateFun inflate_predicate_fun) {
347   ScopedUnzip scoped_unzip(in_zip_filename);
348   unzFile in_file = scoped_unzip.OpenOrDie();
349 
350   ScopedZip scoped_zip(out_zip_filename);
351   zipFile out_file = scoped_zip.OpenOrDie();
352   if (unzGoToFirstFile(in_file) != UNZ_OK) {
353     LOG(ERROR) << "failed to go to first file in " << in_zip_filename;
354     return false;
355   }
356 
357   int err = 0;
358   while (true) {
359     char in_filename[kMaxFilenameInZip + 1];
360     // Get info and extra field for current file.
361     char extra_buffer[kMaxExtraFieldInZip];
362     unz_file_info in_info;
363     err = unzGetCurrentFileInfo(in_file,
364                                 &in_info,
365                                 in_filename,
366                                 sizeof(in_filename) - 1,
367                                 &extra_buffer,
368                                 sizeof(extra_buffer),
369                                 NULL,
370                                 0);
371     if (err != UNZ_OK) {
372       LOG(ERROR) << "failed to get filename " << in_zip_filename;
373       return false;
374     }
375     assert(in_info.size_filename <= kMaxFilenameInZip);
376     in_filename[in_info.size_filename] = '\0';
377 
378     std::string out_filename = in_filename;
379     if (rename_fun != NULL) {
380       out_filename = rename_fun(in_filename);
381     }
382 
383     bool inflate = false;
384     if (inflate_predicate_fun != NULL) {
385       inflate = inflate_predicate_fun(in_filename);
386     }
387 
388     // Open the current file.
389     int method = 0;
390     int level = 0;
391     int raw = !inflate;
392     err = unzOpenCurrentFile2(in_file, &method, &level, raw);
393     if (inflate) {
394       method = Z_NO_COMPRESSION;
395       level = 0;
396     }
397 
398     if (err != UNZ_OK) {
399       LOG(ERROR) << "failed to open subfile " << in_zip_filename << " "
400                  << in_filename;
401       return false;
402     }
403 
404     // Get the extra field from the local header.
405     char local_extra_buffer[kMaxExtraFieldInZip];
406     int local_extra_size = unzGetLocalExtrafield(
407         in_file, &local_extra_buffer, sizeof(local_extra_buffer));
408 
409     if (align_fun != NULL) {
410       local_extra_size =
411           align_fun(in_filename, in_file, local_extra_buffer, local_extra_size);
412     }
413 
414     const char* local_extra = local_extra_size > 0 ? local_extra_buffer : NULL;
415     const char* extra = in_info.size_file_extra > 0 ? extra_buffer : NULL;
416 
417     // Build the output info structure from the input info structure.
418     const zip_fileinfo out_info = BuildOutInfo(in_info);
419 
420     const int ret = zipOpenNewFileInZip4(out_file,
421                                          out_filename.c_str(),
422                                          &out_info,
423                                          local_extra,
424                                          local_extra_size,
425                                          extra,
426                                          in_info.size_file_extra,
427                                          /* comment */ NULL,
428                                          method,
429                                          level,
430                                          /* raw */ 1,
431                                          /* windowBits */ 0,
432                                          /* memLevel */ 0,
433                                          /* strategy */ 0,
434                                          /* password */ NULL,
435                                          /* crcForCrypting */ 0,
436                                          in_info.version,
437                                          /* flagBase */ 0);
438 
439     if (ZIP_OK != ret) {
440       LOG(ERROR) << "failed to open subfile " << out_zip_filename << " "
441                  << out_filename;
442       return false;
443     }
444 
445     if (!CopySubfile(in_file,
446                      out_file,
447                      in_zip_filename,
448                      out_zip_filename,
449                      in_filename,
450                      out_filename.c_str())) {
451       return false;
452     }
453 
454     if (ZIP_OK != zipCloseFileInZipRaw(
455                       out_file, in_info.uncompressed_size, in_info.crc)) {
456       LOG(ERROR) << "failed to close subfile " << out_zip_filename << " "
457                  << out_filename;
458       return false;
459     }
460 
461     err = unzCloseCurrentFile(in_file);
462     if (err != UNZ_OK) {
463       LOG(ERROR) << "failed to close subfile " << in_zip_filename << " "
464                  << in_filename;
465       return false;
466     }
467     const int next = unzGoToNextFile(in_file);
468     if (next == UNZ_END_OF_LIST_OF_FILE) {
469       break;
470     }
471     if (next != UNZ_OK) {
472       LOG(ERROR) << "failed to go to next file" << in_zip_filename;
473       return false;
474     }
475   }
476 
477   return true;
478 }
479 
main(int argc,const char * argv[])480 int main(int argc, const char* argv[]) {
481   if (argc != 4) {
482     LOG(ERROR) << "Usage: <action> <in_zipfile> <out_zipfile>";
483     LOG(ERROR) << " <action> is 'inflatealign', 'dropdescriptors' or 'rename'";
484     LOG(ERROR) << " 'inflatealign'";
485     LOG(ERROR) << "   inflate and page aligns files of the form "
486         "lib/*/crazy.lib*.so";
487     LOG(ERROR) << " 'dropdescriptors':";
488     LOG(ERROR) << "   remove zip data descriptors from the zip file";
489     LOG(ERROR) << " 'rename':";
490     LOG(ERROR) << "   renames files of the form lib/*/lib*.so to "
491         "lib/*/crazy.lib*.so. Note libchromium_android_linker.so is "
492         "not renamed as the crazy linker can not load itself.";
493     exit(1);
494   }
495 
496   const char* action = argv[1];
497   const char* in_zip_filename = argv[2];
498   const char* out_zip_filename = argv[3];
499 
500   InflatePredicateFun inflate_predicate_fun = NULL;
501   AlignFun align_fun = NULL;
502   RenameFun rename_fun = NULL;
503   bool check_page_align = false;
504   if (strcmp("inflatealign", action) == 0) {
505     inflate_predicate_fun = &IsCrazyLibraryFilename;
506     align_fun = &PageAlignCrazyLibrary;
507     check_page_align = true;
508   } else if (strcmp("rename", action) == 0) {
509     rename_fun = &RenameLibraryForCrazyLinker;
510   } else if (strcmp("dropdescriptors", action) == 0) {
511     // Minizip does not know about data descriptors, so the default
512     // copying action will drop the descriptors. This should be fine
513     // as data descriptors are redundant information.
514     // Note we need to explicitly drop the descriptors before trying to
515     // do alignment otherwise we will miscalculate the position because
516     // we don't know about the data descriptors.
517   } else {
518     LOG(ERROR) << "Usage: <action> should be 'inflatealign', "
519                   "'dropdescriptors' or 'rename'";
520     exit(1);
521   }
522 
523   if (!Rezip(in_zip_filename,
524              out_zip_filename,
525              align_fun,
526              rename_fun,
527              inflate_predicate_fun)) {
528     exit(1);
529   }
530   if (check_page_align && !CheckPageAlignAndOnlyOneLibrary(out_zip_filename)) {
531     exit(1);
532   }
533   return 0;
534 }
535