Chromium Code Reviews| Index: base/android/java/src/org/chromium/base/library_loader/Linker.java |
| diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java |
| index 41104f0453e7acc0631a3d535ed3bbe3ae30c725..20d2cb084d12c865de361c49a7f18f2abaccc983 100644 |
| --- a/base/android/java/src/org/chromium/base/library_loader/Linker.java |
| +++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java |
| @@ -15,9 +15,20 @@ import org.chromium.base.CalledByNative; |
| import org.chromium.base.SysUtils; |
| import org.chromium.base.ThreadUtils; |
| +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.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| +import java.util.zip.ZipEntry; |
| +import java.util.zip.ZipException; |
| +import java.util.zip.ZipFile; |
| import javax.annotation.Nullable; |
| @@ -651,7 +662,7 @@ public class Linker { |
| final long address = nativeGetRandomBaseLoadAddress(maxExpectedBytes); |
| if (DEBUG) { |
| Log.i(TAG, |
| - String.format(Locale.US, "Random native base load address: 0x%x", address)); |
| + String.format(Locale.US, "Random native base load address: 0x%x", address)); |
| } |
| return address; |
| } |
| @@ -717,9 +728,11 @@ public class Linker { |
| * |
| * @param zipfile The filename of the zipfile contain the library. |
| * @param library The library's base name. |
| + * @param fallbackDir Name of directory to use if falling back on copying. |
| */ |
| - public static void loadLibraryInZipFile(String zipfile, String library) { |
| - loadLibraryMaybeInZipFile(zipfile, library); |
| + public static void loadLibraryInZipFile( |
| + String zipfile, String library, String fallbackDir) { |
| + loadLibraryMaybeInZipFile(zipfile, library, fallbackDir); |
| } |
| /** |
| @@ -728,11 +741,13 @@ public class Linker { |
| * @param library The library's base name. |
| */ |
| public static void loadLibrary(String library) { |
| - loadLibraryMaybeInZipFile(null, library); |
| + loadLibraryMaybeInZipFile(null, library, null); |
| } |
| private static void loadLibraryMaybeInZipFile( |
| - @Nullable String zipFile, String library) { |
| + @Nullable String zipFile, |
| + String library, @Nullable String fallbackDir) { |
| + |
| if (DEBUG) Log.i(TAG, "loadLibrary: " + library); |
| // Don't self-load the linker. This is because the build system is |
| @@ -771,13 +786,19 @@ public class Linker { |
| String sharedRelRoName = libName; |
| if (zipFile != null) { |
| - if (!nativeLoadLibraryInZipFile(zipFile, libName, loadAddress, libInfo)) { |
| - String errorMessage = "Unable to load library: " + libName + |
| - ", in: " + zipFile; |
| + if (!checkLibraryLoadFromApkSupport(zipFile)) { |
| + sharedRelRoName = loadLibraryInZipFileFallback( |
| + zipFile, libName, fallbackDir, loadAddress, sInBrowserProcess, libInfo); |
| + } else if (nativeLoadLibraryInZipFile( |
| + zipFile, libName, loadAddress, libInfo)) { |
| + sharedRelRoName = zipFile; |
| + } else { |
| + String errorMessage = |
| + "Unable to load library: " + libName + " in: " + |
| + zipFile; |
| Log.e(TAG, errorMessage); |
| throw new UnsatisfiedLinkError(errorMessage); |
| } |
| - sharedRelRoName = zipFile; |
| } else { |
| if (!nativeLoadLibrary(libName, loadAddress, libInfo)) { |
| String errorMessage = "Unable to load library: " + libName; |
| @@ -849,6 +870,235 @@ public class Linker { |
| } |
| } |
| + // Log an error and throw UnsatisfiedLinkError. |
| + private static void throwUnsatisfiedLinkError( |
| + String fallbackLibName, String zipfileName, String reason, @Nullable Throwable e) { |
| + String errorMessage = |
| + "Unable to load library: " + fallbackLibName + |
| + " when falling back from loading directly from: " + |
| + zipfileName + ", " + reason; |
| + if (e != null) { |
| + Log.e(TAG, errorMessage, e); |
| + } else { |
| + Log.e(TAG, errorMessage); |
| + } |
| + throw new UnsatisfiedLinkError(errorMessage); |
| + } |
| + |
| + // Delete the file and log it. |
| + private static void deleteFile(File file) { |
| + if (file.delete()) { |
| + Log.i(TAG, "deleted file: " + file); |
| + } else { |
| + Log.w(TAG, "failed to delete file: " + file); |
| + } |
| + } |
| + |
| + // Delete the temporary file and log a warning if it fails. |
| + private static void deleteTempFile(File tmpFile) { |
| + if (!tmpFile.delete()) { |
| + Log.w(TAG, "Failed to delete the temporary fallback file: " + tmpFile.getPath()); |
| + } |
| + } |
| + |
| + // Close the 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); |
| + } |
| + } |
| + |
| + private static final String sFallbackPrefix = "fallback-"; |
| + private static final String sTempPrefix = "tmp-"; |
| + |
| + /** |
| + * Used as a fallback when we are unable to load the library directly |
| + * from the zipfile. Instead we copy the library into the application |
| + * private directory and load it from there. |
| + * |
| + * @param zipfileName Filename of the zip file containing the library. |
| + * @param library Platform specific library name (e.g. libfoo.so) |
| + * @param fallbackDir Name of directory to put a copy of the library. |
| + * @param createFallbackIfNeeded If necessary make the fallback file. |
| + * @param loadAddress Explicit load address, or 0 for randomized one. |
| + * @param libInfo If not null, the mLoadAddress and mLoadSize fields |
| + * of this LibInfo instance will set on success. |
| + * @return name of the fallback copy of the library. |
| + */ |
| + private static String loadLibraryInZipFileFallback( |
| + String zipfileName, |
| + String libraryName, |
| + String fallbackDir, |
| + long loadAddress, |
| + boolean createFallbackIfNeeded, |
| + LibInfo libInfo) { |
| + |
| + File fallbackDirFile = new File(fallbackDir); |
| + // Name used for the fallback library in the filesystem. |
| + File fallbackLib = new File(fallbackDirFile, sFallbackPrefix + libraryName); |
| + String fallbackLibName = fallbackLib.getPath(); |
| + |
| + // We either want to create a new fallback file or we want to check |
| + // the one we have is the same size as the one in the archive. So |
| + // we always open the zip file and locate the library entry. Sadly |
| + // this does take tens of milliseconds. |
| + long before = System.currentTimeMillis(); |
| + ZipFile zipfile = null; |
| + try { |
| + zipfile = new ZipFile(zipfileName); |
| + } catch (ZipException e) { |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "unable to open the zip file", e); |
| + } catch (IOException e) { |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "unable to open the zip file", e); |
| + } |
| + |
| + // Open the library inside the zip file. |
| + String nameInZip = nativeLibraryFilenameInZipFile(libraryName); |
| + ZipEntry zipEntry = null; |
| + try { |
| + zipEntry = zipfile.getEntry(nameInZip); |
| + } catch (IllegalStateException e) { |
| + close(zipfile, "zipfile"); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "zipfile has been closed", e); |
| + } |
| + |
| + boolean useExistingFallbackFile = fallbackLib.exists(); |
| + if (useExistingFallbackFile) { |
| + // Check that it is reasonable to use the existing file. |
| + File zipfileFile = new File(zipfileName); |
| + long zipfileTime = zipfileFile.lastModified(); |
| + long fallbackLibTime = fallbackLib.lastModified(); |
| + if (zipfileTime > fallbackLibTime) { |
| + Log.i(TAG, "Not using existing fallback file because the" |
| + + " APK file: " + zipfileName |
| + + " is newer then fallback library " + libraryName |
| + + " APK timestamp: " + zipfileTime |
| + + " fallback library timestamp " + fallbackLibTime); |
| + deleteFile(fallbackLib); |
| + useExistingFallbackFile = false; |
| + } else { |
| + long fallbackLibSize = fallbackLib.length(); |
| + long zipfileLibSize = zipEntry.getSize(); |
| + if (fallbackLibSize != zipfileLibSize) { |
| + Log.i(TAG, "Not using existing fallback file because the" |
| + + " library in the APK file is a different size: " |
| + + " fallback library size: " + fallbackLibSize |
| + + " library in APK size: " + zipfileLibSize); |
| + deleteFile(fallbackLib); |
| + useExistingFallbackFile = false; |
| + } |
| + } |
| + } |
|
picksi1
2014/10/28 12:25:26
Should the above block of code be re-jigged slight
Anton
2014/10/28 12:42:14
Agreed to the decomposition. Though fallbackLib.ex
|
| + |
| + if (createFallbackIfNeeded && !useExistingFallbackFile) { |
| + // Remove any "tmp-" and "fallback-" libraries which currently exist. |
| + for (File f : fallbackDirFile.listFiles()) { |
| + String name = f.getName(); |
| + if (name.endsWith(".so") && |
| + (name.startsWith(sFallbackPrefix + "lib") |
| + || name.startsWith(sTempPrefix + "lib"))) { |
| + deleteFile(f); |
| + } |
| + } |
| + // Locate the library inside the zip file. |
| + InputStream in = null; |
| + try { |
| + in = zipfile.getInputStream(zipEntry); |
| + } catch (IOException e) { |
| + close(zipfile, "zipfile"); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, |
| + "io error locating library in the zipfile", e); |
| + } |
| + // Temporary name used while the fallback library file is incomplete. |
| + File tmpFile = new File(fallbackDirFile, sTempPrefix + libraryName); |
| + |
| + // Open the output file. |
| + OutputStream out = null; |
| + try { |
| + out = new BufferedOutputStream(new FileOutputStream(tmpFile)); |
| + } catch (FileNotFoundException e) { |
| + close(in, "input stream"); |
| + close(zipfile, "zipfile"); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "failed to create fallback file", e); |
| + } catch (SecurityException e) { |
| + close(in, "input stream"); |
| + close(zipfile, "zipfile"); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, |
| + "security prevented creation of fallback file", e); |
| + } |
| + try { |
| + // Copy the library from the zipfile into the file system. |
| + byte[] buffer = new byte[4096]; |
|
picksi1
2014/10/28 12:25:26
Will this leak? Or does 'byte' contain a destructo
Anton
2014/10/28 12:42:14
Nope. This is Java it is garbage collected.
|
| + int len; |
| + while ((len = in.read(buffer)) != -1) { |
| + out.write(buffer, 0, len); |
| + } |
| + } catch (IOException e) { |
| + // Attempt to delete the fallback file. |
| + deleteTempFile(tmpFile); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "failed to make copy of library file", e); |
| + } finally { |
| + close(in, "input stream"); |
| + close(out, "output stream"); |
| + close(zipfile, "zipfile"); |
| + } |
| + if (!tmpFile.setReadable(/* readable */ true, /* ownerOnly */ false)) { |
| + deleteTempFile(tmpFile); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "failed to chmod +r the file", null); |
| + } |
| + if (!tmpFile.setExecutable(/* executable */ true, /* ownerOnly */ false)) { |
| + deleteTempFile(tmpFile); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "failed to chmod +x the file", null); |
| + } |
| + if (!tmpFile.renameTo(fallbackLib)) { |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "failed to rename the temporary file", null); |
| + } |
| + long after = System.currentTimeMillis(); |
| + Log.i(TAG, "Fallback copying library took: " + (after - before) + " millis"); |
| + Log.i(TAG, "Fallback library is: " + fallbackLibName + " exists: " + |
| + fallbackLib.exists()); |
| + useExistingFallbackFile = true; |
| + } else { |
| + close(zipfile, "zipfile"); |
| + long after = System.currentTimeMillis(); |
| + Log.i(TAG, "Checking the fallback library took: " + (after - before) + " millis"); |
| + } |
| + |
| + if (!useExistingFallbackFile) { |
| + if (fallbackLib.exists()) { |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "the fallback file was invalid", null); |
| + } else { |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, |
| + "the browser process did not create the fallback file", null); |
| + } |
| + } |
| + |
| + if (!nativeLoadLibrary(fallbackLibName, loadAddress, libInfo)) { |
| + // Delete the fallback library. In case the failure was because the |
| + // file was corrupt, this is our best bet at avoiding becoming |
| + // permenantly broken. |
| + deleteFile(fallbackLib); |
| + throwUnsatisfiedLinkError( |
| + fallbackLibName, zipfileName, "fallback copy did not load", null); |
| + } |
| + return fallbackLibName; |
| + } |
| + |
| /** |
| * Move activity from the native thread to the main UI thread. |
| * Called from native code on its own thread. Posts a callback from |
| @@ -886,6 +1136,14 @@ public class Linker { |
| LibInfo libInfo); |
| /** |
| + * Native method used to get the library filename in zip file: |
| + * "lib/<abi>/crazy.<lib_name>". |
| + * |
| + * @return the library filename (or empty string on failure). |
| + */ |
| + private static native String nativeLibraryFilenameInZipFile(String libName); |
| + |
| + /** |
| * Native method used to load a library which is inside a zipfile. |
| * @param zipfileName Filename of the zip file containing the library. |
| * @param library Platform specific library name (e.g. libfoo.so) |
| @@ -895,10 +1153,10 @@ public class Linker { |
| * @return true for success, false otherwise. |
| */ |
| private static native boolean nativeLoadLibraryInZipFile( |
| - String zipfileName, |
| - String libraryName, |
| - long loadAddress, |
| - LibInfo libInfo); |
| + String zipfileName, |
| + String libraryName, |
| + long loadAddress, |
| + LibInfo libInfo); |
| /** |
| * Native method used to create a shared RELRO section. |
| @@ -1089,5 +1347,5 @@ public class Linker { |
| // Used to pass the shared RELRO Bundle through Binder. |
| public static final String EXTRA_LINKER_SHARED_RELROS = |
| - "org.chromium.base.android.linker.shared_relros"; |
| + "org.chromium.base.android.linker.shared_relros"; |
| } |