java/com/facebook/soloader/ApkSoSource.java (128 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 android.os.Parcel;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipEntry;
/** {@link SoSource} that extracts libraries from an APK to the filesystem. */
public class ApkSoSource extends ExtractFromZipSoSource {
private static final String TAG = "ApkSoSource";
/**
* If this flag is given, do not extract libraries that appear to be correctly extracted to the
* application libs directory.
*/
public static final int PREFER_ANDROID_LIBS_DIRECTORY = (1 << 0);
private static final byte APK_SO_SOURCE_SIGNATURE_VERSION = 2;
private static final byte LIBS_DIR_DONT_CARE = 0;
private static final byte LIBS_DIR_DOESNT_EXIST = 1;
private static final byte LIBS_DIR_SNAPSHOT = 2;
private final int mFlags;
public ApkSoSource(Context context, String name, int flags) {
this(context, new File(context.getApplicationInfo().sourceDir), name, flags);
}
public ApkSoSource(Context context, File apkPath, String name, int flags) {
super(
context,
name,
apkPath,
// The regular expression matches libraries that would ordinarily be unpacked
// during installation.
"^lib/([^/]+)/([^/]+\\.so)$");
mFlags = flags;
}
@Override
protected Unpacker makeUnpacker(byte state) throws IOException {
return new ApkUnpacker(this);
}
protected class ApkUnpacker extends ZipUnpacker {
private final File mLibDir;
private final int mFlags;
ApkUnpacker(ExtractFromZipSoSource soSource) throws IOException {
super(soSource);
mLibDir = new File(mContext.getApplicationInfo().nativeLibraryDir);
mFlags = ApkSoSource.this.mFlags;
}
@Override
protected boolean shouldExtract(ZipEntry ze, String soName) {
String msg = "";
boolean result = false;
String zipPath = ze.getName();
if (soName.equals(mCorruptedLib)) {
mCorruptedLib = null;
msg = String.format("allowing consideration of corrupted lib %s", soName);
result = true;
} else if ((mFlags & PREFER_ANDROID_LIBS_DIRECTORY) == 0) {
msg = "allowing consideration of " + zipPath + ": self-extraction preferred";
result = true;
} else {
boolean validPath = true;
File sysLibFile = new File(mLibDir, soName);
try {
if (!sysLibFile.getCanonicalPath().startsWith(mLibDir.getCanonicalPath())) {
validPath = false;
msg =
String.format(
"not allowing consideration of %s: %s not in lib dir", zipPath, soName);
result = false;
}
} catch (IOException e) {
validPath = false;
result = false;
msg =
String.format(
"not allowing consideration of %s: %s, IOException when constructing path: %s",
zipPath, soName, e.toString());
}
if (validPath) {
if (!sysLibFile.isFile()) {
msg =
String.format(
"allowing consideration of %s: %s not in system lib dir", zipPath, soName);
result = true;
} else {
long sysLibLength = sysLibFile.length();
long apkLibLength = ze.getSize();
if (sysLibLength != apkLibLength) {
msg =
String.format(
"allowing consideration of %s: sysdir file length is %s, but "
+ "the file is %s bytes long in the APK",
sysLibFile, sysLibLength, apkLibLength);
result = true;
} else {
msg = "not allowing consideration of " + zipPath + ": deferring to libdir";
result = false;
}
}
}
}
Log.d(TAG, msg);
return result;
}
}
@Override
protected byte[] getDepsBlock() throws IOException {
File apkFile = mZipFileName.getCanonicalFile();
Parcel parcel = Parcel.obtain();
try {
parcel.writeByte(APK_SO_SOURCE_SIGNATURE_VERSION);
parcel.writeString(apkFile.getPath());
parcel.writeLong(apkFile.lastModified());
parcel.writeInt(SysUtil.getAppVersionCode(mContext));
if ((mFlags & PREFER_ANDROID_LIBS_DIRECTORY) == 0) {
parcel.writeByte(LIBS_DIR_DONT_CARE);
return parcel.marshall();
}
String nativeLibraryDir = mContext.getApplicationInfo().nativeLibraryDir;
if (nativeLibraryDir == null) {
parcel.writeByte(LIBS_DIR_DOESNT_EXIST);
return parcel.marshall();
}
File canonicalFile = new File(nativeLibraryDir).getCanonicalFile();
if (!canonicalFile.exists()) {
parcel.writeByte(LIBS_DIR_DOESNT_EXIST);
return parcel.marshall();
}
parcel.writeByte(LIBS_DIR_SNAPSHOT);
parcel.writeString(canonicalFile.getPath());
parcel.writeLong(canonicalFile.lastModified());
return parcel.marshall();
} finally {
parcel.recycle();
}
}
}