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.
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.
25 #include "third_party/zlib/contrib/minizip/unzip.h"
26 #include "third_party/zlib/contrib/minizip/zip.h"
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;
35 // This is done to avoid having to make a dependency on all of base.
40 std::cerr
<< stream_
.str() << std::endl
;
42 std::ostream
& stream() {
46 std::ostringstream stream_
;
49 #define LOG(tag) (LogStream().stream() << #tag << ":")
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
,
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
];
63 bytes
= unzReadCurrentFile(in_file
, buf
, sizeof(buf
));
65 LOG(ERROR
) << "failed to read from " << in_filename
<< " in zipfile "
74 if (ZIP_OK
!= zipWriteInFileInZip(out_file
, buf
, bytes
)) {
75 LOG(ERROR
) << "failed to write from " << out_filename
<< " in zipfile "
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
;
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
;
99 // RAII pattern for closing the unzip file.
102 ScopedUnzip(const char* z_filename
)
103 : z_file_(NULL
), z_filename_(z_filename
) {}
105 unzFile
OpenOrDie() {
106 z_file_
= unzOpen(z_filename_
);
107 if (z_file_
== NULL
) {
108 LOG(ERROR
) << "failed to open zipfile " << z_filename_
;
115 if (z_file_
!= NULL
&& unzClose(z_file_
) != UNZ_OK
) {
116 LOG(ERROR
) << "failed to close input zipfile " << z_filename_
;
122 const char* z_filename_
;
126 // RAII pattern for closing the out zip file.
129 ScopedZip(const char* z_filename
)
130 : z_file_(NULL
), z_filename_(z_filename
) {}
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_
;
142 if (z_file_
!= NULL
&& zipClose(z_file_
, NULL
) != ZIP_OK
) {
143 LOG(ERROR
) << "failed to close output zipfile" << z_filename_
;
149 const char* z_filename_
;
153 typedef std::string (*RenameFun
)(const char* in_filename
);
154 typedef int (*AlignFun
)(const char* in_filename
,
158 typedef bool (*InflatePredicateFun
)(const char* filename
);
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";
169 if (filename_str
.length() < suffix
.length() + prefix
.length()) {
174 if (filename_str
.compare(0, prefix
.size(), prefix
) != 0) {
175 // does not start with "lib/"
179 if (filename_str
.compare(filename_str
.length() - suffix
.length(),
182 // does not end with ".so"
186 const size_t last_slash
= filename_str
.find_last_of('/');
187 if (last_slash
< prefix
.length()) {
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.
198 const std::string libprefix
= std::string(base_prefix
) + "lib";
199 if (filename_str
.compare(last_slash
+ 1, libprefix
.length(), libprefix
) !=
201 // basename piece does not start with <base_prefix>"lib"
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.
214 static bool IsLibraryFilename(const char* filename
) {
215 return IsPrefixLibraryFilename(filename
, "");
218 static bool IsCrazyLibraryFilename(const char* filename
) {
219 return IsPrefixLibraryFilename(filename
, "crazy.");
222 static std::string
RenameLibraryForCrazyLinker(const char* in_filename
) {
223 if (!IsLibraryFilename(in_filename
)) {
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) {
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);
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
,
249 if (!IsCrazyLibraryFilename(in_filename
)) {
252 const ZPOS64_T pos
= unzGetCurrentFileZStreamPos64(in_file
);
253 const int padding
= kPageSizeOnDevice
- (pos
% kPageSizeOnDevice
);
254 if (padding
== kPageSizeOnDevice
) {
258 assert(extra_size
< kMaxExtraFieldInZip
- padding
);
259 memset(extra_buffer
+ extra_size
, 0, padding
);
260 return extra_size
+ padding
;
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.
266 static bool CheckPageAlignAndOnlyOneLibrary(const char* out_zip_filename
) {
267 ScopedUnzip
scoped_unzip(out_zip_filename
);
268 unzFile in_file
= scoped_unzip
.OpenOrDie();
272 bool checked
= false;
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
,
280 sizeof(in_filename
) - 1,
286 LOG(ERROR
) << "failed to get filename" << out_zip_filename
;
289 assert(in_info
.size_filename
<= kMaxFilenameInZip
);
290 in_filename
[in_info
.size_filename
] = '\0';
292 if (IsCrazyLibraryFilename(in_filename
)) {
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.";
305 err
= unzOpenCurrentFile(in_file
);
307 LOG(ERROR
) << "failed to open subfile" << out_zip_filename
<< " "
312 const ZPOS64_T pos
= unzGetCurrentFileZStreamPos64(in_file
);
313 const int alignment
= pos
% kPageSizeOnDevice
;
314 checked
= (alignment
== 0);
316 LOG(ERROR
) << "Failed to page align library " << in_filename
317 << ", position " << pos
<< " alignment " << alignment
;
320 err
= unzCloseCurrentFile(in_file
);
322 LOG(ERROR
) << "failed to close subfile" << out_zip_filename
<< " "
328 const int next
= unzGoToNextFile(in_file
);
329 if (next
== UNZ_END_OF_LIST_OF_FILE
) {
332 if (next
!= UNZ_OK
) {
333 LOG(ERROR
) << "failed to go to next file" << out_zip_filename
;
340 // Copy files from one archive to another applying alignment, rename and
341 // inflate transformations if given.
342 static bool Rezip(const char* in_zip_filename
,
343 const char* out_zip_filename
,
345 RenameFun rename_fun
,
346 InflatePredicateFun inflate_predicate_fun
) {
347 ScopedUnzip
scoped_unzip(in_zip_filename
);
348 unzFile in_file
= scoped_unzip
.OpenOrDie();
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
;
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
,
366 sizeof(in_filename
) - 1,
368 sizeof(extra_buffer
),
372 LOG(ERROR
) << "failed to get filename " << in_zip_filename
;
375 assert(in_info
.size_filename
<= kMaxFilenameInZip
);
376 in_filename
[in_info
.size_filename
] = '\0';
378 std::string out_filename
= in_filename
;
379 if (rename_fun
!= NULL
) {
380 out_filename
= rename_fun(in_filename
);
383 bool inflate
= false;
384 if (inflate_predicate_fun
!= NULL
) {
385 inflate
= inflate_predicate_fun(in_filename
);
388 // Open the current file.
392 err
= unzOpenCurrentFile2(in_file
, &method
, &level
, raw
);
394 method
= Z_NO_COMPRESSION
;
399 LOG(ERROR
) << "failed to open subfile " << in_zip_filename
<< " "
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
));
409 if (align_fun
!= NULL
) {
411 align_fun(in_filename
, in_file
, local_extra_buffer
, local_extra_size
);
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
;
417 // Build the output info structure from the input info structure.
418 const zip_fileinfo out_info
= BuildOutInfo(in_info
);
420 const int ret
= zipOpenNewFileInZip4(out_file
,
421 out_filename
.c_str(),
426 in_info
.size_file_extra
,
435 /* crcForCrypting */ 0,
440 LOG(ERROR
) << "failed to open subfile " << out_zip_filename
<< " "
445 if (!CopySubfile(in_file
,
450 out_filename
.c_str())) {
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
<< " "
461 err
= unzCloseCurrentFile(in_file
);
463 LOG(ERROR
) << "failed to close subfile " << in_zip_filename
<< " "
467 const int next
= unzGoToNextFile(in_file
);
468 if (next
== UNZ_END_OF_LIST_OF_FILE
) {
471 if (next
!= UNZ_OK
) {
472 LOG(ERROR
) << "failed to go to next file" << in_zip_filename
;
480 int main(int argc
, const char* argv
[]) {
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.";
496 const char* action
= argv
[1];
497 const char* in_zip_filename
= argv
[2];
498 const char* out_zip_filename
= argv
[3];
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.
518 LOG(ERROR
) << "Usage: <action> should be 'inflatealign', "
519 "'dropdescriptors' or 'rename'";
523 if (!Rezip(in_zip_filename
,
527 inflate_predicate_fun
)) {
530 if (check_page_align
&& !CheckPageAlignAndOnlyOneLibrary(out_zip_filename
)) {