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"; |
} |