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

Unified Diff: base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java

Issue 693943003: Update from https://crrev.com/302630 (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
Index: base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
index 58198aed8c4874352e343cad7c6effa93d3e1681..c185fadbe5f4b00a558ed863ead1abfc56d09948 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
@@ -6,18 +6,42 @@
package org.chromium.base.library_loader;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
+import java.io.BufferedOutputStream;
+import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
+ * Class representing an exception which occured during the unpacking process.
+ */
+class UnpackingException extends Exception {
+ public UnpackingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnpackingException(String message) {
+ super(message);
+ }
+}
+
+/**
* The class provides helper functions to extract native libraries from APK,
* and load libraries from there.
*
@@ -27,7 +51,12 @@ import java.util.zip.ZipFile;
public class LibraryLoaderHelper {
private static final String TAG = "LibraryLoaderHelper";
- private static final String LIB_DIR = "lib";
+ // Workaround and fallback directories.
+ // TODO(petrcermak): Merge the directories once refactored.
+ public static final String PACKAGE_MANAGER_WORKAROUND_DIR = "lib";
+ public static final String LOAD_FROM_APK_FALLBACK_DIR = "fallback";
+
+ private static final int BUFFER_SIZE = 16384;
/**
* One-way switch becomes true if native libraries were unpacked
@@ -71,7 +100,7 @@ public class LibraryLoaderHelper {
* recreate one to the new directory. However, on some devices (e.g. Sony Xperia),
* the symlink was updated, but fails to extract new native libraries from
* the new apk.
-
+ *
* We make the following changes to alleviate the issue:
* 1) name the native library with apk version code, e.g.,
* libchrome.1750.136.so, 1750.136 is Chrome version number;
@@ -89,8 +118,9 @@ public class LibraryLoaderHelper {
*/
static boolean tryLoadLibraryUsingWorkaround(Context context, String library) {
assert context != null;
- File libFile = getWorkaroundLibFile(context, library);
- if (!libFile.exists() && !unpackLibrariesOnce(context)) {
+ String libName = System.mapLibraryName(library);
+ File libFile = new File(getLibDir(context, PACKAGE_MANAGER_WORKAROUND_DIR), libName);
+ if (!libFile.exists() && !unpackWorkaroundLibrariesOnce(context)) {
return false;
}
try {
@@ -105,16 +135,18 @@ public class LibraryLoaderHelper {
* Returns the directory for holding extracted native libraries.
* It may create the directory if it doesn't exist.
*
- * @param context
- * @return the directory file object
+ * @param context The context the code is running.
+ * @param dirName The name of the directory containing the libraries.
+ * @return The directory file object.
*/
- public static File getWorkaroundLibDir(Context context) {
- return context.getDir(LIB_DIR, Context.MODE_PRIVATE);
+ public static File getLibDir(Context context, String dirName) {
+ return context.getDir(dirName, Context.MODE_PRIVATE);
}
- private static File getWorkaroundLibFile(Context context, String library) {
- String libName = System.mapLibraryName(library);
- return new File(getWorkaroundLibDir(context), libName);
+ @SuppressWarnings("deprecation")
+ private static String getJniNameInApk(String libName) {
+ // TODO(aurimas): Build.CPU_ABI has been deprecated. Replace it when final L SDK is public.
+ return "lib/" + Build.CPU_ABI + "/" + libName;
}
/**
@@ -126,128 +158,306 @@ public class LibraryLoaderHelper {
* @return true when unpacking was successful, false when failed or called
* more than once.
*/
- private static boolean unpackLibrariesOnce(Context context) {
+ private static boolean unpackWorkaroundLibrariesOnce(Context context) {
if (sLibrariesWereUnpacked) {
return false;
}
sLibrariesWereUnpacked = true;
- File libDir = getWorkaroundLibDir(context);
- deleteDirectorySync(libDir);
+ deleteLibrariesSynchronously(context, PACKAGE_MANAGER_WORKAROUND_DIR);
+ File libDir = getLibDir(context, PACKAGE_MANAGER_WORKAROUND_DIR);
try {
- ApplicationInfo appInfo = context.getApplicationInfo();
- ZipFile file = new ZipFile(new File(appInfo.sourceDir), ZipFile.OPEN_READ);
- for (String libName : NativeLibraries.LIBRARIES) {
- String jniNameInApk = getJniNameInApk(libName);
-
- final ZipEntry entry = file.getEntry(jniNameInApk);
- if (entry == null) {
- Log.e(TAG, appInfo.sourceDir + " doesn't have file " + jniNameInApk);
- file.close();
- deleteDirectorySync(libDir);
- return false;
- }
-
- File outputFile = getWorkaroundLibFile(context, libName);
-
- Log.i(TAG, "Extracting native libraries into " + outputFile.getAbsolutePath());
-
- assert !outputFile.exists();
-
- try {
- if (!outputFile.createNewFile()) {
- throw new IOException();
- }
-
- InputStream is = null;
- FileOutputStream os = null;
- try {
- is = file.getInputStream(entry);
- os = new FileOutputStream(outputFile);
- int count = 0;
- byte[] buffer = new byte[16 * 1024];
- while ((count = is.read(buffer)) > 0) {
- os.write(buffer, 0, count);
- }
- } finally {
- try {
- if (is != null) is.close();
- } finally {
- if (os != null) os.close();
- }
- }
- // Change permission to rwxr-xr-x
- outputFile.setReadable(true, false);
- outputFile.setExecutable(true, false);
- outputFile.setWritable(true);
- } catch (IOException e) {
- if (outputFile.exists()) {
- if (!outputFile.delete()) {
- Log.e(TAG, "Failed to delete " + outputFile.getAbsolutePath());
- }
- }
- file.close();
- throw e;
- }
+ Map<String, File> dstFiles = new HashMap<String, File>();
+ for (String library : NativeLibraries.LIBRARIES) {
+ String libName = System.mapLibraryName(library);
+ String pathInZipFile = getJniNameInApk(libName);
+ dstFiles.put(pathInZipFile, new File(libDir, libName));
}
- file.close();
+ unpackLibraries(context, dstFiles);
return true;
- } catch (IOException e) {
+ } catch (UnpackingException e) {
Log.e(TAG, "Failed to unpack native libraries", e);
- deleteDirectorySync(libDir);
+ deleteLibrariesSynchronously(context, PACKAGE_MANAGER_WORKAROUND_DIR);
return false;
}
}
/**
- * Delete old library files in the backup directory.
- * The actual deletion is done in a background thread.
+ * Delete libraries and their directory synchronously.
+ * For testing purpose only.
+ *
+ * @param context
+ */
+ public static void deleteLibrariesSynchronously(Context context, String dirName) {
+ File libDir = getLibDir(context, dirName);
+ deleteObsoleteLibraries(libDir, Collections.<File>emptyList());
+ }
+
+ /**
+ * Delete libraries and their directory asynchronously.
+ * The actual deletion is done in a background thread.
*
* @param context
*/
- static void deleteWorkaroundLibrariesAsynchronously(final Context context) {
+ static void deleteLibrariesAsynchronously(
+ final Context context, final String dirName) {
// Child process should not reach here.
new Thread() {
@Override
public void run() {
- deleteWorkaroundLibrariesSynchronously(context);
+ deleteLibrariesSynchronously(context, dirName);
}
}.start();
}
/**
- * Delete the workaround libraries and directory synchronously.
- * For testing purpose only.
- * @param context
+ * Copy a library from a zip file to the application's private directory.
+ * This is used as a fallback when we are unable to load the library
+ * directly from the APK file (crbug.com/390618).
+ *
+ * @param context The context the code is running in.
+ * @param library Library name.
+ * @return name of the fallback copy of the library.
*/
- public static void deleteWorkaroundLibrariesSynchronously(Context context) {
- File libDir = getWorkaroundLibDir(context);
- deleteDirectorySync(libDir);
- }
+ public static String buildFallbackLibrary(Context context, String library) {
+ try {
+ String libName = System.mapLibraryName(library);
+ File fallbackLibDir = getLibDir(context, LOAD_FROM_APK_FALLBACK_DIR);
+ File fallbackLibFile = new File(fallbackLibDir, libName);
+ String pathInZipFile = Linker.getLibraryFilePathInZipFile(libName);
+ Map<String, File> dstFiles = Collections.singletonMap(pathInZipFile, fallbackLibFile);
- @SuppressWarnings("deprecation")
- private static String getJniNameInApk(String libName) {
- // TODO(aurimas): Build.CPU_ABI has been deprecated. Replace it when final L SDK is public.
- return "lib/" + Build.CPU_ABI + "/" + System.mapLibraryName(libName);
+ deleteObsoleteLibraries(fallbackLibDir, dstFiles.values());
+ unpackLibraries(context, dstFiles);
+
+ return fallbackLibFile.getAbsolutePath();
+ } catch (Exception e) {
+ String errorMessage = "Unable to load fallback for library " + library
+ + " (" + (e.getMessage() == null ? e.toString() : e.getMessage()) + ")";
+ Log.e(TAG, errorMessage, e);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
}
- private static void deleteDirectorySync(File dir) {
+ // Delete obsolete libraries from a library folder.
+ private static void deleteObsoleteLibraries(File libDir, Collection<File> keptFiles) {
try {
- File[] files = dir.listFiles();
+ // Build a list of libraries that should NOT be deleted.
+ Set<String> keptFileNames = new HashSet<String>();
+ for (File k : keptFiles) {
+ keptFileNames.add(k.getName());
+ }
+
+ // Delete the obsolete libraries.
+ Log.i(TAG, "Deleting obsolete libraries in " + libDir.getPath());
+ File[] files = libDir.listFiles();
if (files != null) {
- for (File file : files) {
- if (!file.delete()) {
- Log.e(TAG, "Failed to remove " + file.getAbsolutePath());
+ for (File f : files) {
+ if (!keptFileNames.contains(f.getName())) {
+ delete(f);
}
}
+ } else {
+ Log.e(TAG, "Failed to list files in " + libDir.getPath());
}
- if (!dir.delete()) {
- Log.w(TAG, "Failed to remove " + dir.getAbsolutePath());
+
+ // Delete the folder if no libraries were kept.
+ if (keptFileNames.isEmpty()) {
+ delete(libDir);
}
- return;
} catch (Exception e) {
- Log.e(TAG, "Failed to remove old libs, ", e);
+ Log.e(TAG, "Failed to remove obsolete libraries from " + libDir.getPath());
+ }
+ }
+
+ // Unpack libraries from a zip file to the file system.
+ private static void unpackLibraries(Context context,
+ Map<String, File> dstFiles) throws UnpackingException {
+ String zipFilePath = context.getApplicationInfo().sourceDir;
+ Log.i(TAG, "Opening zip file " + zipFilePath);
+ File zipFile = new File(zipFilePath);
+ ZipFile zipArchive = openZipFile(zipFile);
+
+ try {
+ for (Entry<String, File> d : dstFiles.entrySet()) {
+ String pathInZipFile = d.getKey();
+ File dstFile = d.getValue();
+ Log.i(TAG, "Unpacking " + pathInZipFile
+ + " to " + dstFile.getAbsolutePath());
+ ZipEntry packedLib = zipArchive.getEntry(pathInZipFile);
+
+ if (needToUnpackLibrary(zipFile, packedLib, dstFile)) {
+ unpackLibraryFromZipFile(zipArchive, packedLib, dstFile);
+ setLibraryFilePermissions(dstFile);
+ }
+ }
+ } finally {
+ closeZipFile(zipArchive);
+ }
+ }
+
+ // Open a zip file.
+ private static ZipFile openZipFile(File zipFile) throws UnpackingException {
+ try {
+ return new ZipFile(zipFile);
+ } catch (ZipException e) {
+ throw new UnpackingException("Failed to open zip file " + zipFile.getPath());
+ } catch (IOException e) {
+ throw new UnpackingException("Failed to open zip file " + zipFile.getPath());
+ }
+ }
+
+ // Determine whether it is necessary to unpack a library from a zip file.
+ private static boolean needToUnpackLibrary(
+ File zipFile, ZipEntry packedLib, File dstFile) {
+ // Check if the fallback library already exists.
+ if (!dstFile.exists()) {
+ Log.i(TAG, "File " + dstFile.getPath() + " does not exist yet");
+ return true;
+ }
+
+ // Check last modification dates.
+ long zipTime = zipFile.lastModified();
+ long fallbackLibTime = dstFile.lastModified();
+ if (zipTime > fallbackLibTime) {
+ Log.i(TAG, "Not using existing fallback file because "
+ + "the APK file " + zipFile.getPath()
+ + " (timestamp=" + zipTime + ") is newer than "
+ + "the fallback library " + dstFile.getPath()
+ + "(timestamp=" + fallbackLibTime + ")");
+ return true;
+ }
+
+ // Check file sizes.
+ long packedLibSize = packedLib.getSize();
+ long fallbackLibSize = dstFile.length();
+ if (fallbackLibSize != packedLibSize) {
+ Log.i(TAG, "Not using existing fallback file because "
+ + "the library in the APK " + zipFile.getPath()
+ + " (" + packedLibSize + "B) has a different size than "
+ + "the fallback library " + dstFile.getPath()
+ + "(" + fallbackLibSize + "B)");
+ return true;
+ }
+
+ Log.i(TAG, "Reusing existing file " + dstFile.getPath());
+ return false;
+ }
+
+ // Unpack a library from a zip file to the filesystem.
+ private static void unpackLibraryFromZipFile(ZipFile zipArchive, ZipEntry packedLib,
+ File dstFile) throws UnpackingException {
+ // Open input stream for the library file inside the zip file.
+ InputStream in;
+ try {
+ in = zipArchive.getInputStream(packedLib);
+ } catch (IOException e) {
+ throw new UnpackingException(
+ "IO exception when locating library in the zip file", e);
+ }
+
+ // Ensure that the input stream is closed at the end.
+ try {
+ // Delete existing file if it exists.
+ if (dstFile.exists()) {
+ Log.i(TAG, "Deleting existing unpacked library file " + dstFile.getPath());
+ if (!dstFile.delete()) {
+ throw new UnpackingException(
+ "Failed to delete existing unpacked library file " + dstFile.getPath());
+ }
+ }
+
+ // Ensure that the library folder exists. Since this is added
+ // for increased robustness, we log errors and carry on.
+ try {
+ dstFile.getParentFile().mkdirs();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to make library folder", e);
+ }
+
+ // Create the destination file.
+ try {
+ if (!dstFile.createNewFile()) {
+ throw new UnpackingException("existing unpacked library file was not deleted");
+ }
+ } catch (IOException e) {
+ throw new UnpackingException("failed to create unpacked library file", e);
+ }
+
+ // Open the output stream for the destination file.
+ OutputStream out;
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(dstFile));
+ } catch (FileNotFoundException e) {
+ throw new UnpackingException(
+ "failed to open output stream for unpacked library file", e);
+ }
+
+ // Ensure that the output stream is closed at the end.
+ try {
+ // Copy the library from the zip file to the destination file.
+ Log.i(TAG, "Copying " + packedLib.getName() + " from " + zipArchive.getName()
+ + " to " + dstFile.getPath());
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ } catch (IOException e) {
+ throw new UnpackingException(
+ "failed to copy the library from the zip file", e);
+ } finally {
+ close(out, "output stream");
+ }
+ } finally {
+ close(in, "input stream");
+ }
+ }
+
+ // Set up library file permissions.
+ private static void setLibraryFilePermissions(File libFile) {
+ // Change permission to rwxr-xr-x
+ Log.i(TAG, "Setting file permissions for " + libFile.getPath());
+ if (!libFile.setReadable(/* readable */ true, /* ownerOnly */ false)) {
+ Log.e(TAG, "failed to chmod a+r the temporary file");
+ }
+ if (!libFile.setExecutable(/* executable */ true, /* ownerOnly */ false)) {
+ Log.e(TAG, "failed to chmod a+x the temporary file");
+ }
+ if (!libFile.setWritable(/* writable */ true)) {
+ Log.e(TAG, "failed to chmod +w the temporary file");
+ }
+ }
+
+ // Close a closable and log a warning if it fails.
+ private static void close(Closeable closeable, String name) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ // Warn and ignore.
+ Log.w(TAG, "IO exception when closing " + name, e);
+ }
+ }
+
+ // Close a zip file and log a warning if it fails.
+ // This needs to be a separate method because ZipFile is not Closeable in
+ // Java 6 (used on some older devices).
+ private static void closeZipFile(ZipFile file) {
+ try {
+ file.close();
+ } catch (IOException e) {
+ // Warn and ignore.
+ Log.w(TAG, "IO exception when closing zip file", e);
+ }
+ }
+
+ // Delete a file and log it.
+ private static void delete(File file) {
+ if (file.delete()) {
+ Log.i(TAG, "Deleted " + file.getPath());
+ } else {
+ Log.w(TAG, "Failed to delete " + file.getPath());
}
}
}

Powered by Google App Engine
This is Rietveld 408576698