java/com/facebook/soloader/ExtractFromZipSoSource.java (140 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.content.Context;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
/** {@link SoSource} that extracts libraries from a zip file to the filesystem. */
public class ExtractFromZipSoSource extends UnpackingSoSource {
protected final File mZipFileName;
protected final String mZipSearchPattern;
/**
* @param context Application context
* @param name Name of the DSO store
* @param zipFileName Name of the zip file from which we extract; opened only on demand
* @param zipSearchPattern Regular expression string matching DSOs in the zip file; subgroup 1
* must be an ABI (as from Build.CPU_ABI) and subgroup 2 must be the shared library basename.
*/
public ExtractFromZipSoSource(
Context context, String name, File zipFileName, String zipSearchPattern) {
super(context, name);
mZipFileName = zipFileName;
mZipSearchPattern = zipSearchPattern;
}
@Override
protected Unpacker makeUnpacker(byte state) throws IOException {
return new ZipUnpacker(this);
}
protected class ZipUnpacker extends Unpacker {
private @Nullable ZipDso[] mDsos;
private final ZipFile mZipFile;
private final UnpackingSoSource mSoSource;
ZipUnpacker(final UnpackingSoSource soSource) throws IOException {
mZipFile = new ZipFile(mZipFileName);
mSoSource = soSource;
}
final ZipDso[] ensureDsos() {
if (mDsos == null) {
Set<String> librariesAbiSet = new LinkedHashSet<>();
HashMap<String, ZipDso> providedLibraries = new HashMap<>();
Pattern zipSearchPattern = Pattern.compile(mZipSearchPattern);
String[] supportedAbis = SysUtil.getSupportedAbis();
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
Matcher m = zipSearchPattern.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) {
librariesAbiSet.add(libraryAbi);
ZipDso so = providedLibraries.get(soName);
if (so == null || abiScore < so.abiScore) {
providedLibraries.put(soName, new ZipDso(soName, entry, abiScore));
}
}
}
}
mSoSource.setSoSourceAbis(librariesAbiSet.toArray(new String[librariesAbiSet.size()]));
ZipDso[] dsos = providedLibraries.values().toArray(new ZipDso[providedLibraries.size()]);
Arrays.sort(dsos);
int nrFilteredDsos = 0;
for (int i = 0; i < dsos.length; ++i) {
ZipDso zd = dsos[i];
if (shouldExtract(zd.backingEntry, zd.name)) {
nrFilteredDsos += 1;
} else {
dsos[i] = null;
}
}
ZipDso[] filteredDsos = new ZipDso[nrFilteredDsos];
for (int i = 0, j = 0; i < dsos.length; ++i) {
ZipDso zd = dsos[i];
if (zd != null) {
filteredDsos[j++] = zd;
}
}
mDsos = filteredDsos;
}
return mDsos;
}
/**
* Hook for subclasses to filter out certain library names from being extracted from the zip
* file.
*
* @param soName Candidate soName
* @param ze Zip entry for file to extract
*/
protected boolean shouldExtract(ZipEntry ze, String soName) {
return true;
}
@Override
public void close() throws IOException {
mZipFile.close();
}
@Override
public final DsoManifest getDsoManifest() throws IOException {
return new DsoManifest(ensureDsos());
}
@Override
public final InputDsoIterator openDsoIterator() throws IOException {
return new ZipBackedInputDsoIterator();
}
private final class ZipBackedInputDsoIterator extends InputDsoIterator {
private int mCurrentDso;
@Override
public boolean hasNext() {
ensureDsos();
return mCurrentDso < mDsos.length;
}
@Override
public InputDso next() throws IOException {
ensureDsos();
ZipDso zipDso = mDsos[mCurrentDso++];
InputStream is = mZipFile.getInputStream(zipDso.backingEntry);
try {
InputDso ret = new InputDsoStream(zipDso, is);
is = null; // Transfer ownership
return ret;
} finally {
if (is != null) {
is.close();
}
}
}
}
}
private static final class ZipDso extends Dso implements Comparable {
final ZipEntry backingEntry;
final int abiScore;
ZipDso(String name, ZipEntry backingEntry, int abiScore) {
super(name, makePseudoHash(backingEntry));
this.backingEntry = backingEntry;
this.abiScore = abiScore;
}
private static String makePseudoHash(ZipEntry ze) {
return String.format( // Yuck, but no real metadata
"pseudo-zip-hash-1-%s-%s-%s-%s",
ze.getName(), ze.getSize(), ze.getCompressedSize(), ze.getCrc());
}
@Override
public int compareTo(Object other) {
return name.compareTo(((ZipDso) other).name);
}
}
}