Chromium Code Reviews| 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..8c56ec8c6bc8f4d557deb83e8b457bdce67a1006 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"; |
| + 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))) { |
| + 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 SecurityException( |
|
Anton
2014/10/29 13:35:35
SecurityException?
petrcermak
2014/10/29 14:01:01
Done (no idea how this happened).
|
| + "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(); |
|
Anton
2014/10/29 13:35:35
We should look into whether there is an alternativ
petrcermak
2014/10/29 14:01:02
It doesn't look like there are any other options.
|
| + 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(); |
| + |
| + try { |
| + ZipFile zip = openZipFile(zipFilename); |
| + File tmpLib = null; |
| + |
| + // Copy the zipped library into a temporary file (if necessary). |
| + try { |
| + ZipEntry entry = findZipEntry(zip, library); |
| + 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. |
| + 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, "Fallback process took " + (after - before) + "ms"); |
|
Anton
2014/10/29 13:35:35
"Building fallback library took .."
petrcermak
2014/10/29 14:01:01
Done.
|
| + } |
| + |
| + return fallbackLibFilename; |
| + } |
| } |