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