/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.soloader;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.HashSet;
import java.util.ArrayList;
import java.io.FileNotFoundException;
import java.util.Set;
import javax.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.StatFs;
import android.util.Log;
import android.content.pm.ApplicationInfo;
/**
* Note that {@link com.facebook.base.app.DelegatingApplication} will automatically register itself
* with SoLoader before running application-specific code; most applications do not need to call
* {@link #init} explicitly.
*/
@SuppressLint({
"BadMethodUse-android.util.Log.v",
"BadMethodUse-android.util.Log.d",
"BadMethodUse-android.util.Log.i",
"BadMethodUse-android.util.Log.w",
"BadMethodUse-android.util.Log.e",
})
public class SoLoader {
/* package */ static final String TAG = "SoLoader";
/* package */ static final boolean DEBUG = false;
/**
* Ordered list of sources to consult when trying to load a shared library or one of its
* dependencies. {@code null} indicates that SoLoader is uninitialized.
*/
@Nullable private static SoSource[] sSoSources = null;
/**
* Records the sonames (e.g., "libdistract.so") of shared libraries we've loaded.
*/
private static final Set<String> sLoadedLibraries = new HashSet<>();
/**
* Initializes native code loading for this app; this class's other static facilities cannot be
* used until this {@link #init} is called. This method is idempotent: calls after the first are
* ignored.
*
* @param context - application context.
* @param isNativeExopackageEnabled - whether native exopackage feature is enabled in the build.
*/
public static synchronized void init(@Nullable Context context, boolean isNativeExopackageEnabled) {
if (sSoSources == null) {
ArrayList<SoSource> soSources = new ArrayList<>();
//
// Add SoSource objects for each of the system library directories.
//
String LD_LIBRARY_PATH = System.getenv("LD_LIBRARY_PATH");
if (LD_LIBRARY_PATH == null) {
LD_LIBRARY_PATH = "/vendor/lib:/system/lib";
}
String[] systemLibraryDirectories = LD_LIBRARY_PATH.split(":");
for (int i = 0; i < systemLibraryDirectories.length; ++i) {
// Don't pass DirectorySoSource.RESOLVE_DEPENDENCIES for directories we find on
// LD_LIBRARY_PATH: Bionic's dynamic linker is capable of correctly resolving dependencies
// these libraries have on each other, so doing that ourselves would be a waste.
File systemSoDirectory = new File(systemLibraryDirectories[i]);
soSources.add(
new DirectorySoSource(
systemSoDirectory,
DirectorySoSource.ON_LD_LIBRARY_PATH));
}
//
// We can only proceed forward if we have a Context. The prominent case
// where we don't have a Context is barebones dalvikvm instantiations. In
// that case, the caller is responsible for providing a correct LD_LIBRARY_PATH.
//
if (context != null) {
//
// Prepend our own SoSource for our own DSOs.
//
ApplicationInfo applicationInfo = context.getApplicationInfo();
boolean isSystemApplication =
(applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 &&
(applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0;
try {
if (isNativeExopackageEnabled) {
soSources.add(0, new ExoSoSource(context));
} else if (isSystemApplication) {
soSources.add(0, new ApkSoSource(context));
} else {
// Delete the old libs directory if we don't need it.
SysUtil.dumbDeleteRecrusive(SysUtil.getLibsDirectory(context));
int ourSoSourceFlags = 0;
// On old versions of Android, Bionic doesn't add our library directory to its internal
// search path, and the system doesn't resolve dependencies between modules we ship. On
// these systems, we resolve dependencies ourselves. On other systems, Bionic's built-in
// resolver suffices.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
ourSoSourceFlags |= DirectorySoSource.RESOLVE_DEPENDENCIES;
}
SoSource ourSoSource = new DirectorySoSource(
new File(applicationInfo.nativeLibraryDir),
ourSoSourceFlags);
soSources.add(0, ourSoSource);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
sSoSources = soSources.toArray(new SoSource[soSources.size()]);
}
}
/**
* Turn shared-library loading into a no-op. Useful in special circumstances.
*/
public static void setInTestMode() {
sSoSources = new SoSource[]{new NoopSoSource()};
}
/**
* Load a shared library, initializing any JNI binding it contains.
*
* @param shortName Name of library to find, without "lib" prefix or ".so" suffix
*/
public static synchronized void loadLibrary(String shortName)
throws UnsatisfiedLinkError
{
if (sSoSources == null) {
// This should never happen during normal operation,
// but if we're running in a non-Android environment,
// fall back to System.loadLibrary.
if ("http://www.android.com/".equals(System.getProperty("java.vendor.url"))) {
// This will throw.
assertInitialized();
} else {
// Not on an Android system. Ask the JVM to load for us.
System.loadLibrary(shortName);
return;
}
}
try {
loadLibraryBySoName(System.mapLibraryName(shortName), 0);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Unpack library and its dependencies, returning the location of the unpacked library file. All
* non-system dependencies of the given library will either be on LD_LIBRARY_PATH or will be in
* the same directory as the returned File.
*
* @param shortName Name of library to find, without "lib" prefix or ".so" suffix
* @return Unpacked DSO location
*/
public static File unpackLibraryAndDependencies(String shortName)
throws UnsatisfiedLinkError
{
assertInitialized();
try {
return unpackLibraryBySoName(System.mapLibraryName(shortName));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/* package */ static void loadLibraryBySoName(String soName, int loadFlags) throws IOException {
int result = sLoadedLibraries.contains(soName)
? SoSource.LOAD_RESULT_LOADED
: SoSource.LOAD_RESULT_NOT_FOUND;
for (int i = 0; result == SoSource.LOAD_RESULT_NOT_FOUND && i < sSoSources.length; ++i) {
result = sSoSources[i].loadLibrary(soName, loadFlags);
}
if (result == SoSource.LOAD_RESULT_NOT_FOUND) {
throw new UnsatisfiedLinkError("could find DSO to load: " + soName);
}
if (result == SoSource.LOAD_RESULT_LOADED) {
sLoadedLibraries.add(soName);
}
}
/* package */ static File unpackLibraryBySoName(String soName) throws IOException {
for (int i = 0; i < sSoSources.length; ++i) {
File unpacked = sSoSources[i].unpackLibrary(soName);
if (unpacked != null) {
return unpacked;
}
}
throw new FileNotFoundException(soName);
}
private static void assertInitialized() {
if (sSoSources == null) {
throw new RuntimeException("SoLoader.init() not yet called");
}
}
}