| 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 |