Bug 1047549 - Copy libraries out of the APK when they're missing. r=blassey, a=lmandel
authorRichard Newman <rnewman@mozilla.com>
Wed, 06 Aug 2014 11:38:06 -0700
changeset 208262 9d8f79b400bf
parent 208261 e8fbf14de311
child 208263 551f71d3138f
push id3794
push userryanvm@gmail.com
push date2014-08-07 20:40 +0000
treeherdermozilla-beta@599c7756380c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, lmandel
bugs1047549
milestone32.0
Bug 1047549 - Copy libraries out of the APK when they're missing. r=blassey, a=lmandel
mobile/android/base/mozglue/GeckoLoader.java.in
--- a/mobile/android/base/mozglue/GeckoLoader.java.in
+++ b/mobile/android/base/mozglue/GeckoLoader.java.in
@@ -1,28 +1,32 @@
 #filter substitution
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.mozglue;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Environment;
 import android.util.Log;
 
-import java.io.File;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.util.Locale;
-
 public final class GeckoLoader {
     private static final String LOGTAG = "GeckoLoader";
 
     // These match AppConstants, but we're built earlier.
     private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
     private static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
 
     private static volatile Intent sIntent;
@@ -258,16 +262,99 @@ public final class GeckoLoader {
             sNSSLibsLoaded = true;
         }
 
         loadMozGlue(context);
         loadLibsSetup(context);
         loadNSSLibsNative(apkName, false);
     }
 
+    /**
+     * Copy a library out of our APK.
+     *
+     * @param context a Context.
+     * @param lib the name of the library; e.g., "mozglue".
+     * @param outDir the output directory for the .so. No trailing slash.
+     * @return true on success, false on failure.
+     */
+    private static boolean extractLibrary(final Context context, final String lib, final String outDir) {
+        final String apkPath = context.getApplicationInfo().sourceDir;
+
+        // Sanity check.
+        if (!apkPath.endsWith(".apk")) {
+            Log.w(LOGTAG, "sourceDir is not an APK.");
+            return false;
+        }
+
+        // Try to extract the named library from the APK.
+        File outDirFile = new File(outDir);
+        if (!outDirFile.isDirectory()) {
+            if (!outDirFile.mkdirs()) {
+                Log.e(LOGTAG, "Couldn't create " + outDir);
+                return false;
+            }
+        }
+
+        final String abi = android.os.Build.CPU_ABI;
+        try {
+            final ZipFile zipFile = new ZipFile(new File(apkPath));
+            try {
+                final String libPath = "lib/" + abi + "/lib" + lib + ".so";
+                final ZipEntry entry = zipFile.getEntry(libPath);
+                if (entry == null) {
+                    Log.w(LOGTAG, libPath + " not found in APK " + apkPath);
+                    return false;
+                }
+
+                final InputStream in = zipFile.getInputStream(entry);
+                try {
+                    final String outPath = outDir + "/lib" + lib + ".so";
+                    final FileOutputStream out = new FileOutputStream(outPath);
+                    final byte[] bytes = new byte[1024];
+                    int read;
+
+                    Log.d(LOGTAG, "Copying " + libPath + " to " + outPath);
+                    boolean failed = false;
+                    try {
+                        while ((read = in.read(bytes, 0, 1024)) != -1) {
+                            out.write(bytes, 0, read);
+                        }
+                    } catch (Exception e) {
+                        Log.w(LOGTAG, "Failing library copy.", e);
+                        failed = true;
+                    } finally {
+                        out.close();
+                    }
+
+                    if (failed) {
+                        // Delete the partial copy so we don't fail to load it.
+                        // Don't bother to check the return value -- there's nothing
+                        // we can do about a failure.
+                        new File(outPath).delete();
+                    } else {
+                        // Mark the file as executable. This doesn't seem to be
+                        // necessary for the loader, but it's the normal state of
+                        // affairs.
+                        Log.d(LOGTAG, "Marking " + outPath + " as executable.");
+                        new File(outPath).setExecutable(true);
+                    }
+
+                    return !failed;
+                } finally {
+                    in.close();
+                }
+            } finally {
+                zipFile.close();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Failed to extract lib from APK.", e);
+            return false;
+        }
+    }
+
     private static String getLoadDiagnostics(final Context context, final String lib) {
         final StringBuilder message = new StringBuilder("LOAD ");
         message.append(lib);
 
         // These might differ. If so, we know why the library won't load!
         message.append(": ABI: " + MOZ_APP_ABI + ", " + android.os.Build.CPU_ABI);
         message.append(": Data: " + context.getApplicationInfo().dataDir);
         try {
@@ -361,32 +448,58 @@ public final class GeckoLoader {
 
     public static void doLoadLibrary(final Context context, final String lib) {
         final Throwable e = doLoadLibraryExpected(context, lib);
         if (e == null) {
             // Success.
             return;
         }
 
+        // If we're in a mismatched UID state (Bug 1042935 Comment 16) there's really
+        // nothing we can do.
+        if (Build.VERSION.SDK_INT >= 9) {
+            final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
+            if (nativeLibPath.contains("mismatched_uid")) {
+                throw new RuntimeException("Fatal: mismatched UID: cannot load.");
+            }
+        }
+
         // Attempt 3: try finding the path the pseudo-supported way using .dataDir.
         final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so";
         if (attemptLoad(dataLibPath)) {
             return;
         }
 
         // Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
         if (attemptLoad("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so")) {
             return;
         }
 
         // Attempt 5: even more optimistic.
         if (attemptLoad("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so")) {
             return;
         }
 
+        // Look in our files directory, copying from the APK first if necessary.
+        final String filesLibDir = context.getFilesDir() + "/lib";
+        final String filesLibPath = filesLibDir + "/lib" + lib + ".so";
+        if (new File(filesLibPath).exists()) {
+            if (attemptLoad(filesLibPath)) {
+                return;
+            }
+        } else {
+            // Try copying.
+            if (extractLibrary(context, lib, filesLibDir)) {
+                // Let's try it!
+                if (attemptLoad(filesLibPath)) {
+                    return;
+                }
+            }
+        }
+
         // Give up loudly, leaking information to debug the failure.
         final String message = getLoadDiagnostics(context, lib);
         Log.e(LOGTAG, "Load diagnostics: " + message);
 
         // Throw the descriptive message, using the original library load
         // failure as the cause.
         throw new RuntimeException(message, e);
     }