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

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

Issue 684163002: Library loading fallback. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update for review feedback 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 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..9e80ac4ef552a815acd13b577172b48c896f24f2 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
@@ -10,13 +10,33 @@ 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.zip.ZipEntry;
+import java.util.zip.ZipException;
import java.util.zip.ZipFile;
+import javax.annotation.Nullable;
+
+/**
+ * Class representing an exception which occured during the fallback process.
+ */
+class FallbackException extends Exception {
+ public FallbackException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FallbackException(String message) {
+ this(message, null);
+ }
+}
+
/**
* The class provides helper functions to extract native libraries from APK,
* and load libraries from there.
@@ -29,6 +49,14 @@ public class LibraryLoaderHelper {
private static final String LIB_DIR = "lib";
+ // Fallback process constants.
+ private static final String FALLBACK_DIR = "fallback";
rmcilroy 2014/10/29 17:20:43 Could we try integrating this with the other fallb
petrcermak 2014/10/30 19:55:07 I refactored the code and removed duplication wher
+ private static final String FALLBACK_PREFIX = "fallback-";
+ private static final String TEMP_PREFIX = "tmp-";
+ private static final String LIBRARY_PREFIX = "lib";
+ private static final String LIBRARY_EXTENSION = ".so";
+ private static final int BUFFER_SIZE = 4096;
+
/**
* One-way switch becomes true if native libraries were unpacked
* from APK.
@@ -71,7 +99,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;
@@ -250,4 +278,240 @@ public class LibraryLoaderHelper {
Log.e(TAG, "Failed to remove old libs, ", e);
}
}
+
+ // Log an error and throw an UnsatisfiedLinkError.
+ private static void throwUnsatisfiedLinkError(
+ String fallbackLibFilename, String zipFilename, String library, String reason,
+ @Nullable Throwable e) {
+ String errorMessage = "Unable to load fallback library " + fallbackLibFilename +
+ " for library " + library + " in " + zipFilename + ", " + reason;
+ if (e != null) {
+ Log.e(TAG, errorMessage, e);
+ } else {
+ Log.e(TAG, errorMessage);
+ }
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+
+ // Open a given zip file.
+ private static ZipFile openZipFile(String zipFilename) throws FallbackException {
+ Log.i(TAG, "Opening zip file " + zipFilename);
+ try {
+ return new ZipFile(zipFilename);
+ } catch (ZipException e) {
+ throw new FallbackException("unable to open the zip file", e);
+ } catch (IOException e) {
+ throw new FallbackException("unable to open the zip file", e);
+ }
+ }
+
+ // Find the library in the zip file.
+ private static ZipEntry findZipEntry(ZipFile zip, String library) throws FallbackException {
+ String libFilenameInZip = Linker.getLibraryFilenameInZipFile(library);
+ if (libFilenameInZip.equals("")) {
+ throw new FallbackException("could not retrieve library filename in zip file");
+ }
+
+ Log.i(TAG, "Searching for " + libFilenameInZip + " inside the zip file");
+ try {
+ return zip.getEntry(libFilenameInZip);
+ } catch (IllegalStateException e) {
+ throw new FallbackException("did not find library in zip file", e);
+ }
+ }
+
+ // Close a closable and log a warning if it fails.
+ private static void close(Closeable closable, String name) {
+ try {
+ closable.close();
+ } catch (IOException e) {
+ // Warn and ignore.
+ Log.w(TAG, "IO exception when closing " + name, e);
+ }
+ }
+
+ // Delete a file and log it.
+ private static void deleteFile(File file) {
+ if (file != null && file.delete()) {
+ Log.i(TAG, "Deleted file " + file);
+ } else {
+ Log.w(TAG, "Failed to delete file " + file);
+ }
+ }
+
+ // Determine whether it is necessary to generate the fallback library.
+ private static boolean needToGenerateFallback(
+ File fallbackLib, File zipFile, ZipEntry entry) {
+ // Check if the fallback library already exists.
+ if (!fallbackLib.exists()) {
+ Log.i(TAG, "Fallback file does not exist yet");
+ return true;
+ }
+
+ // Check last modification dates.
+ long zipTime = zipFile.lastModified();
+ long fallbackLibTime = fallbackLib.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 " + fallbackLib.getPath() +
+ "(timestamp=" + fallbackLibTime + ")");
+ return true;
+ }
+
+ // Check file sizes.
+ long entrySize = entry.getSize();
+ long fallbackLibSize = fallbackLib.length();
+ if (fallbackLibSize != entrySize) {
+ Log.i(TAG, "Not using existing fallback file because " +
+ "the library in the APK " + zipFile.getPath() +
+ " (" + entrySize + "B) has a different size than " +
+ "the fallback library " + fallbackLib.getPath() +
+ "(" + fallbackLibSize + "B)");
+ return true;
+ }
+
+ Log.i(TAG, "Reusing existing fallback file");
+ return false;
+ }
+
+ // Remove any "tmp-" and "fallback-" libraries which currently exist.
+ private static void removeExistingFallbackFiles(File fallbackDir) {
+ Log.i(TAG, "Removing existing fallback files in " + fallbackDir);
+ for (File f : fallbackDir.listFiles()) {
+ String name = f.getName();
+ if (name.endsWith(LIBRARY_EXTENSION) &&
+ (name.startsWith(FALLBACK_PREFIX + LIBRARY_PREFIX) ||
+ name.startsWith(TEMP_PREFIX + LIBRARY_PREFIX))) {
picksi1 2014/10/30 10:48:07 If this were me I'd probably write a function that
petrcermak 2014/10/30 19:55:07 I removed this code.
+ deleteFile(f);
+ }
+ }
+ }
+
+ // Copy the library from the zipfile into a temporary file.
+ private static File copyLibraryFromZipFile(ZipFile zip, ZipEntry entry,
+ File fallbackDir, String library) throws FallbackException {
+ // Locate the library inside the zip file.
+ InputStream in = null;
+ try {
+ in = zip.getInputStream(entry);
+ } catch (IOException e) {
+ throw new FallbackException(
+ "IO exception when locating library in the zip file", e);
+ }
+
+ // Temporary file used while the fallback library file is incomplete.
+ File tmpFile = new File(fallbackDir, TEMP_PREFIX + library);
+
+ // Open the output file.
+ OutputStream out = null;
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(tmpFile));
+ } catch (FileNotFoundException e) {
+ close(in, "input stream");
+ throw new FallbackException("failed to create temporary file", e);
+ } catch (SecurityException e) {
+ close(in, "input stream");
+ throw new FallbackException(
+ "security exception when creating temporary file", e);
+ }
+
+ // Copy the library from the zip file into the temporary file.
+ Log.i(TAG, "Copying " + library + " from " + zip.getName() + " to " + tmpFile);
+ try {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ } catch (IOException e) {
+ deleteFile(tmpFile);
+ throw new FallbackException(
+ "failed to copy the library into temporary file", e);
+ } finally {
+ close(in, "input stream");
+ close(out, "output stream");
+ }
+
+ return tmpFile;
+ }
+
+ // Set up file permissions and move the temporary file to the fallback file.
+ private static void setUpFallbackLibrary(File tmpLib, File fallbackLib) throws FallbackException {
+ Log.i(TAG, "Setting file permissions for " + tmpLib);
+ if (!tmpLib.setReadable(/* readable */ true, /* ownerOnly */ false)) {
+ throw new FallbackException("failed to chmod +r the temporary file");
+ }
+ if (!tmpLib.setExecutable(/* executable */ true, /* ownerOnly */ false)) {
+ throw new FallbackException("failed to chmod +x the temporary file");
+ }
+ Log.i(TAG, "Renaming " + tmpLib + " to " + fallbackLib);
+ if (!tmpLib.renameTo(fallbackLib)) {
+ throw new FallbackException("failed to rename the temporary file");
+ }
+ }
+
+ /**
+ * 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 zip file.
+ *
+ * @param context The context the code is running in.
+ * @param zipFilename Filename of the zip file containing the library.
+ * @param library Platform specific library name (e.g. libfoo.so).
+ * @return name of the fallback copy of the library.
+ */
+ public static String buildFallbackLibrary(
+ Context context, String zipFilename, String library) {
+ // Get the fallback directory and file.
+ String fallbackDirFilename = context.getDir(
+ FALLBACK_DIR, Context.MODE_WORLD_READABLE).getPath();
+ File fallbackDir = new File(fallbackDirFilename);
+ File fallbackLib = new File(fallbackDir, FALLBACK_PREFIX + library);
+ String fallbackLibFilename = fallbackLib.getPath();
+
+ // The zip process takes tens of milliseconds.
+ long before = System.currentTimeMillis();
+
picksi1 2014/10/30 10:48:07 should 'before' and 'after' be called startTime &
petrcermak 2014/10/30 19:55:07 ditto.
+ try {
+ ZipFile zip = openZipFile(zipFilename);
rmcilroy 2014/10/29 17:20:44 Why can't we just use unpackLibrariesOnce (possibl
petrcermak 2014/10/30 19:55:07 I refactored the code and merged them where I coul
+ File tmpLib = null;
+
picksi1 2014/10/30 10:48:07 should "tmpLib" be called tempLibraryFile?
petrcermak 2014/10/30 19:55:06 ditto (removed code).
+ // Copy the zipped library into a temporary file (if necessary).
+ try {
+ ZipEntry entry = findZipEntry(zip, library);
picksi1 2014/10/30 10:48:07 The name 'entry' doesn't help understand what this
petrcermak 2014/10/30 19:55:07 I renamed it to "packedLib".
+ if (needToGenerateFallback(fallbackLib, new File(zipFilename), entry)) {
+ removeExistingFallbackFiles(fallbackDir);
+ tmpLib = copyLibraryFromZipFile(zip, entry, fallbackDir, library);
+ }
+ } finally {
+ close(zip, "zip file");
+ }
+
+ // Modify permissions of the temporary file and rename it to fallback library.
picksi1 2014/10/30 10:48:07 Is the above comment correct?
petrcermak 2014/10/30 19:55:07 ditto (but I think that it was).
+ if (tmpLib != null) {
+ try {
+ setUpFallbackLibrary(tmpLib, fallbackLib);
+ } finally {
+ if (tmpLib.exists()) {
+ deleteFile(tmpLib);
+ }
+ }
+ }
+
+ // Check that the fallback library exists.
+ if (!fallbackLib.exists()) {
+ throw new FallbackException("fallback library was not created");
+ }
+ } catch (FallbackException e) {
+ throwUnsatisfiedLinkError(fallbackLibFilename, zipFilename, library,
+ e.getMessage(), e.getCause());
+ } finally {
+ long after = System.currentTimeMillis();
+ Log.i(TAG, "Building fallback library took " + (after - before) + "ms");
rmcilroy 2014/10/29 17:20:43 There is no point logging this I don't think (othe
petrcermak 2014/10/30 19:55:07 Done.
+ }
+
+ return fallbackLibFilename;
+ }
}

Powered by Google App Engine
This is Rietveld 408576698