Index: build/android/rezip/RezipApk.java |
diff --git a/build/android/rezip/RezipApk.java b/build/android/rezip/RezipApk.java |
index 0349d3b5efa2e53b3e8c186a3c85b3eb7938ef7c..fcb0703aca18cfc7c45b1a324c1e0fb7f2350331 100644 |
--- a/build/android/rezip/RezipApk.java |
+++ b/build/android/rezip/RezipApk.java |
@@ -19,9 +19,15 @@ import java.util.regex.Pattern; |
import java.util.zip.CRC32; |
/** |
- * Command line tool used to page align non-compressed libraries (*.so) in APK files. |
- * Tool is designed so that running SignApk and/or zipalign on the resulting APK does not |
- * break the page alignment. |
+ * 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). |
@@ -36,6 +42,31 @@ class RezipApk { |
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. |
*/ |
@@ -71,14 +102,32 @@ class RezipApk { |
} |
} |
+ private static String outputName(JarEntry entry, boolean rename) { |
+ String inName = entry.getName(); |
+ if (rename && entry.getSize() > 0 && isLibraryFilename(inName)) { |
+ return renameLibraryForCrazyLinker(inName); |
+ } |
+ return inName; |
+ } |
+ |
/** |
- * Sort filenames in natural string order, except that filenames matching |
+ * 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 FilenameComparator implements Comparator<String> { |
+ private static class EntryComparator implements Comparator<JarEntry> { |
+ private boolean mRename; |
+ |
+ public EntryComparator(boolean rename) { |
+ mRename = rename; |
+ } |
+ |
@Override |
- public int compare(String o1, String o2) { |
+ 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) { |
@@ -89,10 +138,13 @@ class RezipApk { |
} |
} |
- // Build an ordered list of filenames. Using the same deterministic ordering used |
- // by SignApk. If omitMetaFiles is true do not include the META-INF files. |
- private static List<String> orderFilenames(JarFile jar, boolean omitMetaFiles) { |
- List<String> names = new ArrayList<String>(); |
+ // 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()) { |
@@ -102,13 +154,13 @@ class RezipApk { |
sMetaFilePattern.matcher(entry.getName()).matches()) { |
continue; |
} |
- names.add(entry.getName()); |
+ entries.add(entry); |
} |
// We sort the input entries by name. When present META-INF files |
// are sorted to the end. |
- Collections.sort(names, new FilenameComparator()); |
- return names; |
+ Collections.sort(entries, new EntryComparator(rename)); |
+ return entries; |
} |
/** |
@@ -148,7 +200,8 @@ class RezipApk { |
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); |
+ + "Other file: " + prevName + " Alignment file: " + alignName |
+ + " file: " + name); |
} |
// Compute the size of the alignment file header. |
@@ -187,40 +240,98 @@ class RezipApk { |
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. Uncompressed files |
- * will be aligned in the output stream. Uncompressed native code libraries (*.so) |
- * will be aligned on a page boundary. 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. Care is take so that the output 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. |
+ * 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 copyAndAlignFiles( |
+ private static void rezip( |
JarFile in, JarOutputStream out, CountingOutputStream countOut, |
- boolean addAlignment) throws IOException { |
+ boolean addAlignment, boolean rename) throws IOException { |
- List<String> names = orderFilenames(in, addAlignment); |
+ List<JarEntry> entries = getOutputFileOrderEntries(in, addAlignment, rename); |
long timestamp = System.currentTimeMillis(); |
byte[] buffer = new byte[4096]; |
boolean firstEntry = true; |
String prevName = null; |
- for (String name : names) { |
- JarEntry inEntry = in.getJarEntry(name); |
- JarEntry outEntry = 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); |
+ "Nested APKs are not supported: " + name); |
} |
- if (inEntry.getMethod() == JarEntry.STORED) { |
+ |
+ // 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_file'.\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); |
@@ -230,6 +341,7 @@ class RezipApk { |
} |
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 |
@@ -240,8 +352,8 @@ class RezipApk { |
firstEntry = false; |
offset += 4; |
} |
- if (inEntry.getMethod() == JarEntry.STORED) { |
- if (name.endsWith(".so")) { |
+ if (outEntry.getMethod() == JarEntry.STORED) { |
+ if (isCrazy) { |
if (addAlignment) { |
addAlignmentFile(offset, timestamp, name, prevName, out); |
} |
@@ -249,11 +361,13 @@ class RezipApk { |
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)); |
+ "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) { |
@@ -263,21 +377,33 @@ class RezipApk { |
} |
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(" addalignment - adds alignment file removes manifest and signature"); |
- System.err.println(" reorder - re-creates canonical ordering and checks alignment"); |
+ 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); |
} |
@@ -285,9 +411,18 @@ class RezipApk { |
if (args.length != 3) usage(); |
boolean addAlignment = false; |
- if (args[0].equals("addalignment")) { |
+ 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(); |
@@ -309,7 +444,7 @@ class RezipApk { |
// Match the compression level used by SignApk. |
outputJar.setLevel(9); |
- copyAndAlignFiles(inputJar, outputJar, outCount, addAlignment); |
+ rezip(inputJar, outputJar, outCount, addAlignment, rename); |
outputJar.close(); |
} finally { |
if (inputJar != null) inputJar.close(); |