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

Unified 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 side-by-side diff with in-line comments
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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/android/rezip/RezipApk.java
diff --git a/build/android/rezip/RezipApk.java b/build/android/rezip/RezipApk.java
deleted file mode 100644
index 43d75447c8f5f27011d96a32f0bf359a58f495d5..0000000000000000000000000000000000000000
--- a/build/android/rezip/RezipApk.java
+++ /dev/null
@@ -1,448 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.regex.Pattern;
-import java.util.zip.CRC32;
-
-/**
- * Command line tool used to build APKs which support loading the native code library
- * directly from the APK file. To construct the APK we rename the native library by
- * adding the prefix "crazy." to the filename. This is done to prevent the Android
- * Package Manager from extracting the library. The native code must be page aligned
- * and uncompressed. The page alignment is implemented by adding a zero filled file
- * in front of the the native code library. This tool is designed so that running
- * SignApk and/or zipalign on the resulting APK does not break the page alignment.
- * This is achieved by outputing the filenames in the same canonical order used
- * by SignApk and adding the same alignment fields added by zipalign.
- */
-class RezipApk {
- // Alignment to use for non-compressed files (must match zipalign).
- private static final int ALIGNMENT = 4;
-
- // Alignment to use for non-compressed *.so files
- private static final int LIBRARY_ALIGNMENT = 4096;
-
- // Files matching this pattern are not copied to the output when adding alignment.
- // When reordering and verifying the APK they are copied to the end of the file.
- private static Pattern sMetaFilePattern =
- Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|("
- + Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
-
- // Pattern for matching a shared library in the APK
- private static Pattern sLibraryPattern = Pattern.compile("^lib/[^/]*/lib.*[.]so$");
- // Pattern for match the crazy linker in the APK
- private static Pattern sCrazyLinkerPattern =
- Pattern.compile("^lib/[^/]*/libchromium_android_linker.so$");
- // Pattern for matching a crazy loaded shared library in the APK
- private static Pattern sCrazyLibraryPattern = Pattern.compile("^lib/[^/]*/crazy.lib.*[.]so$");
-
- private static boolean isLibraryFilename(String filename) {
- return sLibraryPattern.matcher(filename).matches()
- && !sCrazyLinkerPattern.matcher(filename).matches();
- }
-
- private static boolean isCrazyLibraryFilename(String filename) {
- return sCrazyLibraryPattern.matcher(filename).matches();
- }
-
- private static String renameLibraryForCrazyLinker(String filename) {
- int lastSlash = filename.lastIndexOf('/');
- // We rename the library, so that the Android Package Manager
- // no longer extracts the library.
- return filename.substring(0, lastSlash + 1) + "crazy." + filename.substring(lastSlash + 1);
- }
-
- /**
- * Wraps another output stream, counting the number of bytes written.
- */
- private static class CountingOutputStream extends OutputStream {
- private long mCount = 0;
- private OutputStream mOut;
-
- public CountingOutputStream(OutputStream out) {
- this.mOut = out;
- }
-
- /** Returns the number of bytes written. */
- public long getCount() {
- return mCount;
- }
-
- @Override public void write(byte[] b, int off, int len) throws IOException {
- mOut.write(b, off, len);
- mCount += len;
- }
-
- @Override public void write(int b) throws IOException {
- mOut.write(b);
- mCount++;
- }
-
- @Override public void close() throws IOException {
- mOut.close();
- }
-
- @Override public void flush() throws IOException {
- mOut.flush();
- }
- }
-
- private static String outputName(JarEntry entry, boolean rename) {
- String inName = entry.getName();
- if (rename && entry.getSize() > 0 && isLibraryFilename(inName)) {
- return renameLibraryForCrazyLinker(inName);
- }
- return inName;
- }
-
- /**
- * Comparator used to sort jar entries from the input file.
- * Sorting is done based on the output filename (which maybe renamed).
- * Filenames are in natural string order, except that filenames matching
- * the meta-file pattern are always after other files. This is so the manifest
- * and signature are at the end of the file after any alignment file.
- */
- private static class EntryComparator implements Comparator<JarEntry> {
- private boolean mRename;
-
- public EntryComparator(boolean rename) {
- mRename = rename;
- }
-
- @Override
- public int compare(JarEntry j1, JarEntry j2) {
- String o1 = outputName(j1, mRename);
- String o2 = outputName(j2, mRename);
- boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
- boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
- if (o1Matches != o2Matches) {
- return o1Matches ? 1 : -1;
- } else {
- return o1.compareTo(o2);
- }
- }
- }
-
- // Build an ordered list of jar entries. The jar entries from the input are
- // sorted based on the output filenames (which maybe renamed). If |omitMetaFiles|
- // is true do not include the jar entries for the META-INF files.
- // Entries are ordered in the deterministic order used by SignApk.
- private static List<JarEntry> getOutputFileOrderEntries(
- JarFile jar, boolean omitMetaFiles, boolean rename) {
- List<JarEntry> entries = new ArrayList<JarEntry>();
- for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
- JarEntry entry = e.nextElement();
- if (entry.isDirectory()) {
- continue;
- }
- if (omitMetaFiles && sMetaFilePattern.matcher(entry.getName()).matches()) {
- continue;
- }
- entries.add(entry);
- }
-
- // We sort the input entries by name. When present META-INF files
- // are sorted to the end.
- Collections.sort(entries, new EntryComparator(rename));
- return entries;
- }
-
- /**
- * Add a zero filled alignment file at this point in the zip file,
- * The added file will be added before |name| and after |prevName|.
- * The size of the alignment file is such that the location of the
- * file |name| will be on a LIBRARY_ALIGNMENT boundary.
- *
- * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
- * file will not alter the alignment.
- *
- * @param offset number of bytes into the output file at this point.
- * @param timestamp time in millis since the epoch to include in the header.
- * @param name the name of the library filename.
- * @param prevName the name of the previous file in the archive (or null).
- * @param out jar output stream to write the alignment file to.
- *
- * @throws IOException if the output file can not be written.
- */
- private static void addAlignmentFile(
- long offset, long timestamp, String name, String prevName,
- JarOutputStream out) throws IOException {
-
- // Compute the start and alignment of the library, as if it was next.
- int headerSize = JarFile.LOCHDR + name.length();
- long libOffset = offset + headerSize;
- int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT);
- if (libNeeded == LIBRARY_ALIGNMENT) {
- // Already aligned, no need to added alignment file.
- return;
- }
-
- // Check that there is not another file between the library and the
- // alignment file.
- String alignName = name.substring(0, name.length() - 2) + "align";
- if (prevName != null && prevName.compareTo(alignName) >= 0) {
- throw new UnsupportedOperationException(
- "Unable to insert alignment file, because there is "
- + "another file in front of the file to be aligned. "
- + "Other file: " + prevName + " Alignment file: " + alignName
- + " file: " + name);
- }
-
- // Compute the size of the alignment file header.
- headerSize = JarFile.LOCHDR + alignName.length();
- // We are going to add an alignment file of type STORED. This file
- // will itself induce a zipalign alignment adjustment.
- int extraNeeded =
- (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNMENT;
- headerSize += extraNeeded;
-
- if (libNeeded < headerSize + 1) {
- // The header was bigger than the alignment that we need, add another page.
- libNeeded += LIBRARY_ALIGNMENT;
- }
- // Compute the size of the alignment file.
- libNeeded -= headerSize;
-
- // Build the header for the alignment file.
- byte[] zeroBuffer = new byte[libNeeded];
- JarEntry alignEntry = new JarEntry(alignName);
- alignEntry.setMethod(JarEntry.STORED);
- alignEntry.setSize(libNeeded);
- alignEntry.setTime(timestamp);
- CRC32 crc = new CRC32();
- crc.update(zeroBuffer);
- alignEntry.setCrc(crc.getValue());
-
- if (extraNeeded != 0) {
- alignEntry.setExtra(new byte[extraNeeded]);
- }
-
- // Output the alignment file.
- out.putNextEntry(alignEntry);
- out.write(zeroBuffer);
- out.closeEntry();
- out.flush();
- }
-
- // Make a JarEntry for the output file which corresponds to the input
- // file. The output file will be called |name|. The output file will always
- // be uncompressed (STORED). If the input is not STORED it is necessary to inflate
- // it to compute the CRC and size of the output entry.
- private static JarEntry makeStoredEntry(String name, JarEntry inEntry, JarFile in)
- throws IOException {
- JarEntry outEntry = new JarEntry(name);
- outEntry.setMethod(JarEntry.STORED);
-
- if (inEntry.getMethod() == JarEntry.STORED) {
- outEntry.setCrc(inEntry.getCrc());
- outEntry.setSize(inEntry.getSize());
- } else {
- // We are inflating the file. We need to compute the CRC and size.
- byte[] buffer = new byte[4096];
- CRC32 crc = new CRC32();
- int size = 0;
- int num;
- InputStream data = in.getInputStream(inEntry);
- while ((num = data.read(buffer)) > 0) {
- crc.update(buffer, 0, num);
- size += num;
- }
- data.close();
- outEntry.setCrc(crc.getValue());
- outEntry.setSize(size);
- }
- return outEntry;
- }
-
- /**
- * Copy the contents of the input APK file to the output APK file. If |rename| is
- * true then non-empty libraries (*.so) in the input will be renamed by prefixing
- * "crazy.". This is done to prevent the Android Package Manager extracting the
- * library. Note the crazy linker itself is not renamed, for bootstrapping reasons.
- * Empty libraries are not renamed (they are in the APK to workaround a bug where
- * the Android Package Manager fails to delete old versions when upgrading).
- * There must be exactly one "crazy" library in the output stream. The "crazy"
- * library will be uncompressed and page aligned in the output stream. Page
- * alignment is implemented by adding a zero filled file, regular alignment is
- * implemented by adding a zero filled extra field to the zip file header. If
- * |addAlignment| is true a page alignment file is added, otherwise the "crazy"
- * library must already be page aligned. Care is taken so that the output is generated
- * in the same way as SignApk. This is important so that running SignApk and
- * zipalign on the output does not break the page alignment. The archive may not
- * contain a "*.apk" as SignApk has special nested signing logic that we do not
- * support.
- *
- * @param in The input APK File.
- * @param out The output APK stream.
- * @param countOut Counting output stream (to measure the current offset).
- * @param addAlignment Whether to add the alignment file or just check.
- * @param rename Whether to rename libraries to be "crazy".
- *
- * @throws IOException if the output file can not be written.
- */
- private static void rezip(
- JarFile in, JarOutputStream out, CountingOutputStream countOut,
- boolean addAlignment, boolean rename) throws IOException {
-
- List<JarEntry> entries = getOutputFileOrderEntries(in, addAlignment, rename);
- long timestamp = System.currentTimeMillis();
- byte[] buffer = new byte[4096];
- boolean firstEntry = true;
- String prevName = null;
- int numCrazy = 0;
- for (JarEntry inEntry : entries) {
- // Rename files, if specied.
- String name = outputName(inEntry, rename);
- if (name.endsWith(".apk")) {
- throw new UnsupportedOperationException(
- "Nested APKs are not supported: " + name);
- }
-
- // Build the header.
- JarEntry outEntry = null;
- boolean isCrazy = isCrazyLibraryFilename(name);
- if (isCrazy) {
- // "crazy" libraries are alway output uncompressed (STORED).
- outEntry = makeStoredEntry(name, inEntry, in);
- numCrazy++;
- if (numCrazy > 1) {
- throw new UnsupportedOperationException(
- "Found more than one library\n"
- + "Multiple libraries are not supported for APKs that use "
- + "'load_library_from_zip'.\n"
- + "See crbug/388223.\n"
- + "Note, check that your build is clean.\n"
- + "An unclean build can incorrectly incorporate old "
- + "libraries in the APK.");
- }
- } else if (inEntry.getMethod() == JarEntry.STORED) {
- // Preserve the STORED method of the input entry.
- outEntry = new JarEntry(inEntry);
- outEntry.setExtra(null);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- outEntry = new JarEntry(name);
- }
- outEntry.setTime(timestamp);
-
- // Compute and add alignment
- long offset = countOut.getCount();
- if (firstEntry) {
- // The first entry in a jar file has an extra field of
- // four bytes that you can't get rid of; any extra
- // data you specify in the JarEntry is appended to
- // these forced four bytes. This is JAR_MAGIC in
- // JarOutputStream; the bytes are 0xfeca0000.
- firstEntry = false;
- offset += 4;
- }
- if (outEntry.getMethod() == JarEntry.STORED) {
- if (isCrazy) {
- if (addAlignment) {
- addAlignmentFile(offset, timestamp, name, prevName, out);
- }
- // We check that we did indeed get to a page boundary.
- offset = countOut.getCount() + JarFile.LOCHDR + name.length();
- if ((offset % LIBRARY_ALIGNMENT) != 0) {
- throw new AssertionError(
- "Library was not page aligned when verifying page alignment. "
- + "Library name: " + name + " Expected alignment: "
- + LIBRARY_ALIGNMENT + "Offset: " + offset + " Error: "
- + (offset % LIBRARY_ALIGNMENT));
- }
- } else {
- // This is equivalent to zipalign.
- offset += JarFile.LOCHDR + name.length();
- int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIGNMENT;
- if (needed != 0) {
- outEntry.setExtra(new byte[needed]);
- }
- }
- }
- out.putNextEntry(outEntry);
-
- // Copy the data from the input to the output
- int num;
- InputStream data = in.getInputStream(inEntry);
- while ((num = data.read(buffer)) > 0) {
- out.write(buffer, 0, num);
- }
- data.close();
- out.closeEntry();
- out.flush();
- prevName = name;
- }
- if (numCrazy == 0) {
- throw new AssertionError("There was no crazy library in the archive");
- }
- }
-
- private static void usage() {
- System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
- System.err.println("\"crazy\" libraries are always inflated in the output");
- System.err.println(
- " renamealign - rename libraries with \"crazy.\" prefix and add alignment file");
- System.err.println(" align - add alignment file");
- System.err.println(" reorder - re-creates canonical ordering and checks alignment");
- System.exit(2);
- }
-
- public static void main(String[] args) throws IOException {
- if (args.length != 3) usage();
-
- boolean addAlignment = false;
- boolean rename = false;
- if (args[0].equals("renamealign")) {
- // Normal case. Before signing we rename the library and add an alignment file.
- addAlignment = true;
- rename = true;
- } else if (args[0].equals("align")) {
- // LGPL compliance case. Before signing, we add an alignment file to a
- // reconstructed APK which already contains the "crazy" library.
- addAlignment = true;
- rename = false;
- } else if (args[0].equals("reorder")) {
- // Normal case. After jarsigning we write the file in the canonical order and check.
- addAlignment = false;
- } else {
- usage();
- }
-
- String inputFilename = args[1];
- String outputFilename = args[2];
-
- JarFile inputJar = null;
- FileOutputStream outputFile = null;
-
- try {
- inputJar = new JarFile(new File(inputFilename), true);
- outputFile = new FileOutputStream(outputFilename);
-
- CountingOutputStream outCount = new CountingOutputStream(outputFile);
- JarOutputStream outputJar = new JarOutputStream(outCount);
-
- // Match the compression level used by SignApk.
- outputJar.setLevel(9);
-
- rezip(inputJar, outputJar, outCount, addAlignment, rename);
- outputJar.close();
- } finally {
- if (inputJar != null) inputJar.close();
- if (outputFile != null) outputFile.close();
- }
- }
-}
« 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