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

Side by Side Diff: build/android/rezip/RezipApk.java

Issue 595933003: Re-invent page aligning libraries in APK file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update for Ross' review Created 6 years, 2 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 unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698