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