Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1378aba7 authored by Ramin Zaghi's avatar Ramin Zaghi Committed by Narayan Kamath
Browse files

Re-implement native library search and copies.

We now use a two step approach :

- First we look through the list of shared libraries in an
  APK, and choose an ABI based on the (priority)  list of ABIs
  a given device supports.
- Then we look through the list of shared libraries and copy
  all shared libraries that match the ABI we've selected.

This fixes a long-standing bug where we would sometimes copy
a mixture of different ABIs to the device, and also allows us
to clearly pick an ABI to run an app with.

The code in NativeLibraryHelper has been refactored so that all
file name validation & matching logic is done in a single place
(NativeLibrariesIterator). This allows us to avoid a lot of
redundant logic and straightens out a few corner cases (for eg.
where the abi determination & copying logic do not agree on
what files to skip).

bug: https://code.google.com/p/android/issues/detail?id=65053


bug: 13647418

Change-Id: I34d08353f24115b0f6b800a7eda3ac427fa25fef
Co-Authored-By: default avatarZhenghua Wang <zhenghua.wang0923@gmail.com>
Co-Authored-By: default avatarRamin Zaghi <ramin.zaghi@arm.com>
Co-Authored-By: default avatarNarayan Kamath <narayan@google.com>
parent a6f5e79d
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -674,6 +674,25 @@ public abstract class PackageManager {
     */
    public static final int INSTALL_FAILED_USER_RESTRICTED = -111;

    /**
     * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
     * if the system failed to install the package because its packaged native code did not
     * match any of the ABIs supported by the system.
     *
     * @hide
     */
    public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -112;

    /**
     * Internal return code for NativeLibraryHelper methods to indicate that the package
     * being processed did not contain any native code. This is placed here only so that
     * it can belong to the same value space as the other install failure codes.
     *
     * @hide
     */
    public static final int NO_NATIVE_LIBRARIES = -113;

    /**
     * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
     * package's data directory.
+8 −1
Original line number Diff line number Diff line
@@ -75,6 +75,13 @@ public class Build {
    /** A hardware serial number, if available.  Alphanumeric only, case-insensitive. */ 
    public static final String SERIAL = getString("ro.serialno");

    /**
     * A list of ABIs (in priority) order supported by this device.
     *
     * @hide
     */
    public static final String[] SUPPORTED_ABIS = getString("ro.product.cpu.abilist").split(",");

    /** Various version strings. */
    public static class VERSION {
        /**
+54 −16
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

package com.android.internal.content;

import android.os.Build;
import android.content.pm.PackageManager;
import android.util.Slog;

import java.io.File;
@@ -31,38 +31,76 @@ public class NativeLibraryHelper {

    private static final boolean DEBUG_NATIVE = false;

    private static native long nativeSumNativeBinaries(String file, String cpuAbi, String cpuAbi2);
    /**
     * A handle to an opened APK. Used as input to the various NativeLibraryHelper
     * methods. Allows us to scan and parse the APK exactly once instead of doing
     * it multiple times.
     *
     * @hide
     */
    public static class ApkHandle {
        final String apkPath;
        final long apkHandle;

        public ApkHandle(String path) {
            apkPath = path;
            apkHandle = nativeOpenApk(apkPath);
        }

        public ApkHandle(File apkFile) {
            apkPath = apkFile.getPath();
            apkHandle = nativeOpenApk(apkPath);
        }

        public void close() {
            nativeClose(apkHandle);
        }
    }


    private static native long nativeOpenApk(String path);
    private static native void nativeClose(long handle);

    private static native long nativeSumNativeBinaries(long handle, String cpuAbi);

    /**
     * Sums the size of native binaries in an APK.
     * Sums the size of native binaries in an APK for a given ABI.
     *
     * @param apkFile APK file to scan for native libraries
     * @return size of all native binary files in bytes
     */
    public static long sumNativeBinariesLI(File apkFile) {
        final String cpuAbi = Build.CPU_ABI;
        final String cpuAbi2 = Build.CPU_ABI2;
        return nativeSumNativeBinaries(apkFile.getPath(), cpuAbi, cpuAbi2);
    public static long sumNativeBinariesLI(ApkHandle handle, String abi) {
        return nativeSumNativeBinaries(handle.apkHandle, abi);
    }

    private native static int nativeCopyNativeBinaries(String filePath, String sharedLibraryPath,
            String cpuAbi, String cpuAbi2);
    private native static int nativeCopyNativeBinaries(long handle,
            String sharedLibraryPath, String abiToCopy);

    /**
     * Copies native binaries to a shared library directory.
     *
     * @param apkFile APK file to scan for native libraries
     * @param handle APK file to scan for native libraries
     * @param sharedLibraryDir directory for libraries to be copied to
     * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another
     *         error code from that class if not
     */
    public static int copyNativeBinariesIfNeededLI(File apkFile, File sharedLibraryDir) {
        final String cpuAbi = Build.CPU_ABI;
        final String cpuAbi2 = Build.CPU_ABI2;
        return nativeCopyNativeBinaries(apkFile.getPath(), sharedLibraryDir.getPath(), cpuAbi,
                cpuAbi2);
    public static int copyNativeBinariesIfNeededLI(ApkHandle handle, File sharedLibraryDir,
            String abi) {
        return nativeCopyNativeBinaries(handle.apkHandle, sharedLibraryDir.getPath(), abi);
    }

    /**
     * Checks if a given APK contains native code for any of the provided
     * {@code supportedAbis}. Returns an index into {@code supportedAbis} if a matching
     * ABI is found, {@link PackageManager#NO_NATIVE_LIBRARIES} if the
     * APK doesn't contain any native code, and
     * {@link PackageManager#INSTALL_FAILED_NO_MATCHING_ABIS} if none of the ABIs match.
     */
    public static int findSupportedAbi(ApkHandle handle, String[] supportedAbis) {
        return nativeFindSupportedAbi(handle.apkHandle, supportedAbis);
    }

    private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);

    // Convenience method to call removeNativeBinariesFromDirLI(File)
    public static boolean removeNativeBinariesLI(String nativeLibraryPath) {
        return removeNativeBinariesFromDirLI(new File(nativeLibraryPath));
+209 −80
Original line number Diff line number Diff line
@@ -19,11 +19,12 @@

#include <android_runtime/AndroidRuntime.h>

#include <utils/Log.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
#include <ScopedUtfChars.h>
#include <UniquePtr.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
#include <utils/Log.h>
#include <utils/Vector.h>

#include <zlib.h>

@@ -54,17 +55,19 @@
namespace android {

// These match PackageManager.java install codes
typedef enum {
enum install_status_t {
    INSTALL_SUCCEEDED = 1,
    INSTALL_FAILED_INVALID_APK = -2,
    INSTALL_FAILED_INSUFFICIENT_STORAGE = -4,
    INSTALL_FAILED_CONTAINER_ERROR = -18,
    INSTALL_FAILED_INTERNAL_ERROR = -110,
} install_status_t;
    INSTALL_FAILED_NO_MATCHING_ABIS = -112,
    NO_NATIVE_LIBRARIES = -113
};

typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*);

// Equivalent to isFilenameSafe
// Equivalent to android.os.FileUtils.isFilenameSafe
static bool
isFilenameSafe(const char* filename)
{
@@ -268,32 +271,39 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr
    return INSTALL_SUCCEEDED;
}

static install_status_t
iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2,
        iterFunc callFunc, void* callArg) {
    ScopedUtfChars filePath(env, javaFilePath);
    ScopedUtfChars cpuAbi(env, javaCpuAbi);
    ScopedUtfChars cpuAbi2(env, javaCpuAbi2);

    UniquePtr<ZipFileRO> zipFile(ZipFileRO::open(filePath.c_str()));
    if (zipFile.get() == NULL) {
        ALOGI("Couldn't open APK %s\n", filePath.c_str());
        return INSTALL_FAILED_INVALID_APK;
/*
 * An iterator over all shared libraries in a zip file. An entry is
 * considered to be a shared library if all of the conditions below are
 * satisfied :
 *
 * - The entry is under the lib/ directory.
 * - The entry name ends with ".so" and the entry name starts with "lib",
 *   an exception is made for entries whose name is "gdbserver".
 * - The entry filename is "safe" (as determined by isFilenameSafe).
 *
 */
class NativeLibrariesIterator {
private:
    NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie)
        : mZipFile(zipFile), mCookie(cookie), mLastSlash(NULL) {
        fileName[0] = '\0';
    }

    char fileName[PATH_MAX];
    bool hasPrimaryAbi = false;

public:
    static NativeLibrariesIterator* create(ZipFileRO* zipFile) {
        void* cookie = NULL;
        if (!zipFile->startIteration(&cookie)) {
        ALOGI("Couldn't iterate over APK%s\n", filePath.c_str());
        return INSTALL_FAILED_INVALID_APK;
            return NULL;
        }

    ZipEntryRO entry = NULL;
    while ((entry = zipFile->nextEntry(cookie)) != NULL) {
        return new NativeLibrariesIterator(zipFile, cookie);
    }

    ZipEntryRO next() {
        ZipEntryRO next = NULL;
        while ((next = mZipFile->nextEntry(mCookie)) != NULL) {
            // Make sure this entry has a filename.
        if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
            if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
                continue;
            }

@@ -312,82 +322,201 @@ iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, js
            const char* lastSlash = strrchr(fileName, '/');
            ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName);

        // Check to make sure the CPU ABI of this file is one we support.
        const char* cpuAbiOffset = fileName + APK_LIB_LEN;
        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;

        ALOGV("Comparing ABIs %s and %s versus %s\n", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset);
        if (cpuAbi.size() == cpuAbiRegionSize
                && *(cpuAbiOffset + cpuAbi.size()) == '/'
                && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
            ALOGV("Using primary ABI %s\n", cpuAbi.c_str());
            hasPrimaryAbi = true;
        } else if (cpuAbi2.size() == cpuAbiRegionSize
                && *(cpuAbiOffset + cpuAbi2.size()) == '/'
                && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) {
            // Exception: If we find the gdbserver binary, return it.
            if (!strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) {
                break;
            }

            /*
             * If this library matches both the primary and secondary ABIs,
             * only use the primary ABI.
             */
            if (hasPrimaryAbi) {
                ALOGV("Already saw primary ABI, skipping secondary ABI %s\n", cpuAbi2.c_str());
            // Make sure the filename starts with lib and ends with ".so".
            if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
                || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) {
                continue;
            } else {
                ALOGV("Using secondary ABI %s\n", cpuAbi2.c_str());
            }
        } else {
            ALOGV("abi didn't match anything: %s (end at %zd)\n", cpuAbiOffset, cpuAbiRegionSize);

            // Make sure the filename is safe.
            if (!isFilenameSafe(lastSlash + 1)) {
                continue;
            }

        // If this is a .so file, check to see if we need to copy it.
        if ((!strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
                    && !strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)
                    && isFilenameSafe(lastSlash + 1))
                || !strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) {
            mLastSlash = lastSlash;
            break;
        }

        return next;
    }

    inline const char* currentEntry() const {
        return fileName;
    }

    inline const char* lastSlash() const {
        return mLastSlash;
    }

    virtual ~NativeLibrariesIterator() {
        mZipFile->endIteration(mCookie);
    }
private:

    char fileName[PATH_MAX];
    ZipFileRO* const mZipFile;
    void* mCookie;
    const char* mLastSlash;
};

static install_status_t
iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
                       iterFunc callFunc, void* callArg) {
    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

    UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
    if (it.get() == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

            install_status_t ret = callFunc(env, callArg, zipFile.get(), entry, lastSlash + 1);
    const ScopedUtfChars cpuAbi(env, javaCpuAbi);
    if (cpuAbi.c_str() == NULL) {
        // This would've thrown, so this return code isn't observable by
        // Java.
        return INSTALL_FAILED_INVALID_APK;
    }
    ZipEntryRO entry = NULL;
    while ((entry = it->next()) != NULL) {
        const char* fileName = it->currentEntry();
        const char* lastSlash = it->lastSlash();

        // Check to make sure the CPU ABI of this file is one we support.
        const char* cpuAbiOffset = fileName + APK_LIB_LEN;
        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;

        if (cpuAbi.size() == cpuAbiRegionSize && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
            install_status_t ret = callFunc(env, callArg, zipFile, entry, lastSlash + 1);

            if (ret != INSTALL_SUCCEEDED) {
                ALOGV("Failure for entry %s", lastSlash + 1);
                zipFile->endIteration(cookie);
                return ret;
            }
        }
    }

    zipFile->endIteration(cookie);

    return INSTALL_SUCCEEDED;
}


static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
    const int numAbis = env->GetArrayLength(supportedAbisArray);
    Vector<ScopedUtfChars*> supportedAbis;

    for (int i = 0; i < numAbis; ++i) {
        supportedAbis.add(new ScopedUtfChars(env,
            (jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
    }

    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

    UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
    if (it.get() == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

    ZipEntryRO entry = NULL;
    char fileName[PATH_MAX];
    int status = NO_NATIVE_LIBRARIES;
    while ((entry = it->next()) != NULL) {
        // We're currently in the lib/ directory of the APK, so it does have some native
        // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
        // libraries match.
        if (status == NO_NATIVE_LIBRARIES) {
            status = INSTALL_FAILED_NO_MATCHING_ABIS;
        }

        const char* fileName = it->currentEntry();
        const char* lastSlash = it->lastSlash();

        // Check to see if this CPU ABI matches what we are looking for.
        const char* abiOffset = fileName + APK_LIB_LEN;
        const size_t abiSize = lastSlash - abiOffset;
        for (int i = 0; i < numAbis; i++) {
            const ScopedUtfChars* abi = supportedAbis[i];
            if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
                // The entry that comes in first (i.e. with a lower index) has the higher priority.
                if (((i < status) && (status >= 0)) || (status < 0) ) {
                    status = i;
                }
            }
        }
    }

    for (int i = 0; i < numAbis; ++i) {
        delete supportedAbis[i];
    }

    return status;
}

static jint
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
        jstring javaFilePath, jstring javaNativeLibPath, jstring javaCpuAbi, jstring javaCpuAbi2)
        jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi)
{
    return (jint) iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2,
    return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi,
            copyFileIfChanged, &javaNativeLibPath);
}

static jlong
com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz,
        jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2)
        jlong apkHandle, jstring javaCpuAbi)
{
    size_t totalSize = 0;

    iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2, sumFiles, &totalSize);
    iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize);

    return totalSize;
}

