From 1d67eec191b76e299acfa54d95f3897e25536174 Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Thu, 15 May 2014 09:54:26 -0700 Subject: [PATCH] make SignApk do zip alignment When signing an APK, make the SignApk tool align the stored entries to (by default) 4-byte boundaries. This obviates the need to run the separate zipalign tool, which currently does this job. The alignment byte count can be specified with the -a option. OTA package signing (with -w) never does alignment. The order of files in the output APK is changed so that all stored files come first in the output, followed by all non-stored files. This is not expected to have any impact in practice. Change-Id: Iaeef89b2a7283e25fadb99c0a0f0641f682d76b8 --- tools/signapk/SignApk.java | 76 +++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java index b2470722c..e661e506f 100644 --- a/tools/signapk/SignApk.java +++ b/tools/signapk/SignApk.java @@ -461,24 +461,75 @@ class SignApk { * reduce variation in the output file and make incremental OTAs * more efficient. */ - private static void copyFiles(Manifest manifest, - JarFile in, JarOutputStream out, long timestamp) throws IOException { + private static void copyFiles(Manifest manifest, JarFile in, JarOutputStream out, + long timestamp, int alignment) throws IOException { byte[] buffer = new byte[4096]; int num; Map entries = manifest.getEntries(); ArrayList names = new ArrayList(entries.keySet()); Collections.sort(names); + + boolean firstEntry = true; + long offset = 0L; + + // We do the copy in two passes -- first copying all the + // entries that are STORED, then copying all the entries that + // have any other compression flag (which in practice means + // DEFLATED). This groups all the stored entries together at + // the start of the file and makes it easier to do alignment + // on them (since only stored entries are aligned). + for (String name : names) { JarEntry inEntry = in.getJarEntry(name); JarEntry outEntry = null; - if (inEntry.getMethod() == JarEntry.STORED) { - // Preserve the STORED method of the input entry. - outEntry = new JarEntry(inEntry); - } else { - // Create a new entry so that the compressed len is recomputed. - outEntry = new JarEntry(name); + if (inEntry.getMethod() != JarEntry.STORED) continue; + // Preserve the STORED method of the input entry. + outEntry = new JarEntry(inEntry); + outEntry.setTime(timestamp); + + // 'offset' is the offset into the file at which we expect + // the file data to begin. This is the value we need to + // make a multiple of 'alignement'. + offset += JarFile.LOCHDR + outEntry.getName().length(); + 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. + offset += 4; + firstEntry = false; } + if (alignment > 0 && (offset % alignment != 0)) { + // Set the "extra data" of the entry to between 1 and + // alignment-1 bytes, to make the file data begin at + // an aligned offset. + int needed = alignment - (int)(offset % alignment); + outEntry.setExtra(new byte[needed]); + offset += needed; + } + + out.putNextEntry(outEntry); + + InputStream data = in.getInputStream(inEntry); + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + offset += num; + } + out.flush(); + } + + // Copy all the non-STORED entries. We don't attempt to + // maintain the 'offset' variable past this point; we don't do + // alignment on these entries. + + for (String name : names) { + JarEntry inEntry = in.getJarEntry(name); + JarEntry outEntry = null; + if (inEntry.getMethod() == JarEntry.STORED) continue; + // Create a new entry so that the compressed len is recomputed. + outEntry = new JarEntry(name); outEntry.setTime(timestamp); out.putNextEntry(outEntry); @@ -589,7 +640,7 @@ class SignApk { long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; Manifest manifest = addDigestsToManifest(inputJar, hash); - copyFiles(manifest, inputJar, outputJar, timestamp); + copyFiles(manifest, inputJar, outputJar, timestamp, 0); addOtacert(outputJar, publicKeyFile, timestamp, manifest, hash); signFile(manifest, inputJar, @@ -778,6 +829,7 @@ class SignApk { private static void usage() { System.err.println("Usage: signapk [-w] " + + "[-a ] " + "[-providerClass ] " + "publickey.x509[.pem] privatekey.pk8 " + "[publickey2.x509[.pem] privatekey2.pk8 ...] " + @@ -794,6 +846,7 @@ class SignApk { boolean signWholeFile = false; String providerClass = null; String providerArg = null; + int alignment = 4; int argstart = 0; while (argstart < args.length && args[argstart].startsWith("-")) { @@ -806,6 +859,9 @@ class SignApk { } providerClass = args[++argstart]; ++argstart; + } else if ("-a".equals(args[argstart])) { + alignment = Integer.parseInt(args[++argstart]); + ++argstart; } else { usage(); } @@ -872,7 +928,7 @@ class SignApk { outputJar.setLevel(9); Manifest manifest = addDigestsToManifest(inputJar, hashes); - copyFiles(manifest, inputJar, outputJar, timestamp); + copyFiles(manifest, inputJar, outputJar, timestamp, alignment); signFile(manifest, inputJar, publicKey, privateKey, outputJar); outputJar.close(); }