java/com/facebook/soloader/DirectorySoSource.java (169 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.soloader;
import android.os.StrictMode;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
/** {@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;
protected final int flags;
protected final List<String> denyList;
/**
* 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, flags, new String[0]);
}
/**
* This method is similar to {@link #DirectorySoSource(File, int)}, with the following
* differences:
*
* @param denyList the soname list that we won't try to load from this source
*/
public DirectorySoSource(File soDirectory, int flags, String[] denyList) {
this.soDirectory = soDirectory;
this.flags = flags;
this.denyList = Arrays.asList(denyList);
}
@Override
public int loadLibrary(String soName, int loadFlags, StrictMode.ThreadPolicy threadPolicy)
throws IOException {
return loadLibraryFrom(soName, loadFlags, soDirectory, threadPolicy);
}
// Abstracted this logic in another method so subclasses can take advantage of it.
protected int loadLibraryFrom(
String soName, int loadFlags, File libDir, StrictMode.ThreadPolicy threadPolicy)
throws IOException {
if (SoLoader.sSoFileLoader == null) {
throw new IllegalStateException("SoLoader.init() not yet called");
}
if (denyList.contains(soName)) {
Log.d(
SoLoader.TAG,
soName + " is on the denyList, skip loading from " + libDir.getCanonicalPath());
return LOAD_RESULT_NOT_FOUND;
}
File soFile = getSoFileByName(soName);
if (soFile == null) {
Log.d(SoLoader.TAG, soName + " not found on " + libDir.getCanonicalPath());
return LOAD_RESULT_NOT_FOUND;
} else {
Log.d(SoLoader.TAG, soName + " found on " + libDir.getCanonicalPath());
}
if ((loadFlags & LOAD_FLAG_ALLOW_IMPLICIT_PROVISION) != 0
&& (flags & ON_LD_LIBRARY_PATH) != 0) {
Log.d(SoLoader.TAG, soName + " loaded implicitly");
return LOAD_RESULT_IMPLICITLY_PROVIDED;
}
ElfByteChannel bc = null;
boolean shouldLoadDependencies = (flags & RESOLVE_DEPENDENCIES) != 0;
boolean shouldLoadFromFile = soFile.getName().equals(soName);
try {
if (shouldLoadDependencies || !shouldLoadFromFile) {
bc = getChannel(soFile);
}
if (shouldLoadDependencies) {
loadDependencies(soName, bc, loadFlags, threadPolicy);
} else {
Log.d(SoLoader.TAG, "Not resolving dependencies for " + soName);
}
try {
if (shouldLoadFromFile) {
SoLoader.sSoFileLoader.load(soFile.getAbsolutePath(), loadFlags);
} else {
// The shared object does not exist in the file system, only in memory
SoLoader.sSoFileLoader.loadBytes(soFile.getAbsolutePath(), bc, loadFlags);
}
} catch (UnsatisfiedLinkError e) {
if (e.getMessage().contains("bad ELF magic")) {
Log.d(SoLoader.TAG, "Corrupted lib file detected");
// Swallow exception. Higher layers will try again from a backup source
return LOAD_RESULT_CORRUPTED_LIB_FILE;
} else {
throw e;
}
}
} finally {
if (bc != null) {
bc.close();
}
}
return LOAD_RESULT_LOADED;
}
@Override
@Nullable
protected File getSoFileByName(String soName) throws IOException {
File soFile = new File(soDirectory, soName);
if (soFile.exists()) {
return soFile;
}
return null;
}
@Override
@Nullable
public String getLibraryPath(String soName) throws IOException {
File soFile = getSoFileByName(soName);
if (soFile == null) {
return null;
}
return soFile.getCanonicalPath();
}
@Nullable
@Override
public String[] getLibraryDependencies(String soName) throws IOException {
File soFile = getSoFileByName(soName);
if (soFile == null) {
return null;
}
try (ElfByteChannel bc = getChannel(soFile)) {
return getDependencies(soName, bc);
}
}
private void loadDependencies(
String soName, ElfByteChannel bc, int loadFlags, StrictMode.ThreadPolicy threadPolicy)
throws IOException {
String[] dependencies = getDependencies(soName, bc);
Log.d(SoLoader.TAG, "Loading " + soName + "'s dependencies: " + Arrays.toString(dependencies));
for (String dependency : dependencies) {
if (dependency.startsWith("/")) {
continue;
}
SoLoader.loadLibraryBySoName(
dependency, loadFlags | LOAD_FLAG_ALLOW_IMPLICIT_PROVISION, threadPolicy);
}
}
protected ElfByteChannel getChannel(File soFile) throws IOException {
return new ElfFileChannel(soFile);
}
protected String[] getDependencies(String soName, ElfByteChannel bc) throws IOException {
if (SoLoader.SYSTRACE_LIBRARY_LOADING) {
Api18TraceUtils.beginTraceSection("SoLoader.getElfDependencies[", soName, "]");
}
try {
return NativeDeps.getDependencies(soName, bc);
} finally {
if (SoLoader.SYSTRACE_LIBRARY_LOADING) {
Api18TraceUtils.endSection();
}
}
}
@Override
@Nullable
public File unpackLibrary(String soName) throws IOException {
return getSoFileByName(soName);
}
@Override
public void addToLdLibraryPath(Collection<String> paths) {
paths.add(soDirectory.getAbsolutePath());
}
@Override
public String toString() {
String path;
try {
path = String.valueOf(soDirectory.getCanonicalPath());
} catch (IOException e) {
path = soDirectory.getName();
}
return new StringBuilder()
.append(getClass().getName())
.append("[root = ")
.append(path)
.append(" flags = ")
.append(flags)
.append(']')
.toString();
}
}