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 RezipApk { | |
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 omitMetaFiles is true do not include the META-INF files. | |
94 private static List<String> orderFilenames(JarFile jar, boolean omitMetaFile s) { | |
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 (omitMetaFiles && | |
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 * Add a zero filled alignment file at this point in the zip file, | |
116 * The added file will be added before |name| and after |prevName|. | |
117 * The size of the alignment file is such that the location of the | |
118 * 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 * | |
129 * @throws IOException if the output file can not be written. | |
130 */ | |
131 private static void addAlignmentFile( | |
132 long offset, long timestamp, String name, String prevName, | |
133 JarOutputStream out) throws IOException { | |
rmcilroy
2014/09/30 09:36:33
Fix alignment
Anton
2014/09/30 10:30:01
Done.
| |
134 | |
135 // Compute the start and alignment of the library, as if it was next. | |
136 int headerSize = JarFile.LOCHDR + name.length(); | |
137 long libOffset = offset + headerSize; | |
138 int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT ); | |
139 if (libNeeded == LIBRARY_ALIGNMENT) { | |
140 // Already aligned, no need to added alignment file. | |
141 return; | |
142 } | |
143 | |
144 // Check that there is not another file between the library and the | |
145 // alignment file. | |
146 String alignName = name.substring(0, name.length() - 2) + "align"; | |
147 if (prevName != null && prevName.compareTo(alignName) >= 0) { | |
148 throw new UnsupportedOperationException( | |
149 "Unable to insert alignment file, because there is " | |
150 + "another file in front of the file to be aligned. " | |
151 + "Other file: " + prevName + " Alignment file: " + alignName); | |
152 } | |
153 | |
154 // Compute the size of the alignment file header. | |
155 headerSize = JarFile.LOCHDR + alignName.length(); | |
156 // We are going to add an alignment file of type STORED. This file | |
157 // will itself induce a zipalign alignment adjustment. | |
158 int extraNeeded = | |
159 (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNM ENT; | |
160 headerSize += extraNeeded; | |
161 | |
162 if (libNeeded < headerSize + 1) { | |
163 // The header was bigger than the alignment that we need, add anothe r page. | |
164 libNeeded += LIBRARY_ALIGNMENT; | |
165 } | |
166 // Compute the size of the alignment file. | |
167 libNeeded -= headerSize; | |
168 | |
169 // Build the header for the alignment file. | |
170 byte[] zeroBuffer = new byte[libNeeded]; | |
171 JarEntry alignEntry = new JarEntry(alignName); | |
172 alignEntry.setMethod(JarEntry.STORED); | |
173 alignEntry.setSize(libNeeded); | |
174 alignEntry.setTime(timestamp); | |
175 CRC32 crc = new CRC32(); | |
176 crc.update(zeroBuffer); | |
177 alignEntry.setCrc(crc.getValue()); | |
178 | |
179 if (extraNeeded != 0) { | |
180 alignEntry.setExtra(new byte[extraNeeded]); | |
181 } | |
182 | |
183 // Output the alignment file. | |
184 out.putNextEntry(alignEntry); | |
185 out.write(zeroBuffer); | |
186 out.closeEntry(); | |
187 out.flush(); | |
188 } | |
189 | |
190 /** | |
191 * Copy the contents of the input APK file to the output APK file. Uncompres sed files | |
192 * will be aligned in the output stream. Uncompressed native code libraries (*.so) | |
193 * will be aligned on a page boundary. Page alignment is implemented by addi ng a | |
194 * zero filled file, regular alignment is implemented by adding a zero fille d extra | |
195 * field to the zip file header. Care is take so that the output generated i n the | |
196 * same way as SignApk. This is important so that running SignApk and zipali gn on | |
197 * the output does not break the page alignment. The archive may not contain a "*.apk" | |
198 * as SignApk has special nested signing logic that we do not support. | |
199 * | |
200 * @param in The input APK File. | |
201 * @param out The output APK stream. | |
202 * @param countOut Counting output stream (to measure the current offset). | |
203 * @param addAlignment Whether to add the alignment file or just check. | |
204 * | |
205 * @throws IOException if the output file can not be written. | |
206 */ | |
207 private static void copyAndAlignFiles( | |
208 JarFile in, JarOutputStream out, CountingOutputStream countOut, | |
209 boolean addAlignment) throws IOException { | |
rmcilroy
2014/09/30 09:36:33
alignment
Anton
2014/09/30 10:30:01
Done.
| |
210 | |
211 List<String> names = orderFilenames(in, addAlignment); | |
212 long timestamp = System.currentTimeMillis(); | |
213 byte[] buffer = new byte[4096]; | |
214 boolean firstEntry = true; | |
215 String prevName = null; | |
216 for (String name : names) { | |
217 JarEntry inEntry = in.getJarEntry(name); | |
218 JarEntry outEntry = null; | |
219 if (name.endsWith(".apk")) { | |
220 throw new UnsupportedOperationException( | |
221 "Nested APKs are not supported: " + name); | |
222 } | |
223 if (inEntry.getMethod() == JarEntry.STORED) { | |
224 // Preserve the STORED method of the input entry. | |
225 outEntry = new JarEntry(inEntry); | |
226 outEntry.setExtra(null); | |
227 } else { | |
228 // Create a new entry so that the compressed len is recomputed. | |
229 outEntry = new JarEntry(name); | |
230 } | |
231 outEntry.setTime(timestamp); | |
232 | |
233 long offset = countOut.getCount(); | |
234 if (firstEntry) { | |
235 // The first entry in a jar file has an extra field of | |
236 // four bytes that you can't get rid of; any extra | |
237 // data you specify in the JarEntry is appended to | |
238 // these forced four bytes. This is JAR_MAGIC in | |
239 // JarOutputStream; the bytes are 0xfeca0000. | |
240 firstEntry = false; | |
241 offset += 4; | |
242 } | |
243 if (inEntry.getMethod() == JarEntry.STORED) { | |
244 if (name.endsWith(".so")) { | |
245 if (addAlignment) { | |
246 addAlignmentFile(offset, timestamp, name, prevName, out) ; | |
247 } | |
248 // We check that we did indeed get to a page boundary. | |
249 offset = countOut.getCount() + JarFile.LOCHDR + name.length( ); | |
250 if ((offset % LIBRARY_ALIGNMENT) != 0) { | |
251 throw new AssertionError( | |
252 "Library was not page aligned when verifying page al ignment. " | |
253 + "Library name: " + name + " Expected alignment: " + LIBRARY_ALIGNMENT | |
254 + "Offset: " + offset + " Error: " + (offset % LIBRA RY_ALIGNMENT)); | |
255 } | |
256 } else { | |
257 offset += JarFile.LOCHDR + name.length(); | |
258 int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIG NMENT; | |
259 if (needed != 0) { | |
260 outEntry.setExtra(new byte[needed]); | |
261 } | |
262 } | |
263 } | |
264 out.putNextEntry(outEntry); | |
265 | |
266 int num; | |
267 InputStream data = in.getInputStream(inEntry); | |
268 while ((num = data.read(buffer)) > 0) { | |
269 out.write(buffer, 0, num); | |
270 } | |
271 out.closeEntry(); | |
272 out.flush(); | |
273 prevName = name; | |
274 } | |
275 } | |
276 | |
277 private static void usage() { | |
278 System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk"); | |
279 System.err.println(" addalignment - adds alignment file removes manifes t and signature"); | |
280 System.err.println(" reorder - re-creates canonical ordering and c hecks alignment"); | |
281 System.exit(2); | |
282 } | |
283 | |
284 public static void main(String[] args) { | |
285 if (args.length != 3) usage(); | |
286 | |
287 boolean addAlignment = false; | |
288 if (args[0].equals("addalignment")) { | |
289 addAlignment = true; | |
290 } else if (args[0].equals("reorder")) { | |
291 addAlignment = false; | |
292 } else { | |
293 usage(); | |
294 } | |
295 | |
296 String inputFilename = args[1]; | |
297 String outputFilename = args[2]; | |
298 | |
299 JarFile inputJar = null; | |
300 FileOutputStream outputFile = null; | |
301 | |
302 try { | |
303 inputJar = new JarFile(new File(inputFilename), true); | |
304 outputFile = new FileOutputStream(outputFilename); | |
305 | |
306 CountingOutputStream outCount = new CountingOutputStream(outputFile) ; | |
307 JarOutputStream outputJar = new JarOutputStream(outCount); | |
308 | |
309 // Match the compression level used by SignApk. | |
310 outputJar.setLevel(9); | |
311 | |
312 copyAndAlignFiles(inputJar, outputJar, outCount, addAlignment); | |
313 outputJar.close(); | |
314 } catch (Exception e) { | |
315 e.printStackTrace(); | |
316 System.exit(1); | |
rmcilroy
2014/09/30 09:36:33
Are these catches (and the one on 322-323) really
Anton
2014/09/30 10:30:01
Done.
| |
317 } finally { | |
318 try { | |
319 if (inputJar != null) inputJar.close(); | |
320 if (outputFile != null) outputFile.close(); | |
321 } catch (IOException e) { | |
322 e.printStackTrace(); | |
323 System.exit(1); | |
324 } | |
325 } | |
326 } | |
327 } | |
OLD | NEW |