Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(82)

Side by Side Diff: build/android/rezip/RezipApk.java

Issue 2612773005: Reland of Android: Delete rezip in favor of zipalign -p (Closed)
Patch Set: don't do crazy. prefix renaming when crazy linker is not used (monochrome) Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/android/rezip/BUILD.gn ('k') | build/config/android/internal_rules.gni » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 import java.io.File;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.util.ArrayList;
11 import java.util.Collections;
12 import java.util.Comparator;
13 import java.util.Enumeration;
14 import java.util.List;
15 import java.util.jar.JarEntry;
16 import java.util.jar.JarFile;
17 import java.util.jar.JarOutputStream;
18 import java.util.regex.Pattern;
19 import java.util.zip.CRC32;
20
21 /**
22 * Command line tool used to build APKs which support loading the native code li brary
23 * directly from the APK file. To construct the APK we rename the native library by
24 * adding the prefix "crazy." to the filename. This is done to prevent the Andro id
25 * Package Manager from extracting the library. The native code must be page ali gned
26 * and uncompressed. The page alignment is implemented by adding a zero filled f ile
27 * in front of the the native code library. This tool is designed so that runnin g
28 * SignApk and/or zipalign on the resulting APK does not break the page alignmen t.
29 * This is achieved by outputing the filenames in the same canonical order used
30 * by SignApk and adding the same alignment fields added by zipalign.
31 */
32 class RezipApk {
33 // Alignment to use for non-compressed files (must match zipalign).
34 private static final int ALIGNMENT = 4;
35
36 // Alignment to use for non-compressed *.so files
37 private static final int LIBRARY_ALIGNMENT = 4096;
38
39 // Files matching this pattern are not copied to the output when adding alig nment.
40 // When reordering and verifying the APK they are copied to the end of the f ile.
41 private static Pattern sMetaFilePattern =
42 Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert ))|("
43 + Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
44
45 // Pattern for matching a shared library in the APK
46 private static Pattern sLibraryPattern = Pattern.compile("^lib/[^/]*/lib.*[. ]so$");
47 // Pattern for match the crazy linker in the APK
48 private static Pattern sCrazyLinkerPattern =
49 Pattern.compile("^lib/[^/]*/libchromium_android_linker.so$");
50 // Pattern for matching a crazy loaded shared library in the APK
51 private static Pattern sCrazyLibraryPattern = Pattern.compile("^lib/[^/]*/cr azy.lib.*[.]so$");
52
53 private static boolean isLibraryFilename(String filename) {
54 return sLibraryPattern.matcher(filename).matches()
55 && !sCrazyLinkerPattern.matcher(filename).matches();
56 }
57
58 private static boolean isCrazyLibraryFilename(String filename) {
59 return sCrazyLibraryPattern.matcher(filename).matches();
60 }
61
62 private static String renameLibraryForCrazyLinker(String filename) {
63 int lastSlash = filename.lastIndexOf('/');
64 // We rename the library, so that the Android Package Manager
65 // no longer extracts the library.
66 return filename.substring(0, lastSlash + 1) + "crazy." + filename.substr ing(lastSlash + 1);
67 }
68
69 /**
70 * Wraps another output stream, counting the number of bytes written.
71 */
72 private static class CountingOutputStream extends OutputStream {
73 private long mCount = 0;
74 private OutputStream mOut;
75
76 public CountingOutputStream(OutputStream out) {
77 this.mOut = out;
78 }
79
80 /** Returns the number of bytes written. */
81 public long getCount() {
82 return mCount;
83 }
84
85 @Override public void write(byte[] b, int off, int len) throws IOExcepti on {
86 mOut.write(b, off, len);
87 mCount += len;
88 }
89
90 @Override public void write(int b) throws IOException {
91 mOut.write(b);
92 mCount++;
93 }
94
95 @Override public void close() throws IOException {
96 mOut.close();
97 }
98
99 @Override public void flush() throws IOException {
100 mOut.flush();
101 }
102 }
103
104 private static String outputName(JarEntry entry, boolean rename) {
105 String inName = entry.getName();
106 if (rename && entry.getSize() > 0 && isLibraryFilename(inName)) {
107 return renameLibraryForCrazyLinker(inName);
108 }
109 return inName;
110 }
111
112 /**
113 * Comparator used to sort jar entries from the input file.
114 * Sorting is done based on the output filename (which maybe renamed).
115 * Filenames are in natural string order, except that filenames matching
116 * the meta-file pattern are always after other files. This is so the manife st
117 * and signature are at the end of the file after any alignment file.
118 */
119 private static class EntryComparator implements Comparator<JarEntry> {
120 private boolean mRename;
121
122 public EntryComparator(boolean rename) {
123 mRename = rename;
124 }
125
126 @Override
127 public int compare(JarEntry j1, JarEntry j2) {
128 String o1 = outputName(j1, mRename);
129 String o2 = outputName(j2, mRename);
130 boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
131 boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
132 if (o1Matches != o2Matches) {
133 return o1Matches ? 1 : -1;
134 } else {
135 return o1.compareTo(o2);
136 }
137 }
138 }
139
140 // Build an ordered list of jar entries. The jar entries from the input are
141 // sorted based on the output filenames (which maybe renamed). If |omitMetaF iles|
142 // is true do not include the jar entries for the META-INF files.
143 // Entries are ordered in the deterministic order used by SignApk.
144 private static List<JarEntry> getOutputFileOrderEntries(
145 JarFile jar, boolean omitMetaFiles, boolean rename) {
146 List<JarEntry> entries = new ArrayList<JarEntry>();
147 for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
148 JarEntry entry = e.nextElement();
149 if (entry.isDirectory()) {
150 continue;
151 }
152 if (omitMetaFiles && sMetaFilePattern.matcher(entry.getName()).match es()) {
153 continue;
154 }
155 entries.add(entry);
156 }
157
158 // We sort the input entries by name. When present META-INF files
159 // are sorted to the end.
160 Collections.sort(entries, new EntryComparator(rename));
161 return entries;
162 }
163
164 /**
165 * Add a zero filled alignment file at this point in the zip file,
166 * The added file will be added before |name| and after |prevName|.
167 * The size of the alignment file is such that the location of the
168 * file |name| will be on a LIBRARY_ALIGNMENT boundary.
169 *
170 * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
171 * file will not alter the alignment.
172 *
173 * @param offset number of bytes into the output file at this point.
174 * @param timestamp time in millis since the epoch to include in the header.
175 * @param name the name of the library filename.
176 * @param prevName the name of the previous file in the archive (or null).
177 * @param out jar output stream to write the alignment file to.
178 *
179 * @throws IOException if the output file can not be written.
180 */
181 private static void addAlignmentFile(
182 long offset, long timestamp, String name, String prevName,
183 JarOutputStream out) throws IOException {
184
185 // Compute the start and alignment of the library, as if it was next.
186 int headerSize = JarFile.LOCHDR + name.length();
187 long libOffset = offset + headerSize;
188 int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT );
189 if (libNeeded == LIBRARY_ALIGNMENT) {
190 // Already aligned, no need to added alignment file.
191 return;
192 }
193
194 // Check that there is not another file between the library and the
195 // alignment file.
196 String alignName = name.substring(0, name.length() - 2) + "align";
197 if (prevName != null && prevName.compareTo(alignName) >= 0) {
198 throw new UnsupportedOperationException(
199 "Unable to insert alignment file, because there is "
200 + "another file in front of the file to be aligned. "
201 + "Other file: " + prevName + " Alignment file: " + alignName
202 + " file: " + name);
203 }
204
205 // Compute the size of the alignment file header.
206 headerSize = JarFile.LOCHDR + alignName.length();
207 // We are going to add an alignment file of type STORED. This file
208 // will itself induce a zipalign alignment adjustment.
209 int extraNeeded =
210 (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNM ENT;
211 headerSize += extraNeeded;
212
213 if (libNeeded < headerSize + 1) {
214 // The header was bigger than the alignment that we need, add anothe r page.
215 libNeeded += LIBRARY_ALIGNMENT;
216 }
217 // Compute the size of the alignment file.
218 libNeeded -= headerSize;
219
220 // Build the header for the alignment file.
221 byte[] zeroBuffer = new byte[libNeeded];
222 JarEntry alignEntry = new JarEntry(alignName);
223 alignEntry.setMethod(JarEntry.STORED);
224 alignEntry.setSize(libNeeded);
225 alignEntry.setTime(timestamp);
226 CRC32 crc = new CRC32();
227 crc.update(zeroBuffer);
228 alignEntry.setCrc(crc.getValue());
229
230 if (extraNeeded != 0) {
231 alignEntry.setExtra(new byte[extraNeeded]);
232 }
233
234 // Output the alignment file.
235 out.putNextEntry(alignEntry);
236 out.write(zeroBuffer);
237 out.closeEntry();
238 out.flush();
239 }
240
241 // Make a JarEntry for the output file which corresponds to the input
242 // file. The output file will be called |name|. The output file will always
243 // be uncompressed (STORED). If the input is not STORED it is necessary to i nflate
244 // it to compute the CRC and size of the output entry.
245 private static JarEntry makeStoredEntry(String name, JarEntry inEntry, JarFi le in)
246 throws IOException {
247 JarEntry outEntry = new JarEntry(name);
248 outEntry.setMethod(JarEntry.STORED);
249
250 if (inEntry.getMethod() == JarEntry.STORED) {
251 outEntry.setCrc(inEntry.getCrc());
252 outEntry.setSize(inEntry.getSize());
253 } else {
254 // We are inflating the file. We need to compute the CRC and size.
255 byte[] buffer = new byte[4096];
256 CRC32 crc = new CRC32();
257 int size = 0;
258 int num;
259 InputStream data = in.getInputStream(inEntry);
260 while ((num = data.read(buffer)) > 0) {
261 crc.update(buffer, 0, num);
262 size += num;
263 }
264 data.close();
265 outEntry.setCrc(crc.getValue());
266 outEntry.setSize(size);
267 }
268 return outEntry;
269 }
270
271 /**
272 * Copy the contents of the input APK file to the output APK file. If |renam e| is
273 * true then non-empty libraries (*.so) in the input will be renamed by pref ixing
274 * "crazy.". This is done to prevent the Android Package Manager extracting the
275 * library. Note the crazy linker itself is not renamed, for bootstrapping r easons.
276 * Empty libraries are not renamed (they are in the APK to workaround a bug where
277 * the Android Package Manager fails to delete old versions when upgrading).
278 * There must be exactly one "crazy" library in the output stream. The "craz y"
279 * library will be uncompressed and page aligned in the output stream. Page
280 * alignment is implemented by adding a zero filled file, regular alignment is
281 * implemented by adding a zero filled extra field to the zip file header. I f
282 * |addAlignment| is true a page alignment file is added, otherwise the "cra zy"
283 * library must already be page aligned. Care is taken so that the output is generated
284 * in the same way as SignApk. This is important so that running SignApk and
285 * zipalign on the output does not break the page alignment. The archive may not
286 * contain a "*.apk" as SignApk has special nested signing logic that we do not
287 * support.
288 *
289 * @param in The input APK File.
290 * @param out The output APK stream.
291 * @param countOut Counting output stream (to measure the current offset).
292 * @param addAlignment Whether to add the alignment file or just check.
293 * @param rename Whether to rename libraries to be "crazy".
294 *
295 * @throws IOException if the output file can not be written.
296 */
297 private static void rezip(
298 JarFile in, JarOutputStream out, CountingOutputStream countOut,
299 boolean addAlignment, boolean rename) throws IOException {
300
301 List<JarEntry> entries = getOutputFileOrderEntries(in, addAlignment, ren ame);
302 long timestamp = System.currentTimeMillis();
303 byte[] buffer = new byte[4096];
304 boolean firstEntry = true;
305 String prevName = null;
306 int numCrazy = 0;
307 for (JarEntry inEntry : entries) {
308 // Rename files, if specied.
309 String name = outputName(inEntry, rename);
310 if (name.endsWith(".apk")) {
311 throw new UnsupportedOperationException(
312 "Nested APKs are not supported: " + name);
313 }
314
315 // Build the header.
316 JarEntry outEntry = null;
317 boolean isCrazy = isCrazyLibraryFilename(name);
318 if (isCrazy) {
319 // "crazy" libraries are alway output uncompressed (STORED).
320 outEntry = makeStoredEntry(name, inEntry, in);
321 numCrazy++;
322 if (numCrazy > 1) {
323 throw new UnsupportedOperationException(
324 "Found more than one library\n"
325 + "Multiple libraries are not supported for APKs tha t use "
326 + "'load_library_from_zip'.\n"
327 + "See crbug/388223.\n"
328 + "Note, check that your build is clean.\n"
329 + "An unclean build can incorrectly incorporate old "
330 + "libraries in the APK.");
331 }
332 } else if (inEntry.getMethod() == JarEntry.STORED) {
333 // Preserve the STORED method of the input entry.
334 outEntry = new JarEntry(inEntry);
335 outEntry.setExtra(null);
336 } else {
337 // Create a new entry so that the compressed len is recomputed.
338 outEntry = new JarEntry(name);
339 }
340 outEntry.setTime(timestamp);
341
342 // Compute and add alignment
343 long offset = countOut.getCount();
344 if (firstEntry) {
345 // The first entry in a jar file has an extra field of
346 // four bytes that you can't get rid of; any extra
347 // data you specify in the JarEntry is appended to
348 // these forced four bytes. This is JAR_MAGIC in
349 // JarOutputStream; the bytes are 0xfeca0000.
350 firstEntry = false;
351 offset += 4;
352 }
353 if (outEntry.getMethod() == JarEntry.STORED) {
354 if (isCrazy) {
355 if (addAlignment) {
356 addAlignmentFile(offset, timestamp, name, prevName, out) ;
357 }
358 // We check that we did indeed get to a page boundary.
359 offset = countOut.getCount() + JarFile.LOCHDR + name.length( );
360 if ((offset % LIBRARY_ALIGNMENT) != 0) {
361 throw new AssertionError(
362 "Library was not page aligned when verifying pag e alignment. "
363 + "Library name: " + name + " Expected alignment : "
364 + LIBRARY_ALIGNMENT + "Offset: " + offset + " Er ror: "
365 + (offset % LIBRARY_ALIGNMENT));
366 }
367 } else {
368 // This is equivalent to zipalign.
369 offset += JarFile.LOCHDR + name.length();
370 int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIG NMENT;
371 if (needed != 0) {
372 outEntry.setExtra(new byte[needed]);
373 }
374 }
375 }
376 out.putNextEntry(outEntry);
377
378 // Copy the data from the input to the output
379 int num;
380 InputStream data = in.getInputStream(inEntry);
381 while ((num = data.read(buffer)) > 0) {
382 out.write(buffer, 0, num);
383 }
384 data.close();
385 out.closeEntry();
386 out.flush();
387 prevName = name;
388 }
389 if (numCrazy == 0) {
390 throw new AssertionError("There was no crazy library in the archive" );
391 }
392 }
393
394 private static void usage() {
395 System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
396 System.err.println("\"crazy\" libraries are always inflated in the outpu t");
397 System.err.println(
398 " renamealign - rename libraries with \"crazy.\" prefix and ad d alignment file");
399 System.err.println(" align - add alignment file");
400 System.err.println(" reorder - re-creates canonical ordering and c hecks alignment");
401 System.exit(2);
402 }
403
404 public static void main(String[] args) throws IOException {
405 if (args.length != 3) usage();
406
407 boolean addAlignment = false;
408 boolean rename = false;
409 if (args[0].equals("renamealign")) {
410 // Normal case. Before signing we rename the library and add an alig nment file.
411 addAlignment = true;
412 rename = true;
413 } else if (args[0].equals("align")) {
414 // LGPL compliance case. Before signing, we add an alignment file to a
415 // reconstructed APK which already contains the "crazy" library.
416 addAlignment = true;
417 rename = false;
418 } else if (args[0].equals("reorder")) {
419 // Normal case. After jarsigning we write the file in the canonical order and check.
420 addAlignment = false;
421 } else {
422 usage();
423 }
424
425 String inputFilename = args[1];
426 String outputFilename = args[2];
427
428 JarFile inputJar = null;
429 FileOutputStream outputFile = null;
430
431 try {
432 inputJar = new JarFile(new File(inputFilename), true);
433 outputFile = new FileOutputStream(outputFilename);
434
435 CountingOutputStream outCount = new CountingOutputStream(outputFile) ;
436 JarOutputStream outputJar = new JarOutputStream(outCount);
437
438 // Match the compression level used by SignApk.
439 outputJar.setLevel(9);
440
441 rezip(inputJar, outputJar, outCount, addAlignment, rename);
442 outputJar.close();
443 } finally {
444 if (inputJar != null) inputJar.close();
445 if (outputFile != null) outputFile.close();
446 }
447 }
448 }
OLDNEW
« no previous file with comments | « build/android/rezip/BUILD.gn ('k') | build/config/android/internal_rules.gni » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698