static jint
com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,
        jlong apkHandle, jobjectArray javaCpuAbisToSearch)
{
    return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
}

static jlong
com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath)
{
    ScopedUtfChars filePath(env, apkPath);
    ZipFileRO* zipFile = ZipFileRO::open(filePath.c_str());

    return reinterpret_cast<jlong>(zipFile);
}

static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
{
    delete reinterpret_cast<ZipFileRO*>(apkHandle);
}

static JNINativeMethod gMethods[] = {
    {"nativeOpenApk",
            "(Ljava/lang/String;)J",
            (void *)com_android_internal_content_NativeLibraryHelper_openApk},
    {"nativeClose",
            "(J)V",
            (void *)com_android_internal_content_NativeLibraryHelper_close},
    {"nativeCopyNativeBinaries",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
            "(JLjava/lang/String;Ljava/lang/String;)I",
            (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
    {"nativeSumNativeBinaries",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J",
            "(JLjava/lang/String;)J",
            (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
    {"nativeFindSupportedAbi",
            "(J[Ljava/lang/String;)I",
            (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
};


+29 −6
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.content.pm.PackageParser;
import android.content.res.ObbInfo;
import android.content.res.ObbScanner;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Environment.UserEnvironment;
import android.os.FileUtils;
@@ -39,10 +40,8 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;

import com.android.internal.app.IMediaContainerService;
@@ -343,11 +342,13 @@ public class DefaultContainerService extends IntentService {
        // The .apk file
        String codePath = packageURI.getPath();
        File codeFile = new File(codePath);
        NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codePath);
        final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);

        // Calculate size of container needed to hold base APK.
        final int sizeMb;
        try {
            sizeMb = calculateContainerSize(codeFile, isForwardLocked);
            sizeMb = calculateContainerSize(handle, codeFile, abi, isForwardLocked);
        } catch (IOException e) {
            Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
            return null;
@@ -410,7 +411,14 @@ public class DefaultContainerService extends IntentService {

        final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
        if (sharedLibraryDir.mkdir()) {
            int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
            int ret = PackageManager.INSTALL_SUCCEEDED;
            if (abi >= 0) {
                ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
                        sharedLibraryDir, Build.SUPPORTED_ABIS[abi]);
            } else if (abi != PackageManager.NO_NATIVE_LIBRARIES) {
                ret = abi;
            }

            if (ret != PackageManager.INSTALL_SUCCEEDED) {
                Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
                PackageHelper.destroySdDir(newCid);
@@ -824,6 +832,17 @@ public class DefaultContainerService extends IntentService {
        return availSdMb > sizeMb;
    }

    private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
        NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(apkFile);
        final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);

        try {
            return calculateContainerSize(handle, apkFile, abi, forwardLocked);
        } finally {
            handle.close();
        }
    }

    /**
     * Calculate the container size for an APK. Takes into account the
     * 
@@ -831,7 +850,8 @@ public class DefaultContainerService extends IntentService {
     * @return size in megabytes (2^20 bytes)
     * @throws IOException when there is a problem reading the file
     */
    private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
    private int calculateContainerSize(NativeLibraryHelper.ApkHandle apkHandle,
            File apkFile, int abiIndex, boolean forwardLocked) throws IOException {
        // Calculate size of container needed to hold base APK.
        long sizeBytes = apkFile.length();
        if (sizeBytes == 0 && !apkFile.exists()) {
@@ -840,7 +860,10 @@ public class DefaultContainerService extends IntentService {

        // Check all the native files that need to be copied and add that to the
        // container size.
        sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);
        if (abiIndex >= 0) {
            sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkHandle,
                    Build.SUPPORTED_ABIS[abiIndex]);
        }

        if (forwardLocked) {
            sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
Loading