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 | |
6 package org.chromium.base.library_loader; | |
7 | |
8 import android.content.Context; | |
9 import android.util.Log; | |
10 | |
11 import java.io.BufferedOutputStream; | |
12 import java.io.Closeable; | |
13 import java.io.File; | |
14 import java.io.FileNotFoundException; | |
15 import java.io.FileOutputStream; | |
16 import java.io.IOException; | |
17 import java.io.InputStream; | |
18 import java.io.OutputStream; | |
19 import java.util.Collection; | |
20 import java.util.Collections; | |
21 import java.util.HashSet; | |
22 import java.util.Map; | |
23 import java.util.Map.Entry; | |
24 import java.util.Set; | |
25 import java.util.zip.ZipEntry; | |
26 import java.util.zip.ZipException; | |
27 import java.util.zip.ZipFile; | |
28 | |
29 /** | |
30 * Class representing an exception which occured during the unpacking process. | |
31 */ | |
32 class UnpackingException extends Exception { | |
33 public UnpackingException(String message, Throwable cause) { | |
34 super(message, cause); | |
35 } | |
36 | |
37 public UnpackingException(String message) { | |
38 super(message); | |
39 } | |
40 } | |
41 | |
42 /** | |
43 * The class provides helper functions to extract native libraries from APK, | |
44 * and load libraries from there. | |
45 */ | |
46 class LibraryLoaderHelper { | |
47 private static final String TAG = "LibraryLoaderHelper"; | |
48 | |
49 // Fallback directories. | |
50 static final String LOAD_FROM_APK_FALLBACK_DIR = "fallback"; | |
51 | |
52 private static final int BUFFER_SIZE = 16384; | |
53 | |
54 /** | |
55 * Returns the directory for holding extracted native libraries. | |
56 * It may create the directory if it doesn't exist. | |
57 * | |
58 * @param context The context the code is running. | |
59 * @param dirName The name of the directory containing the libraries. | |
60 * @return The directory file object. | |
61 */ | |
62 static File getLibDir(Context context, String dirName) { | |
63 return context.getDir(dirName, Context.MODE_PRIVATE); | |
64 } | |
65 | |
66 /** | |
67 * Delete libraries and their directory synchronously. | |
68 */ | |
69 private static void deleteLibrariesSynchronously(Context context, String dir
Name) { | |
70 File libDir = getLibDir(context, dirName); | |
71 deleteObsoleteLibraries(libDir, Collections.<File>emptyList()); | |
72 } | |
73 | |
74 /** | |
75 * Delete libraries and their directory asynchronously. | |
76 * The actual deletion is done in a background thread. | |
77 */ | |
78 static void deleteLibrariesAsynchronously( | |
79 final Context context, final String dirName) { | |
80 // Child process should not reach here. | |
81 new Thread() { | |
82 @Override | |
83 public void run() { | |
84 deleteLibrariesSynchronously(context, dirName); | |
85 } | |
86 }.start(); | |
87 } | |
88 | |
89 /** | |
90 * Copy a library from a zip file to the application's private directory. | |
91 * This is used as a fallback when we are unable to load the library | |
92 * directly from the APK file (crbug.com/390618). | |
93 * | |
94 * @param context The context the code is running in. | |
95 * @param library Library name. | |
96 * @return name of the fallback copy of the library. | |
97 */ | |
98 static String buildFallbackLibrary(Context context, String library) { | |
99 try { | |
100 String libName = System.mapLibraryName(library); | |
101 File fallbackLibDir = getLibDir(context, LOAD_FROM_APK_FALLBACK_DIR)
; | |
102 File fallbackLibFile = new File(fallbackLibDir, libName); | |
103 String pathInZipFile = Linker.getLibraryFilePathInZipFile(libName); | |
104 Map<String, File> dstFiles = Collections.singletonMap(pathInZipFile,
fallbackLibFile); | |
105 | |
106 deleteObsoleteLibraries(fallbackLibDir, dstFiles.values()); | |
107 unpackLibraries(context, dstFiles); | |
108 | |
109 return fallbackLibFile.getAbsolutePath(); | |
110 } catch (Exception e) { | |
111 String errorMessage = "Unable to load fallback for library " + libra
ry | |
112 + " (" + (e.getMessage() == null ? e.toString() : e.getMessa
ge()) + ")"; | |
113 Log.e(TAG, errorMessage, e); | |
114 throw new UnsatisfiedLinkError(errorMessage); | |
115 } | |
116 } | |
117 | |
118 // Delete obsolete libraries from a library folder. | |
119 private static void deleteObsoleteLibraries(File libDir, Collection<File> ke
ptFiles) { | |
120 try { | |
121 // Build a list of libraries that should NOT be deleted. | |
122 Set<String> keptFileNames = new HashSet<String>(); | |
123 for (File k : keptFiles) { | |
124 keptFileNames.add(k.getName()); | |
125 } | |
126 | |
127 // Delete the obsolete libraries. | |
128 Log.i(TAG, "Deleting obsolete libraries in " + libDir.getPath()); | |
129 File[] files = libDir.listFiles(); | |
130 if (files != null) { | |
131 for (File f : files) { | |
132 if (!keptFileNames.contains(f.getName())) { | |
133 delete(f); | |
134 } | |
135 } | |
136 } else { | |
137 Log.e(TAG, "Failed to list files in " + libDir.getPath()); | |
138 } | |
139 | |
140 // Delete the folder if no libraries were kept. | |
141 if (keptFileNames.isEmpty()) { | |
142 delete(libDir); | |
143 } | |
144 } catch (Exception e) { | |
145 Log.e(TAG, "Failed to remove obsolete libraries from " + libDir.getP
ath()); | |
146 } | |
147 } | |
148 | |
149 // Unpack libraries from a zip file to the file system. | |
150 private static void unpackLibraries(Context context, | |
151 Map<String, File> dstFiles) throws UnpackingException { | |
152 String zipFilePath = context.getApplicationInfo().sourceDir; | |
153 Log.i(TAG, "Opening zip file " + zipFilePath); | |
154 File zipFile = new File(zipFilePath); | |
155 ZipFile zipArchive = openZipFile(zipFile); | |
156 | |
157 try { | |
158 for (Entry<String, File> d : dstFiles.entrySet()) { | |
159 String pathInZipFile = d.getKey(); | |
160 File dstFile = d.getValue(); | |
161 Log.i(TAG, "Unpacking " + pathInZipFile | |
162 + " to " + dstFile.getAbsolutePath()); | |
163 ZipEntry packedLib = zipArchive.getEntry(pathInZipFile); | |
164 | |
165 if (needToUnpackLibrary(zipFile, packedLib, dstFile)) { | |
166 unpackLibraryFromZipFile(zipArchive, packedLib, dstFile); | |
167 setLibraryFilePermissions(dstFile); | |
168 } | |
169 } | |
170 } finally { | |
171 closeZipFile(zipArchive); | |
172 } | |
173 } | |
174 | |
175 // Open a zip file. | |
176 private static ZipFile openZipFile(File zipFile) throws UnpackingException { | |
177 try { | |
178 return new ZipFile(zipFile); | |
179 } catch (ZipException e) { | |
180 throw new UnpackingException("Failed to open zip file " + zipFile.ge
tPath()); | |
181 } catch (IOException e) { | |
182 throw new UnpackingException("Failed to open zip file " + zipFile.ge
tPath()); | |
183 } | |
184 } | |
185 | |
186 // Determine whether it is necessary to unpack a library from a zip file. | |
187 private static boolean needToUnpackLibrary( | |
188 File zipFile, ZipEntry packedLib, File dstFile) { | |
189 // Check if the fallback library already exists. | |
190 if (!dstFile.exists()) { | |
191 Log.i(TAG, "File " + dstFile.getPath() + " does not exist yet"); | |
192 return true; | |
193 } | |
194 | |
195 // Check last modification dates. | |
196 long zipTime = zipFile.lastModified(); | |
197 long fallbackLibTime = dstFile.lastModified(); | |
198 if (zipTime > fallbackLibTime) { | |
199 Log.i(TAG, "Not using existing fallback file because " | |
200 + "the APK file " + zipFile.getPath() | |
201 + " (timestamp=" + zipTime + ") is newer than " | |
202 + "the fallback library " + dstFile.getPath() | |
203 + "(timestamp=" + fallbackLibTime + ")"); | |
204 return true; | |
205 } | |
206 | |
207 // Check file sizes. | |
208 long packedLibSize = packedLib.getSize(); | |
209 long fallbackLibSize = dstFile.length(); | |
210 if (fallbackLibSize != packedLibSize) { | |
211 Log.i(TAG, "Not using existing fallback file because " | |
212 + "the library in the APK " + zipFile.getPath() | |
213 + " (" + packedLibSize + "B) has a different size than " | |
214 + "the fallback library " + dstFile.getPath() | |
215 + "(" + fallbackLibSize + "B)"); | |
216 return true; | |
217 } | |
218 | |
219 Log.i(TAG, "Reusing existing file " + dstFile.getPath()); | |
220 return false; | |
221 } | |
222 | |
223 // Unpack a library from a zip file to the filesystem. | |
224 private static void unpackLibraryFromZipFile(ZipFile zipArchive, ZipEntry pa
ckedLib, | |
225 File dstFile) throws UnpackingException { | |
226 // Open input stream for the library file inside the zip file. | |
227 InputStream in; | |
228 try { | |
229 in = zipArchive.getInputStream(packedLib); | |
230 } catch (IOException e) { | |
231 throw new UnpackingException( | |
232 "IO exception when locating library in the zip file", e); | |
233 } | |
234 | |
235 // Ensure that the input stream is closed at the end. | |
236 try { | |
237 // Delete existing file if it exists. | |
238 if (dstFile.exists()) { | |
239 Log.i(TAG, "Deleting existing unpacked library file " + dstFile.
getPath()); | |
240 if (!dstFile.delete()) { | |
241 throw new UnpackingException( | |
242 "Failed to delete existing unpacked library file " +
dstFile.getPath()); | |
243 } | |
244 } | |
245 | |
246 // Ensure that the library folder exists. Since this is added | |
247 // for increased robustness, we log errors and carry on. | |
248 try { | |
249 dstFile.getParentFile().mkdirs(); | |
250 } catch (Exception e) { | |
251 Log.e(TAG, "Failed to make library folder", e); | |
252 } | |
253 | |
254 // Create the destination file. | |
255 try { | |
256 if (!dstFile.createNewFile()) { | |
257 throw new UnpackingException("existing unpacked library file
was not deleted"); | |
258 } | |
259 } catch (IOException e) { | |
260 throw new UnpackingException("failed to create unpacked library
file", e); | |
261 } | |
262 | |
263 // Open the output stream for the destination file. | |
264 OutputStream out; | |
265 try { | |
266 out = new BufferedOutputStream(new FileOutputStream(dstFile)); | |
267 } catch (FileNotFoundException e) { | |
268 throw new UnpackingException( | |
269 "failed to open output stream for unpacked library file"
, e); | |
270 } | |
271 | |
272 // Ensure that the output stream is closed at the end. | |
273 try { | |
274 // Copy the library from the zip file to the destination file. | |
275 Log.i(TAG, "Copying " + packedLib.getName() + " from " + zipArch
ive.getName() | |
276 + " to " + dstFile.getPath()); | |
277 byte[] buffer = new byte[BUFFER_SIZE]; | |
278 int len; | |
279 while ((len = in.read(buffer)) != -1) { | |
280 out.write(buffer, 0, len); | |
281 } | |
282 } catch (IOException e) { | |
283 throw new UnpackingException( | |
284 "failed to copy the library from the zip file", e); | |
285 } finally { | |
286 close(out, "output stream"); | |
287 } | |
288 } finally { | |
289 close(in, "input stream"); | |
290 } | |
291 } | |
292 | |
293 // Set up library file permissions. | |
294 private static void setLibraryFilePermissions(File libFile) { | |
295 // Change permission to rwxr-xr-x | |
296 Log.i(TAG, "Setting file permissions for " + libFile.getPath()); | |
297 if (!libFile.setReadable(/* readable */ true, /* ownerOnly */ false)) { | |
298 Log.e(TAG, "failed to chmod a+r the temporary file"); | |
299 } | |
300 if (!libFile.setExecutable(/* executable */ true, /* ownerOnly */ false)
) { | |
301 Log.e(TAG, "failed to chmod a+x the temporary file"); | |
302 } | |
303 if (!libFile.setWritable(/* writable */ true)) { | |
304 Log.e(TAG, "failed to chmod +w the temporary file"); | |
305 } | |
306 } | |
307 | |
308 // Close a closable and log a warning if it fails. | |
309 private static void close(Closeable closeable, String name) { | |
310 try { | |
311 closeable.close(); | |
312 } catch (IOException e) { | |
313 // Warn and ignore. | |
314 Log.w(TAG, "IO exception when closing " + name, e); | |
315 } | |
316 } | |
317 | |
318 // Close a zip file and log a warning if it fails. | |
319 // This needs to be a separate method because ZipFile is not Closeable in | |
320 // Java 6 (used on some older devices). | |
321 private static void closeZipFile(ZipFile file) { | |
322 try { | |
323 file.close(); | |
324 } catch (IOException e) { | |
325 // Warn and ignore. | |
326 Log.w(TAG, "IO exception when closing zip file", e); | |
327 } | |
328 } | |
329 | |
330 // Delete a file and log it. | |
331 private static void delete(File file) { | |
332 if (file.delete()) { | |
333 Log.i(TAG, "Deleted " + file.getPath()); | |
334 } else { | |
335 Log.w(TAG, "Failed to delete " + file.getPath()); | |
336 } | |
337 } | |
338 } | |
OLD | NEW |