mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-11 22:32:38 +08:00
Release React Native for Android
This is an early release and there are several things that are known not to work if you're porting your iOS app to Android. See the Known Issues guide on the website. We will work with the community to reach platform parity with iOS.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that extracts libraries from an APK to the filesystem.
|
||||
*/
|
||||
public class ApkSoSource extends DirectorySoSource {
|
||||
|
||||
private static final String TAG = SoLoader.TAG;
|
||||
private static final boolean DEBUG = SoLoader.DEBUG;
|
||||
|
||||
/**
|
||||
* Make a new ApkSoSource that extracts DSOs from our APK instead of relying on the system to do
|
||||
* the extraction for us.
|
||||
*
|
||||
* @param context Application context
|
||||
*/
|
||||
public ApkSoSource(Context context) throws IOException {
|
||||
//
|
||||
// Initialize a normal DirectorySoSource that will load from our extraction directory. At this
|
||||
// point, the directory may be empty or contain obsolete libraries, but that's okay.
|
||||
//
|
||||
|
||||
super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES);
|
||||
|
||||
//
|
||||
// Synchronize the contents of that directory with the library payload in our APK, deleting and
|
||||
// extracting as needed.
|
||||
//
|
||||
|
||||
try (JarFile apk = new JarFile(context.getApplicationInfo().publicSourceDir)) {
|
||||
File libsDir = super.soDirectory;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "synchronizing log directory: " + libsDir);
|
||||
}
|
||||
|
||||
Map<String, SoInfo> providedLibraries = findProvidedLibraries(apk);
|
||||
try (FileLocker lock = SysUtil.lockLibsDirectory(context)) {
|
||||
// Delete files in libsDir that we don't provide or that are out of date. Forget about any
|
||||
// libraries that are up-to-date already so we don't unpack them below.
|
||||
File extantFiles[] = libsDir.listFiles();
|
||||
for (int i = 0; i < extantFiles.length; ++i) {
|
||||
File extantFile = extantFiles[i];
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "considering libdir file: " + extantFile);
|
||||
}
|
||||
|
||||
String name = extantFile.getName();
|
||||
SoInfo so = providedLibraries.get(name);
|
||||
boolean shouldDelete =
|
||||
(so == null ||
|
||||
so.entry.getSize() != extantFile.length() ||
|
||||
so.entry.getTime() != extantFile.lastModified());
|
||||
boolean upToDate = (so != null && !shouldDelete);
|
||||
|
||||
if (shouldDelete) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile);
|
||||
}
|
||||
SysUtil.deleteOrThrow(extantFile);
|
||||
}
|
||||
|
||||
if (upToDate) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "found up-to-date library: " + extantFile);
|
||||
}
|
||||
providedLibraries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now extract any libraries left in providedLibraries; we removed all the up-to-date ones.
|
||||
for (SoInfo so : providedLibraries.values()) {
|
||||
JarEntry entry = so.entry;
|
||||
try (InputStream is = apk.getInputStream(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "extracting library: " + so.soName);
|
||||
}
|
||||
SysUtil.reliablyCopyExecutable(
|
||||
is,
|
||||
new File(libsDir, so.soName),
|
||||
entry.getSize(),
|
||||
entry.getTime());
|
||||
}
|
||||
|
||||
SysUtil.freeCopyBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the shared libraries provided in this APK and supported on this system. Each returend
|
||||
* SoInfo points to the most preferred version of that library bundled with the given APK: for
|
||||
* example, if we're on an armv7-a system and we have both arm and armv7-a versions of libfoo, the
|
||||
* returned entry for libfoo points to the armv7-a version of libfoo.
|
||||
*
|
||||
* The caller owns the returned value and may mutate it.
|
||||
*
|
||||
* @param apk Opened application APK file
|
||||
* @return Map of sonames to SoInfo instances
|
||||
*/
|
||||
private static Map<String, SoInfo> findProvidedLibraries(JarFile apk) {
|
||||
// Subgroup 1: ABI. Subgroup 2: soname.
|
||||
Pattern libPattern = Pattern.compile("^lib/([^/]+)/([^/]+\\.so)$");
|
||||
HashMap<String, SoInfo> providedLibraries = new HashMap<>();
|
||||
String[] supportedAbis = SysUtil.getSupportedAbis();
|
||||
Enumeration<JarEntry> entries = apk.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
Matcher m = libPattern.matcher(entry.getName());
|
||||
if (m.matches()) {
|
||||
String libraryAbi = m.group(1);
|
||||
String soName = m.group(2);
|
||||
int abiScore = SysUtil.findAbiScore(supportedAbis, libraryAbi);
|
||||
if (abiScore >= 0) {
|
||||
SoInfo so = providedLibraries.get(soName);
|
||||
if (so == null || abiScore < so.abiScore) {
|
||||
providedLibraries.put(soName, new SoInfo(soName, entry, abiScore));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return providedLibraries;
|
||||
}
|
||||
|
||||
private static final class SoInfo {
|
||||
public final String soName;
|
||||
public final JarEntry entry;
|
||||
public final int abiScore;
|
||||
|
||||
SoInfo(String soName, JarEntry entry, int abiScore) {
|
||||
this.soName = soName;
|
||||
this.entry = entry;
|
||||
this.abiScore = abiScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that finds shared libraries in a given directory.
|
||||
*/
|
||||
public class DirectorySoSource extends SoSource {
|
||||
|
||||
public static final int RESOLVE_DEPENDENCIES = 1;
|
||||
public static final int ON_LD_LIBRARY_PATH = 2;
|
||||
|
||||
protected final File soDirectory;
|
||||
private final int flags;
|
||||
|
||||
/**
|
||||
* Make a new DirectorySoSource. If {@code flags} contains {@code RESOLVE_DEPENDENCIES},
|
||||
* recursively load dependencies for shared objects loaded from this directory. (We shouldn't
|
||||
* need to resolve dependencies for libraries loaded from system directories: the dynamic linker
|
||||
* is smart enough to do it on its own there.)
|
||||
*/
|
||||
public DirectorySoSource(File soDirectory, int flags) {
|
||||
this.soDirectory = soDirectory;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadLibrary(String soName, int loadFlags) throws IOException {
|
||||
File soFile = new File(soDirectory, soName);
|
||||
if (!soFile.exists()) {
|
||||
return LOAD_RESULT_NOT_FOUND;
|
||||
}
|
||||
|
||||
if ((loadFlags & LOAD_FLAG_ALLOW_IMPLICIT_PROVISION) != 0 &&
|
||||
(flags & ON_LD_LIBRARY_PATH) != 0) {
|
||||
return LOAD_RESULT_IMPLICITLY_PROVIDED;
|
||||
}
|
||||
|
||||
if ((flags & RESOLVE_DEPENDENCIES) != 0) {
|
||||
String dependencies[] = MinElf.extract_DT_NEEDED(soFile);
|
||||
for (int i = 0; i < dependencies.length; ++i) {
|
||||
String dependency = dependencies[i];
|
||||
if (dependency.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SoLoader.loadLibraryBySoName(
|
||||
dependency,
|
||||
(loadFlags | LOAD_FLAG_ALLOW_IMPLICIT_PROVISION));
|
||||
}
|
||||
}
|
||||
|
||||
System.load(soFile.getAbsolutePath());
|
||||
return LOAD_RESULT_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File unpackLibrary(String soName) throws IOException {
|
||||
File soFile = new File(soDirectory, soName);
|
||||
if (soFile.exists()) {
|
||||
return soFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Dyn {
|
||||
public static final int d_tag = 0x0;
|
||||
public static final int d_un = 0x4;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Ehdr {
|
||||
public static final int e_ident = 0x0;
|
||||
public static final int e_type = 0x10;
|
||||
public static final int e_machine = 0x12;
|
||||
public static final int e_version = 0x14;
|
||||
public static final int e_entry = 0x18;
|
||||
public static final int e_phoff = 0x1c;
|
||||
public static final int e_shoff = 0x20;
|
||||
public static final int e_flags = 0x24;
|
||||
public static final int e_ehsize = 0x28;
|
||||
public static final int e_phentsize = 0x2a;
|
||||
public static final int e_phnum = 0x2c;
|
||||
public static final int e_shentsize = 0x2e;
|
||||
public static final int e_shnum = 0x30;
|
||||
public static final int e_shstrndx = 0x32;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Phdr {
|
||||
public static final int p_type = 0x0;
|
||||
public static final int p_offset = 0x4;
|
||||
public static final int p_vaddr = 0x8;
|
||||
public static final int p_paddr = 0xc;
|
||||
public static final int p_filesz = 0x10;
|
||||
public static final int p_memsz = 0x14;
|
||||
public static final int p_flags = 0x18;
|
||||
public static final int p_align = 0x1c;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Shdr {
|
||||
public static final int sh_name = 0x0;
|
||||
public static final int sh_type = 0x4;
|
||||
public static final int sh_flags = 0x8;
|
||||
public static final int sh_addr = 0xc;
|
||||
public static final int sh_offset = 0x10;
|
||||
public static final int sh_size = 0x14;
|
||||
public static final int sh_link = 0x18;
|
||||
public static final int sh_info = 0x1c;
|
||||
public static final int sh_addralign = 0x20;
|
||||
public static final int sh_entsize = 0x24;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Dyn {
|
||||
public static final int d_tag = 0x0;
|
||||
public static final int d_un = 0x8;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Ehdr {
|
||||
public static final int e_ident = 0x0;
|
||||
public static final int e_type = 0x10;
|
||||
public static final int e_machine = 0x12;
|
||||
public static final int e_version = 0x14;
|
||||
public static final int e_entry = 0x18;
|
||||
public static final int e_phoff = 0x20;
|
||||
public static final int e_shoff = 0x28;
|
||||
public static final int e_flags = 0x30;
|
||||
public static final int e_ehsize = 0x34;
|
||||
public static final int e_phentsize = 0x36;
|
||||
public static final int e_phnum = 0x38;
|
||||
public static final int e_shentsize = 0x3a;
|
||||
public static final int e_shnum = 0x3c;
|
||||
public static final int e_shstrndx = 0x3e;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Phdr {
|
||||
public static final int p_type = 0x0;
|
||||
public static final int p_flags = 0x4;
|
||||
public static final int p_offset = 0x8;
|
||||
public static final int p_vaddr = 0x10;
|
||||
public static final int p_paddr = 0x18;
|
||||
public static final int p_filesz = 0x20;
|
||||
public static final int p_memsz = 0x28;
|
||||
public static final int p_align = 0x30;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Shdr {
|
||||
public static final int sh_name = 0x0;
|
||||
public static final int sh_type = 0x4;
|
||||
public static final int sh_flags = 0x8;
|
||||
public static final int sh_addr = 0x10;
|
||||
public static final int sh_offset = 0x18;
|
||||
public static final int sh_size = 0x20;
|
||||
public static final int sh_link = 0x28;
|
||||
public static final int sh_info = 0x2c;
|
||||
public static final int sh_addralign = 0x30;
|
||||
public static final int sh_entsize = 0x38;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that retrieves libraries from an exopackage repository.
|
||||
*/
|
||||
public class ExoSoSource extends DirectorySoSource {
|
||||
|
||||
private static final String TAG = SoLoader.TAG;
|
||||
private static final boolean DEBUG = SoLoader.DEBUG;
|
||||
|
||||
/**
|
||||
* @param context Application context
|
||||
*/
|
||||
public ExoSoSource(Context context) throws IOException {
|
||||
//
|
||||
// Initialize a normal DirectorySoSource that will load from our extraction directory. At this
|
||||
// point, the directory may be empty or contain obsolete libraries, but that's okay.
|
||||
//
|
||||
|
||||
super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES);
|
||||
|
||||
//
|
||||
// Synchronize the contents of that directory with the library payload in our APK, deleting and
|
||||
// extracting as needed.
|
||||
//
|
||||
|
||||
File libsDir = super.soDirectory;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "synchronizing log directory: " + libsDir);
|
||||
}
|
||||
|
||||
Map<String, File> providedLibraries = findProvidedLibraries(context);
|
||||
try (FileLocker lock = SysUtil.lockLibsDirectory(context)) {
|
||||
// Delete files in libsDir that we don't provide or that are out of date. Forget about any
|
||||
// libraries that are up-to-date already so we don't unpack them below.
|
||||
File extantFiles[] = libsDir.listFiles();
|
||||
for (int i = 0; i < extantFiles.length; ++i) {
|
||||
File extantFile = extantFiles[i];
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "considering libdir file: " + extantFile);
|
||||
}
|
||||
|
||||
String name = extantFile.getName();
|
||||
File sourceFile = providedLibraries.get(name);
|
||||
boolean shouldDelete =
|
||||
(sourceFile == null ||
|
||||
sourceFile.length() != extantFile.length() ||
|
||||
sourceFile.lastModified() != extantFile.lastModified());
|
||||
boolean upToDate = (sourceFile != null && !shouldDelete);
|
||||
|
||||
if (shouldDelete) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile);
|
||||
}
|
||||
SysUtil.deleteOrThrow(extantFile);
|
||||
}
|
||||
|
||||
if (upToDate) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "found up-to-date library: " + extantFile);
|
||||
}
|
||||
providedLibraries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now extract any libraries left in providedLibraries; we removed all the up-to-date ones.
|
||||
for (String soName : providedLibraries.keySet()) {
|
||||
File sourceFile = providedLibraries.get(soName);
|
||||
try (InputStream is = new FileInputStream(sourceFile)) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "extracting library: " + soName);
|
||||
}
|
||||
SysUtil.reliablyCopyExecutable(
|
||||
is,
|
||||
new File(libsDir, soName),
|
||||
sourceFile.length(),
|
||||
sourceFile.lastModified());
|
||||
}
|
||||
|
||||
SysUtil.freeCopyBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the shared libraries provided through the exopackage directory and supported on this
|
||||
* system. Each returend SoInfo points to the most preferred version of that library included in
|
||||
* our exopackage directory: for example, if we're on an armv7-a system and we have both arm and
|
||||
* armv7-a versions of libfoo, the returned entry for libfoo points to the armv7-a version of
|
||||
* libfoo.
|
||||
*
|
||||
* The caller owns the returned value and may mutate it.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return Map of sonames to providing files
|
||||
*/
|
||||
private static Map<String, File> findProvidedLibraries(Context context) throws IOException {
|
||||
File exoDir = new File(
|
||||
"/data/local/tmp/exopackage/"
|
||||
+ context.getPackageName()
|
||||
+ "/native-libs/");
|
||||
|
||||
HashMap<String, File> providedLibraries = new HashMap<>();
|
||||
for (String abi : SysUtil.getSupportedAbis()) {
|
||||
File abiDir = new File(exoDir, abi);
|
||||
if (!abiDir.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File metadata = new File(abiDir, "metadata.txt");
|
||||
if (!metadata.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (FileReader fr = new FileReader(metadata);
|
||||
BufferedReader br = new BufferedReader(fr)) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int sep = line.indexOf(' ');
|
||||
if (sep == -1) {
|
||||
throw new RuntimeException("illegal line in exopackage metadata: [" + line + "]");
|
||||
}
|
||||
|
||||
String soName = line.substring(0, sep) + ".so";
|
||||
String backingFile = line.substring(sep + 1);
|
||||
|
||||
if (!providedLibraries.containsKey(soName)) {
|
||||
providedLibraries.put(soName, new File(abiDir, backingFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return providedLibraries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.FileOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.io.Closeable;
|
||||
|
||||
public final class FileLocker implements Closeable {
|
||||
|
||||
private final FileOutputStream mLockFileOutputStream;
|
||||
private final FileLock mLock;
|
||||
|
||||
public static FileLocker lock(File lockFile) throws IOException {
|
||||
return new FileLocker(lockFile);
|
||||
}
|
||||
|
||||
private FileLocker(File lockFile) throws IOException {
|
||||
mLockFileOutputStream = new FileOutputStream(lockFile);
|
||||
FileLock lock = null;
|
||||
try {
|
||||
lock = mLockFileOutputStream.getChannel().lock();
|
||||
} finally {
|
||||
if (lock == null) {
|
||||
mLockFileOutputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
mLock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
mLock.release();
|
||||
} finally {
|
||||
mLockFileOutputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
282
ReactAndroid/src/main/java/com/facebook/soloader/MinElf.java
Normal file
282
ReactAndroid/src/main/java/com/facebook/soloader/MinElf.java
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
/**
|
||||
* Extract SoLoader boottsrap information from an ELF file. This is not a general purpose ELF
|
||||
* library.
|
||||
*
|
||||
* See specification at http://www.sco.com/developers/gabi/latest/contents.html. You will not be
|
||||
* able to verify the operation of the functions below without having read the ELF specification.
|
||||
*/
|
||||
public final class MinElf {
|
||||
|
||||
public static final int ELF_MAGIC = 0x464c457f;
|
||||
|
||||
public static final int DT_NULL = 0;
|
||||
public static final int DT_NEEDED = 1;
|
||||
public static final int DT_STRTAB = 5;
|
||||
|
||||
public static final int PT_LOAD = 1;
|
||||
public static final int PT_DYNAMIC = 2;
|
||||
|
||||
public static final int PN_XNUM = 0xFFFF;
|
||||
|
||||
public static String[] extract_DT_NEEDED(File elfFile) throws IOException {
|
||||
FileInputStream is = new FileInputStream(elfFile);
|
||||
try {
|
||||
return extract_DT_NEEDED(is.getChannel());
|
||||
} finally {
|
||||
is.close(); // Won't throw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treating {@code bb} as an ELF file, extract all the DT_NEEDED entries from its dynamic section.
|
||||
*
|
||||
* @param fc FileChannel referring to ELF file
|
||||
* @return Array of strings, one for each DT_NEEDED entry, in file order
|
||||
*/
|
||||
public static String[] extract_DT_NEEDED(FileChannel fc)
|
||||
throws IOException {
|
||||
|
||||
//
|
||||
// All constants below are fixed by the ELF specification and are the offsets of fields within
|
||||
// the elf.h data structures.
|
||||
//
|
||||
|
||||
ByteBuffer bb = ByteBuffer.allocate(8 /* largest read unit */);
|
||||
|
||||
// Read ELF header.
|
||||
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (getu32(fc, bb, Elf32_Ehdr.e_ident) != ELF_MAGIC) {
|
||||
throw new ElfError("file is not ELF");
|
||||
}
|
||||
|
||||
boolean is32 = (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x4) == 1);
|
||||
if (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x5) == 2) {
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
// Offsets above are identical in 32- and 64-bit cases.
|
||||
|
||||
// Find the offset of the dynamic linking information.
|
||||
|
||||
long e_phoff = is32
|
||||
? getu32(fc, bb, Elf32_Ehdr.e_phoff)
|
||||
: get64(fc, bb, Elf64_Ehdr.e_phoff);
|
||||
|
||||
long e_phnum = is32
|
||||
? getu16(fc, bb, Elf32_Ehdr.e_phnum)
|
||||
: getu16(fc, bb, Elf64_Ehdr.e_phnum);
|
||||
|
||||
int e_phentsize = is32
|
||||
? getu16(fc, bb, Elf32_Ehdr.e_phentsize)
|
||||
: getu16(fc, bb, Elf64_Ehdr.e_phentsize);
|
||||
|
||||
if (e_phnum == PN_XNUM) { // Overflowed into section[0].sh_info
|
||||
|
||||
long e_shoff = is32
|
||||
? getu32(fc, bb, Elf32_Ehdr.e_shoff)
|
||||
: get64(fc, bb, Elf64_Ehdr.e_shoff);
|
||||
|
||||
long sh_info = is32
|
||||
? getu32(fc, bb, e_shoff + Elf32_Shdr.sh_info)
|
||||
: getu32(fc, bb, e_shoff + Elf64_Shdr.sh_info);
|
||||
|
||||
e_phnum = sh_info;
|
||||
}
|
||||
|
||||
long dynStart = 0;
|
||||
long phdr = e_phoff;
|
||||
|
||||
for (long i = 0; i < e_phnum; ++i) {
|
||||
long p_type = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_type)
|
||||
: getu32(fc, bb, phdr + Elf64_Phdr.p_type);
|
||||
|
||||
if (p_type == PT_DYNAMIC) {
|
||||
long p_offset = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_offset)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_offset);
|
||||
|
||||
dynStart = p_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
phdr += e_phentsize;
|
||||
}
|
||||
|
||||
if (dynStart == 0) {
|
||||
throw new ElfError("ELF file does not contain dynamic linking information");
|
||||
}
|
||||
|
||||
// Walk the items in the dynamic section, counting the DT_NEEDED entries. Also remember where
|
||||
// the string table for those entries lives. That table is a pointer, which we translate to an
|
||||
// offset below.
|
||||
|
||||
long d_tag;
|
||||
int nr_DT_NEEDED = 0;
|
||||
long dyn = dynStart;
|
||||
long ptr_DT_STRTAB = 0;
|
||||
|
||||
do {
|
||||
d_tag = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_tag)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_tag);
|
||||
|
||||
if (d_tag == DT_NEEDED) {
|
||||
if (nr_DT_NEEDED == Integer.MAX_VALUE) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
nr_DT_NEEDED += 1;
|
||||
} else if (d_tag == DT_STRTAB) {
|
||||
ptr_DT_STRTAB = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_un)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_un);
|
||||
}
|
||||
|
||||
dyn += is32 ? 8 : 16;
|
||||
} while (d_tag != DT_NULL);
|
||||
|
||||
if (ptr_DT_STRTAB == 0) {
|
||||
throw new ElfError("Dynamic section string-table not found");
|
||||
}
|
||||
|
||||
// Translate the runtime string table pointer we found above to a file offset.
|
||||
|
||||
long off_DT_STRTAB = 0;
|
||||
phdr = e_phoff;
|
||||
|
||||
for (int i = 0; i < e_phnum; ++i) {
|
||||
long p_type = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_type)
|
||||
: getu32(fc, bb, phdr + Elf64_Phdr.p_type);
|
||||
|
||||
if (p_type == PT_LOAD) {
|
||||
long p_vaddr = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_vaddr)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_vaddr);
|
||||
|
||||
long p_memsz = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_memsz)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_memsz);
|
||||
|
||||
if (p_vaddr <= ptr_DT_STRTAB && ptr_DT_STRTAB < p_vaddr + p_memsz) {
|
||||
long p_offset = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_offset)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_offset);
|
||||
|
||||
off_DT_STRTAB = p_offset + (ptr_DT_STRTAB - p_vaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
phdr += e_phentsize;
|
||||
}
|
||||
|
||||
if (off_DT_STRTAB == 0) {
|
||||
throw new ElfError("did not find file offset of DT_STRTAB table");
|
||||
}
|
||||
|
||||
String[] needed = new String[nr_DT_NEEDED];
|
||||
|
||||
nr_DT_NEEDED = 0;
|
||||
dyn = dynStart;
|
||||
|
||||
do {
|
||||
d_tag = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_tag)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_tag);
|
||||
|
||||
if (d_tag == DT_NEEDED) {
|
||||
long d_val = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_un)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_un);
|
||||
|
||||
needed[nr_DT_NEEDED] = getSz(fc, bb, off_DT_STRTAB + d_val);
|
||||
if (nr_DT_NEEDED == Integer.MAX_VALUE) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
nr_DT_NEEDED += 1;
|
||||
}
|
||||
|
||||
dyn += is32 ? 8 : 16;
|
||||
} while (d_tag != DT_NULL);
|
||||
|
||||
if (nr_DT_NEEDED != needed.length) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
return needed;
|
||||
}
|
||||
|
||||
private static String getSz(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
short b;
|
||||
while ((b = getu8(fc, bb, offset++)) != 0) {
|
||||
sb.append((char) b);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void read(FileChannel fc, ByteBuffer bb, int sz, long offset)
|
||||
throws IOException {
|
||||
bb.position(0);
|
||||
bb.limit(sz);
|
||||
if (fc.read(bb, offset) != sz) {
|
||||
throw new ElfError("ELF file truncated");
|
||||
}
|
||||
|
||||
bb.position(0);
|
||||
}
|
||||
|
||||
private static long get64(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 8, offset);
|
||||
return bb.getLong();
|
||||
}
|
||||
|
||||
private static long getu32(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 4, offset);
|
||||
return bb.getInt() & 0xFFFFFFFFL; // signed -> unsigned
|
||||
}
|
||||
|
||||
private static int getu16(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 2, offset);
|
||||
return bb.getShort() & (int) 0xFFFF; // signed -> unsigned
|
||||
}
|
||||
|
||||
private static short getu8(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 1, offset);
|
||||
return (short) (bb.get() & 0xFF); // signed -> unsigned
|
||||
}
|
||||
|
||||
private static class ElfError extends RuntimeException {
|
||||
ElfError(String why) {
|
||||
super(why);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.util.List;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This is the base class for all the classes representing certain native library.
|
||||
* For loading native libraries we should always inherit from this class and provide relevant
|
||||
* information (libraries to load, code to test native call, dependencies?).
|
||||
* <p>
|
||||
* This instances should be singletons provided by DI.
|
||||
* <p>
|
||||
* This is a basic template but could be improved if we find the need.
|
||||
*/
|
||||
public abstract class NativeLibrary {
|
||||
private static final String TAG = NativeLibrary.class.getName();
|
||||
|
||||
private final Object mLock;
|
||||
private List<String> mLibraryNames;
|
||||
private Boolean mLoadLibraries;
|
||||
private boolean mLibrariesLoaded;
|
||||
private volatile UnsatisfiedLinkError mLinkError;
|
||||
|
||||
protected NativeLibrary(List<String> libraryNames) {
|
||||
mLock = new Object();
|
||||
mLoadLibraries = true;
|
||||
mLibrariesLoaded = false;
|
||||
mLinkError = null;
|
||||
mLibraryNames = libraryNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* safe loading of native libs
|
||||
* @return true if native libs loaded properly, false otherwise
|
||||
*/
|
||||
public boolean loadLibraries() {
|
||||
synchronized (mLock) {
|
||||
if (mLoadLibraries == false) {
|
||||
return mLibrariesLoaded;
|
||||
}
|
||||
try {
|
||||
for (String name: mLibraryNames) {
|
||||
SoLoader.loadLibrary(name);
|
||||
}
|
||||
initialNativeCheck();
|
||||
mLibrariesLoaded = true;
|
||||
mLibraryNames = null;
|
||||
} catch (UnsatisfiedLinkError error) {
|
||||
Log.e(TAG, "Failed to load native lib: ", error);
|
||||
mLinkError = error;
|
||||
mLibrariesLoaded = false;
|
||||
}
|
||||
mLoadLibraries = false;
|
||||
return mLibrariesLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads libraries (if not loaded yet), throws on failure
|
||||
* @throws UnsatisfiedLinkError
|
||||
*/
|
||||
|
||||
public void ensureLoaded() throws UnsatisfiedLinkError {
|
||||
if (!loadLibraries()) {
|
||||
throw mLinkError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to make some concrete (quick and harmless) native call.
|
||||
* This avoids lazy-loading some phones (LG) use when we call loadLibrary. If there's a problem
|
||||
* we'll face an UnsupportedLinkError when first using the feature instead of here.
|
||||
* This check force a check right when intended.
|
||||
* This way clients of this library can know if it's loaded for sure or not.
|
||||
* @throws UnsatisfiedLinkError if there was an error loading native library
|
||||
*/
|
||||
protected void initialNativeCheck() throws UnsatisfiedLinkError {
|
||||
}
|
||||
|
||||
public UnsatisfiedLinkError getError() {
|
||||
return mLinkError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.File;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that does nothing and pretends to successfully load all libraries.
|
||||
*/
|
||||
public class NoopSoSource extends SoSource {
|
||||
@Override
|
||||
public int loadLibrary(String soName, int loadFlags) {
|
||||
return LOAD_RESULT_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File unpackLibrary(String soName) {
|
||||
throw new UnsupportedOperationException(
|
||||
"unpacking not supported in test mode");
|
||||
}
|
||||
}
|
||||
237
ReactAndroid/src/main/java/com/facebook/soloader/SoLoader.java
Normal file
237
ReactAndroid/src/main/java/com/facebook/soloader/SoLoader.java
Normal file
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
|
||||
abstract public class SoSource {
|
||||
|
||||
/**
|
||||
* This SoSource doesn't know how to provide the given library.
|
||||
*/
|
||||
public static final int LOAD_RESULT_NOT_FOUND = 0;
|
||||
|
||||
/**
|
||||
* This SoSource loaded the given library.
|
||||
*/
|
||||
public static final int LOAD_RESULT_LOADED = 1;
|
||||
|
||||
/**
|
||||
* This SoSource did not load the library, but verified that the system loader will load it if
|
||||
* some other library depends on it. Returned only if LOAD_FLAG_ALLOW_IMPLICIT_PROVISION is
|
||||
* provided to loadLibrary.
|
||||
*/
|
||||
public static final int LOAD_RESULT_IMPLICITLY_PROVIDED = 2;
|
||||
|
||||
/**
|
||||
* Allow loadLibrary to implicitly provide the library instead of actually loading it.
|
||||
*/
|
||||
public static final int LOAD_FLAG_ALLOW_IMPLICIT_PROVISION = 1;
|
||||
|
||||
/**
|
||||
* Load a shared library library into this process. This routine is independent of
|
||||
* {@link #loadLibrary}.
|
||||
*
|
||||
* @param soName Name of library to load
|
||||
* @param loadFlags Zero or more of the LOAD_FLAG_XXX constants.
|
||||
* @return One of the LOAD_RESULT_XXX constants.
|
||||
*/
|
||||
abstract public int loadLibrary(String soName, int LoadFlags) throws IOException;
|
||||
|
||||
/**
|
||||
* Ensure that a shared library exists on disk somewhere. This routine is independent of
|
||||
* {@link #loadLibrary}.
|
||||
*
|
||||
* @param soName Name of library to load
|
||||
* @return File if library found; {@code null} if not.
|
||||
*/
|
||||
abstract public File unpackLibrary(String soName) throws IOException;
|
||||
}
|
||||
205
ReactAndroid/src/main/java/com/facebook/soloader/SysUtil.java
Normal file
205
ReactAndroid/src/main/java/com/facebook/soloader/SysUtil.java
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
/*package*/ final class SysUtil {
|
||||
|
||||
private static byte[] cachedBuffer = null;
|
||||
|
||||
/**
|
||||
* Copy from an inputstream to a named filesystem file. Take care to ensure that we can detect
|
||||
* incomplete copies and that the copied bytes make it to stable storage before returning.
|
||||
* The destination file will be marked executable.
|
||||
*
|
||||
* This routine caches an internal buffer between invocations; after making a sequence of calls
|
||||
* {@link #reliablyCopyExecutable} calls, call {@link #freeCopyBuffer} to release this buffer.
|
||||
*
|
||||
* @param is Stream from which to copy
|
||||
* @param destination File to which to write
|
||||
* @param expectedSize Number of bytes we expect to write; -1 if unknown
|
||||
* @param time Modification time to which to set file on success; must be in the past
|
||||
*/
|
||||
public static void reliablyCopyExecutable(
|
||||
InputStream is,
|
||||
File destination,
|
||||
long expectedSize,
|
||||
long time) throws IOException {
|
||||
destination.delete();
|
||||
try (FileOutputStream os = new FileOutputStream(destination)) {
|
||||
byte buffer[];
|
||||
if (cachedBuffer == null) {
|
||||
cachedBuffer = buffer = new byte[16384];
|
||||
} else {
|
||||
buffer = cachedBuffer;
|
||||
}
|
||||
|
||||
int nrBytes;
|
||||
if (expectedSize > 0) {
|
||||
fallocateIfSupported(os.getFD(), expectedSize);
|
||||
}
|
||||
|
||||
while ((nrBytes = is.read(buffer, 0, buffer.length)) >= 0) {
|
||||
os.write(buffer, 0, nrBytes);
|
||||
}
|
||||
|
||||
os.getFD().sync();
|
||||
destination.setExecutable(true);
|
||||
destination.setLastModified(time);
|
||||
os.getFD().sync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the internal buffer cache for {@link #reliablyCopyExecutable}.
|
||||
*/
|
||||
public static void freeCopyBuffer() {
|
||||
cachedBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how preferred a given ABI is on this system.
|
||||
*
|
||||
* @param supportedAbis ABIs on this system
|
||||
* @param abi ABI of a shared library we might want to unpack
|
||||
* @return -1 if not supported or an integer, smaller being more preferred
|
||||
*/
|
||||
public static int findAbiScore(String[] supportedAbis, String abi) {
|
||||
for (int i = 0; i < supportedAbis.length; ++i) {
|
||||
if (supportedAbis[i] != null && abi.equals(supportedAbis[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void deleteOrThrow(File file) throws IOException {
|
||||
if (!file.delete()) {
|
||||
throw new IOException("could not delete file " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an list of ABIs we supported on this device ordered according to preference. Use a
|
||||
* separate inner class to isolate the version-dependent call where it won't cause the whole
|
||||
* class to fail preverification.
|
||||
*
|
||||
* @return Ordered array of supported ABIs
|
||||
*/
|
||||
public static String[] getSupportedAbis() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new String[]{Build.CPU_ABI, Build.CPU_ABI2};
|
||||
} else {
|
||||
return LollipopSysdeps.getSupportedAbis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-allocate disk space for a file if we can do that
|
||||
* on this version of the OS.
|
||||
*
|
||||
* @param fd File descriptor for file
|
||||
* @param length Number of bytes to allocate.
|
||||
*/
|
||||
public static void fallocateIfSupported(FileDescriptor fd, long length) throws IOException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
LollipopSysdeps.fallocate(fd, length);
|
||||
}
|
||||
}
|
||||
|
||||
public static FileLocker lockLibsDirectory(Context context) throws IOException {
|
||||
File lockFile = new File(context.getApplicationInfo().dataDir, "libs-dir-lock");
|
||||
return FileLocker.lock(lockFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory into which we put our self-extracted native libraries.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return File pointing to an existing directory
|
||||
*/
|
||||
/* package */ static File getLibsDirectory(Context context) {
|
||||
return new File(context.getApplicationInfo().dataDir, "app_libs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory into which we put our self-extracted native libraries and make sure it
|
||||
* exists.
|
||||
*/
|
||||
/* package */ static File createLibsDirectory(Context context) {
|
||||
File libsDirectory = getLibsDirectory(context);
|
||||
if (!libsDirectory.isDirectory() && !libsDirectory.mkdirs()) {
|
||||
throw new RuntimeException("could not create libs directory");
|
||||
}
|
||||
|
||||
return libsDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a directory and its contents.
|
||||
*
|
||||
* WARNING: Java APIs do not let us distinguish directories from symbolic links to directories.
|
||||
* Consequently, if the directory contains symbolic links to directories, we will attempt to
|
||||
* delete the contents of pointed-to directories.
|
||||
*
|
||||
* @param file File or directory to delete
|
||||
*/
|
||||
/* package */ static void dumbDeleteRecrusive(File file) throws IOException {
|
||||
if (file.isDirectory()) {
|
||||
for (File entry : file.listFiles()) {
|
||||
dumbDeleteRecrusive(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.delete() && file.exists()) {
|
||||
throw new IOException("could not delete: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulate Lollipop-specific calls into an independent class so we don't fail preverification
|
||||
* downlevel.
|
||||
*/
|
||||
private static final class LollipopSysdeps {
|
||||
public static String[] getSupportedAbis() {
|
||||
return Build.SUPPORTED_32_BIT_ABIS; // We ain't doing no newfangled 64-bit
|
||||
}
|
||||
|
||||
public static void fallocate(FileDescriptor fd, long length) throws IOException {
|
||||
try {
|
||||
Os.posix_fallocate(fd, 0, length);
|
||||
} catch (ErrnoException ex) {
|
||||
throw new IOException(ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# This script generates Java structures that contain the offsets of
|
||||
# fields in various ELF ABI structures. com.facebook.soloader.MinElf
|
||||
# uses these structures while parsing ELF files.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
struct2java() {
|
||||
../../../../scripts/struct2java.py "$@"
|
||||
}
|
||||
|
||||
declare -a structs=(Elf32_Ehdr Elf64_Ehdr)
|
||||
structs+=(Elf32_Ehdr Elf64_Ehdr)
|
||||
structs+=(Elf32_Phdr Elf64_Phdr)
|
||||
structs+=(Elf32_Shdr Elf64_Shdr)
|
||||
structs+=(Elf32_Dyn Elf64_Dyn)
|
||||
|
||||
for struct in "${structs[@]}"; do
|
||||
cat > elfhdr.c <<EOF
|
||||
#include <elf.h>
|
||||
static const $struct a;
|
||||
EOF
|
||||
gcc -g -c -o elfhdr.o elfhdr.c
|
||||
cat > $struct.java <<EOF
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
EOF
|
||||
struct2java elfhdr.o $struct >> $struct.java
|
||||
done
|
||||
|
||||
rm -f elfhdr.o elfhdr.c
|
||||
@@ -0,0 +1,6 @@
|
||||
# Ensure that methods from LollipopSysdeps don't get inlined. LollipopSysdeps.fallocate references
|
||||
# an exception that isn't present prior to Lollipop, which trips up the verifier if the class is
|
||||
# loaded on a pre-Lollipop OS.
|
||||
-keep class com.facebook.soloader.SysUtil$LollipopSysdeps {
|
||||
public <methods>;
|
||||
}
|
||||
Reference in New Issue
Block a user