Chromium Code Reviews| 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 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 page align non-compressed libraries (*.so) in APK f iles. | |
| 23 * Tool is designed so that running SignApk and/or zipalign on the resulting APK does not | |
| 24 * break the page alignment. | |
| 25 */ | |
| 26 class PrealignApk { | |
|
rmcilroy
2014/09/29 11:38:32
I'm not sure Prealign is a very informative name f
Anton
2014/09/29 16:30:17
Done
rmcilroy
2014/09/30 09:36:32
I would prefer that there were only one tool (and
Anton
2014/09/30 10:30:01
Acknowledged.
| |
| 27 // Alignment to use for non-compressed files (must match zipalign). | |
| 28 private static final int ALIGNMENT = 4; | |
| 29 | |
| 30 // Alignment to use for non-compressed *.so files | |
| 31 private static final int LIBRARY_ALIGNMENT = 4096; | |
| 32 | |
| 33 // Files matching this pattern are not copied to the output when adding alig nment. | |
| 34 // When reordering and verifying the APK they are copied to the end of the f ile. | |
| 35 private static Pattern sMetaFilePattern = | |
| 36 Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert ))|(" + | |
| 37 Pattern.quote(JarFile.MANIFEST_NAME) + ")$"); | |
| 38 | |
| 39 /** | |
| 40 * Wraps another output stream, counting the number of bytes written. | |
| 41 */ | |
| 42 private static class CountingOutputStream extends OutputStream { | |
| 43 private long mCount = 0; | |
| 44 private OutputStream mOut; | |
| 45 | |
| 46 public CountingOutputStream(OutputStream out) { | |
| 47 this.mOut = out; | |
| 48 } | |
| 49 | |
| 50 /** Returns the number of bytes written. */ | |
| 51 public long getCount() { | |
| 52 return mCount; | |
| 53 } | |
| 54 | |
| 55 @Override public void write(byte[] b, int off, int len) throws IOExcepti on { | |
| 56 mOut.write(b, off, len); | |
| 57 mCount += len; | |
| 58 } | |
| 59 | |
| 60 @Override public void write(int b) throws IOException { | |
| 61 mOut.write(b); | |
| 62 mCount++; | |
| 63 } | |
| 64 | |
| 65 @Override public void close() throws IOException { | |
| 66 mOut.close(); | |
| 67 } | |
| 68 | |
| 69 @Override public void flush() throws IOException { | |
| 70 mOut.flush(); | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 /** | |
| 75 * Sort filenames in natural string order, except that filenames matching | |
| 76 * the meta-file pattern are always after other files. This is so the manife st | |
| 77 * and signature are at the end of the file after any alignment file. | |
| 78 */ | |
| 79 private static class FilenameComparator implements Comparator<String> { | |
| 80 @Override | |
| 81 public int compare(String o1, String o2) { | |
| 82 boolean o1Matches = sMetaFilePattern.matcher(o1).matches(); | |
| 83 boolean o2Matches = sMetaFilePattern.matcher(o2).matches(); | |
| 84 if (o1Matches != o2Matches) { | |
| 85 return o1Matches ? 1 : -1; | |
| 86 } else { | |
| 87 return o1.compareTo(o2); | |
| 88 } | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 // Build an ordered list of filenames. Using the same deterministic ordering used | |
| 93 // by SignApk. If addAlignment is true do not include the META-INF files. | |
| 94 private static List<String> orderFilenames(JarFile jar, boolean addAlignment ) { | |
|
rmcilroy
2014/09/29 11:38:32
/s/addAlignment/omitMetaFiles
Anton
2014/09/29 16:30:17
Done.
| |
| 95 List<String> names = new ArrayList<String>(); | |
| 96 for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) { | |
| 97 JarEntry entry = e.nextElement(); | |
| 98 if (entry.isDirectory()) { | |
| 99 continue; | |
| 100 } | |
| 101 if (addAlignment && | |
| 102 sMetaFilePattern.matcher(entry.getName()).matches()) { | |
| 103 continue; | |
| 104 } | |
| 105 names.add(entry.getName()); | |
| 106 } | |
| 107 | |
| 108 // We sort the input entries by name. When present META-INF files | |
| 109 // are sorted to the end. | |
| 110 Collections.sort(names, new FilenameComparator()); | |
| 111 return names; | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * If |addAlignment| is true add a zero filled alignment file at this point in the zip file, | |
| 116 * otherwise verify that the alignment is already correct. The added file wi ll be added | |
| 117 * before |name| and after |prevName|. The size of the alignment file is suc h that the | |
| 118 * location of the file |name| will be on a LIBRARY_ALIGNMENT boundary. | |
| 119 * | |
| 120 * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting | |
| 121 * file will not alter the alignment. | |
| 122 * | |
| 123 * @param offset number of bytes into the output file at this point. | |
| 124 * @param timestamp time in millis since the epoch to include in the header. | |
| 125 * @param name the name of the library filename. | |
| 126 * @param prevName the name of the previous file in the archive (or null). | |
| 127 * @param out jar output stream to write the alignment file to. | |
| 128 * @param addAlignment true to add the alignment file, false to verify align ment. | |
| 129 * | |
| 130 * @throws IOException if the output file can not be written. | |
| 131 */ | |
| 132 private static void addAlignmentFile( | |
| 133 long offset, long timestamp, String name, String prevName, | |
| 134 JarOutputStream out, boolean addAlignment) throws IOException { | |
| 135 | |
| 136 // Compute the start and alignment of the library, as if it was next. | |
| 137 int headerSize = JarFile.LOCHDR + name.length(); | |
| 138 long libOffset = offset + headerSize; | |
| 139 int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT ); | |
| 140 if (libNeeded == LIBRARY_ALIGNMENT) { | |
| 141 // Already aligned, no need to added alignment file. | |
| 142 return; | |
| 143 } | |
| 144 | |
| 145 if (!addAlignment) { | |
| 146 // If we are not adding alignment then it should already be correct. | |
| 147 throw new AssertionError( | |
| 148 "Library was not page aligned when verifying page alignment. " | |
| 149 + "Library name: " + name + " Expected alignment: " + LIBRARY_AL IGNMENT | |
| 150 + "Offset: " + offset + " Error: " + (libOffset % LIBRARY_ALIGNM ENT)); | |
| 151 } | |
| 152 | |
| 153 // Check that there is not another file between the library and the | |
| 154 // alignment file. | |
| 155 String alignName = name.substring(0, name.length() - 2) + "align"; | |
| 156 if (prevName != null && prevName.compareTo(alignName) >= 0) { | |
| 157 throw new UnsupportedOperationException( | |
| 158 "Unable to insert alignment file, because there is " | |
| 159 + "another file in front of the file to be aligned. " | |
| 160 + "Other file: " + prevName + " Alignment file: " + alignName); | |
| 161 } | |
| 162 | |
| 163 // Compute the size of the alignment file header. | |
| 164 headerSize = JarFile.LOCHDR + alignName.length(); | |
| 165 // We are going to add an alignment file of type STORED. This file | |
| 166 // will itself induce a zipalign alignment adjustment. | |
| 167 int extraNeeded = | |
| 168 (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNM ENT; | |
| 169 headerSize += extraNeeded; | |
| 170 | |
| 171 if (libNeeded < headerSize + 1) { | |
| 172 // The header was bigger than the alignment that we need, add anothe r page. | |
| 173 libNeeded += LIBRARY_ALIGNMENT; | |
| 174 } | |
| 175 // Compute the size of the alignment file. | |
| 176 libNeeded -= headerSize; | |
| 177 | |
| 178 // Build the header for the alignment file. | |
| 179 byte[] zeroBuffer = new byte[libNeeded]; | |
| 180 JarEntry alignEntry = new JarEntry(alignName); | |
| 181 alignEntry.setMethod(JarEntry.STORED); | |
| 182 alignEntry.setSize(libNeeded); | |
| 183 alignEntry.setTime(timestamp); | |
| 184 CRC32 crc = new CRC32(); | |
| 185 crc.update(zeroBuffer); | |
| 186 alignEntry.setCrc(crc.getValue()); | |
| 187 | |
| 188 if (extraNeeded != 0) { | |
| 189 alignEntry.setExtra(new byte[extraNeeded]); | |
| 190 } | |
| 191 | |
| 192 // Output the alignment file. | |
| 193 out.putNextEntry(alignEntry); | |
| 194 out.write(zeroBuffer); | |
| 195 out.closeEntry(); | |
| 196 out.flush(); | |
| 197 } | |
| 198 | |
| 199 /** | |
| 200 * Copy the contents of the input APK file to the output APK file. Uncompres sed files | |
| 201 * will be aligned in the output stream. Uncompressed native code libraries (*.so) | |
| 202 * will be aligned on a page boundary. Page alignment is implemented by addi ng a | |
| 203 * zero filled file, regular alignment is implemented by adding a zero fille d extra | |
| 204 * field to the zip file header. Care is take so that the output generated i n the | |
| 205 * same way as SignApk. This is important so that running SignApk and zipali gn on | |
| 206 * the output does not break the page alignment. The archive may not contain a "*.apk" | |
| 207 * as SignApk has special nested signing logic that we do not support. | |
| 208 * | |
| 209 * @param manifest The Manifest naming all files to copy | |
| 210 * @param in The input APK File. | |
| 211 * @param out The output APK stream. | |
| 212 * @param countOut Counting output stream (to measure the current offset). | |
| 213 * @param timestamp The timestamp (millis since epoch) for all entries in th e output. | |
| 214 * @param addAlignment Whether to add the alignment file or just check. | |
| 215 * | |
| 216 * @throws IOException if the output file can not be written. | |
| 217 */ | |
| 218 private static void copyAndAlignFiles( | |
| 219 List<String> names, JarFile in, JarOutputStream out, CountingOutputStrea m countOut, | |
| 220 long timestamp, boolean addAlignment) throws IOException { | |
| 221 | |
| 222 byte[] buffer = new byte[4096]; | |
| 223 boolean firstEntry = true; | |
| 224 String prevName = null; | |
| 225 for (String name : names) { | |
| 226 JarEntry inEntry = in.getJarEntry(name); | |
| 227 JarEntry outEntry = null; | |
| 228 if (name.endsWith(".apk")) { | |
| 229 throw new UnsupportedOperationException( | |
| 230 "Nested APKs are not supported: " + name); | |
| 231 } | |
| 232 if (inEntry.getMethod() == JarEntry.STORED) { | |
| 233 // Preserve the STORED method of the input entry. | |
| 234 outEntry = new JarEntry(inEntry); | |
| 235 outEntry.setExtra(null); | |
| 236 } else { | |
| 237 // Create a new entry so that the compressed len is recomputed. | |
| 238 outEntry = new JarEntry(name); | |
| 239 } | |
| 240 outEntry.setTime(timestamp); | |
| 241 | |
| 242 long offset = countOut.getCount(); | |
| 243 if (firstEntry) { | |
| 244 // The first entry in a jar file has an extra field of | |
| 245 // four bytes that you can't get rid of; any extra | |
| 246 // data you specify in the JarEntry is appended to | |
| 247 // these forced four bytes. This is JAR_MAGIC in | |
| 248 // JarOutputStream; the bytes are 0xfeca0000. | |
| 249 firstEntry = false; | |
| 250 offset += 4; | |
| 251 } | |
| 252 if (inEntry.getMethod() == JarEntry.STORED) { | |
| 253 if (LIBRARY_ALIGNMENT > 0 && name.endsWith(".so")) { | |
| 254 addAlignmentFile( | |
| 255 offset, timestamp, name, prevName, out, addAlignment); | |
|
rmcilroy
2014/09/29 11:38:32
nit - could we just do the addAlignmentFile method
Anton
2014/09/29 16:30:17
Done.
| |
| 256 if (addAlignment) { | |
| 257 // We check that we did indeed get to a page boundary. | |
| 258 offset = countOut.getCount() + JarFile.LOCHDR + name.len gth(); | |
| 259 if ((offset % LIBRARY_ALIGNMENT) != 0) { | |
| 260 throw new AssertionError( | |
| 261 "File was not aligned after adding alignment fil e. " | |
| 262 + "offset = " + offset | |
| 263 + " LIBRARY_ALIGNMENT = " + LIBRARY_ALIGNMENT); | |
| 264 } | |
| 265 } | |
| 266 } else if (ALIGNMENT > 0) { | |
|
rmcilroy
2014/09/29 11:38:32
is it worth having this check given ALIGNMENT is a
Anton
2014/09/29 16:30:17
Removed. Compiler will take them out anyway, but t
| |
| 267 offset += JarFile.LOCHDR + name.length(); | |
| 268 int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIG NMENT; | |
| 269 if (needed != 0) { | |
| 270 outEntry.setExtra(new byte[needed]); | |
| 271 } | |
| 272 } | |
| 273 } | |
| 274 out.putNextEntry(outEntry); | |
| 275 | |
| 276 int num; | |
| 277 InputStream data = in.getInputStream(inEntry); | |
| 278 while ((num = data.read(buffer)) > 0) { | |
| 279 out.write(buffer, 0, num); | |
| 280 } | |
| 281 out.closeEntry(); | |
| 282 out.flush(); | |
| 283 prevName = name; | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 private static void usage() { | |
| 288 System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk"); | |
| 289 System.err.println(" addalignment - adds alignment file removes manifes t and signature"); | |
| 290 System.err.println(" reorder - re-creates canonical ordering check s alignment"); | |
|
rmcilroy
2014/09/29 11:38:32
/s/re-creates canonical ordering checks alignment/
Anton
2014/09/29 16:30:17
Done.
| |
| 291 System.exit(2); | |
| 292 } | |
| 293 | |
| 294 public static void main(String[] args) { | |
| 295 if (args.length != 3) usage(); | |
| 296 | |
| 297 boolean addAlignment = false; | |
| 298 if (args[0].equals("addalignment")) { | |
| 299 addAlignment = true; | |
| 300 } else if (args[0].equals("reorder")) { | |
| 301 addAlignment = false; | |
| 302 } else { | |
| 303 usage(); | |
| 304 } | |
| 305 | |
| 306 String inputFilename = args[1]; | |
| 307 String outputFilename = args[2]; | |
| 308 | |
| 309 JarFile inputJar = null; | |
| 310 FileOutputStream outputFile = null; | |
| 311 | |
| 312 try { | |
| 313 inputJar = new JarFile(new File(inputFilename), false); // Don't ve rify. | |
|
rmcilroy
2014/09/29 11:38:32
Is there a reason not to verify? If so, could you
Anton
2014/09/29 16:30:17
I am not sure of the merits of verifying existing
| |
| 314 outputFile = new FileOutputStream(outputFilename); | |
| 315 | |
| 316 CountingOutputStream outCount = new CountingOutputStream(outputFile) ; | |
| 317 JarOutputStream outputJar = new JarOutputStream(outCount); | |
| 318 | |
| 319 // Match the compression level used by SignApk. | |
| 320 outputJar.setLevel(9); | |
| 321 | |
| 322 long timestamp = System.currentTimeMillis(); | |
| 323 | |
| 324 copyAndAlignFiles( | |
| 325 orderFilenames(inputJar, addAlignment), inputJar, outputJar, out Count, | |
| 326 timestamp, addAlignment); | |
|
rmcilroy
2014/09/29 11:38:32
nit - the "List<String> names" and "timestamp" arg
Anton
2014/09/29 16:30:17
Done.
| |
| 327 outputJar.close(); | |
| 328 } catch (Exception e) { | |
| 329 e.printStackTrace(); | |
| 330 System.exit(1); | |
| 331 } finally { | |
| 332 try { | |
| 333 if (inputJar != null) inputJar.close(); | |
| 334 if (outputFile != null) outputFile.close(); | |
| 335 } catch (IOException e) { | |
| 336 e.printStackTrace(); | |
| 337 System.exit(1); | |
| 338 } | |
| 339 } | |
| 340 } | |
| 341 } | |
| OLD | NEW |