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

Commit 66269ea6 authored by Kenny Root's avatar Kenny Root
Browse files

Move extract native libraries to JNI code

The built-in ZipFile class was quite a long time to find an unpack
libraries. Move everything to using the libutils ZipFileRO class that
goes quite a bit faster. Initial measurements are 6 times faster than
the Java code.

Also, read files off the disk and compare their CRC against the APK's
CRC to see if we need to write the new file to disk. This also cuts down
the bootup time by up to a second per APK that has native files.

Change-Id: Ic464a7969a17368fb6a6b81d026888c4136c7603
parent e432a000
Loading
Loading
Loading
Loading
+12 −273
Original line number Diff line number Diff line
package com.android.internal.content;

import android.content.pm.PackageManager;
import android.os.Build;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * Native libraries helper.
@@ -28,270 +15,22 @@ public class NativeLibraryHelper {

    private static final boolean DEBUG_NATIVE = false;

    /*
     * The following constants are returned by listPackageSharedLibsForAbiLI
     * to indicate if native shared libraries were found in the package.
     * Values are:
     *    PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES => native libraries found and installed
     *    PACKAGE_INSTALL_NATIVE_NO_LIBRARIES     => no native libraries in package
     *    PACKAGE_INSTALL_NATIVE_ABI_MISMATCH     => native libraries for another ABI found
     *                                        in package (and not installed)
     *
     */
    private static final int PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES = 0;
    private static final int PACKAGE_INSTALL_NATIVE_NO_LIBRARIES = 1;
    private static final int PACKAGE_INSTALL_NATIVE_ABI_MISMATCH = 2;

    // Directory in the APK that holds all the native shared libraries.
    private static final String APK_LIB = "lib/";
    private static final int APK_LIB_LENGTH = APK_LIB.length();

    // Prefix that native shared libraries must have.
    private static final String LIB_PREFIX = "lib";
    private static final int LIB_PREFIX_LENGTH = LIB_PREFIX.length();

    // Suffix that the native shared libraries must have.
    private static final String LIB_SUFFIX = ".so";
    private static final int LIB_SUFFIX_LENGTH = LIB_SUFFIX.length();

    // Name of the GDB binary.
    private static final String GDBSERVER = "gdbserver";

    // the minimum length of a valid native shared library of the form
    // lib/<something>/lib<name>.so.
    private static final int MIN_ENTRY_LENGTH = APK_LIB_LENGTH + 2 + LIB_PREFIX_LENGTH + 1
            + LIB_SUFFIX_LENGTH;

    /*
     * Find all files of the form lib/<cpuAbi>/lib<name>.so in the .apk
     * and add them to a list to be installed later.
     *
     * NOTE: this method may throw an IOException if the library cannot
     * be copied to its final destination, e.g. if there isn't enough
     * room left on the data partition, or a ZipException if the package
     * file is malformed.
     */
    private static int listPackageSharedLibsForAbiLI(ZipFile zipFile,
            String cpuAbi, List<Pair<ZipEntry, String>> libEntries) throws IOException,
            ZipException {
        final int cpuAbiLen = cpuAbi.length();
        boolean hasNativeLibraries = false;
        boolean installedNativeLibraries = false;

        if (DEBUG_NATIVE) {
            Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type "
                    + cpuAbi);
        }

        Enumeration<? extends ZipEntry> entries = zipFile.entries();

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();

            // skip directories
            if (entry.isDirectory()) {
                continue;
            }
            String entryName = entry.getName();

            /*
             * Check that the entry looks like lib/<something>/lib<name>.so
             * here, but don't check the ABI just yet.
             *
             * - must be sufficiently long
             * - must end with LIB_SUFFIX, i.e. ".so"
             * - must start with APK_LIB, i.e. "lib/"
             */
            if (entryName.length() < MIN_ENTRY_LENGTH || !entryName.endsWith(LIB_SUFFIX)
                    || !entryName.startsWith(APK_LIB)) {
                continue;
            }

            // file name must start with LIB_PREFIX, i.e. "lib"
            int lastSlash = entryName.lastIndexOf('/');

            if (lastSlash < 0
                    || !entryName.regionMatches(lastSlash + 1, LIB_PREFIX, 0, LIB_PREFIX_LENGTH)) {
                continue;
            }

            hasNativeLibraries = true;

            // check the cpuAbi now, between lib/ and /lib<name>.so
            if (lastSlash != APK_LIB_LENGTH + cpuAbiLen
                    || !entryName.regionMatches(APK_LIB_LENGTH, cpuAbi, 0, cpuAbiLen))
                continue;

            /*
             * Extract the library file name, ensure it doesn't contain
             * weird characters. we're guaranteed here that it doesn't contain
             * a directory separator though.
             */
            String libFileName = entryName.substring(lastSlash+1);
            if (!FileUtils.isFilenameSafe(new File(libFileName))) {
                continue;
            }

            installedNativeLibraries = true;

            if (DEBUG_NATIVE) {
                Log.d(TAG, "Caching shared lib " + entry.getName());
            }

            libEntries.add(Pair.create(entry, libFileName));
        }
        if (!hasNativeLibraries)
            return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;

        if (!installedNativeLibraries)
            return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH;

        return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
    }

    /*
     * Find the gdbserver executable program in a package at
     * lib/<cpuAbi>/gdbserver and add it to the list of binaries
     * to be copied out later.
     *
     * Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
     * or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
     */
    private static int listPackageGdbServerLI(ZipFile zipFile, String cpuAbi,
            List<Pair<ZipEntry, String>> nativeFiles) throws IOException, ZipException {
        final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;

        Enumeration<? extends ZipEntry> entries = zipFile.entries();

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            // skip directories
            if (entry.isDirectory()) {
                continue;
            }
            String entryName = entry.getName();

            if (!entryName.equals(apkGdbServerPath)) {
                continue;
            }

            if (false) {
                Log.d(TAG, "Found gdbserver: " + entry.getName());
            }

            final String installGdbServerPath = GDBSERVER;
            nativeFiles.add(Pair.create(entry, installGdbServerPath));

            return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
        }
        return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
    }

    /*
     * Examine shared libraries stored in the APK as
     * lib/<cpuAbi>/lib<name>.so and add them to a list to be copied
     * later.
     *
     * This function will first try the main CPU ABI defined by Build.CPU_ABI
     * (which corresponds to ro.product.cpu.abi), and also try an alternate
     * one if ro.product.cpu.abi2 is defined.
     */
    public static int listPackageNativeBinariesLI(ZipFile zipFile,
            List<Pair<ZipEntry, String>> nativeFiles) throws ZipException, IOException {
        String cpuAbi = Build.CPU_ABI;

        int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles);

        /*
         * Some architectures are capable of supporting several CPU ABIs
         * for example, 'armeabi-v7a' also supports 'armeabi' native code
         * this is indicated by the definition of the ro.product.cpu.abi2
         * system property.
         *
         * only scan the package twice in case of ABI mismatch
         */
        if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
            final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null);
            if (cpuAbi2 != null) {
                result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles);
            }

            if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
                Slog.w(TAG, "Native ABI mismatch from package file");
                return PackageManager.INSTALL_FAILED_INVALID_APK;
            }

            if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
                cpuAbi = cpuAbi2;
            }
        }

        /*
         * Debuggable packages may have gdbserver embedded, so add it to
         * the list to the list of items to be extracted (as lib/gdbserver)
         * into the application's native library directory later.
         */
        if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
            listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles);
        }
        return PackageManager.INSTALL_SUCCEEDED;
    }
    private static native long nativeSumNativeBinaries(String file, String cpuAbi, String cpuAbi2);

    public static int copyNativeBinariesLI(File scanFile, File sharedLibraryDir) {
        /*
         * Check all the native files that need to be copied and add
         * that to the container size.
         */
        ZipFile zipFile;
        try {
            zipFile = new ZipFile(scanFile);

            List<Pair<ZipEntry, String>> nativeFiles = new LinkedList<Pair<ZipEntry, String>>();

            NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);

            final int N = nativeFiles.size();

            for (int i = 0; i < N; i++) {
                final Pair<ZipEntry, String> entry = nativeFiles.get(i);

                File destFile = new File(sharedLibraryDir, entry.second);
                copyNativeBinaryLI(zipFile, entry.first, sharedLibraryDir, destFile);
            }
            zipFile.close();
        } catch (ZipException e) {
            Slog.w(TAG, "Failed to extract data from package file", e);
            return PackageManager.INSTALL_FAILED_INVALID_APK;
        } catch (IOException e) {
            Slog.w(TAG, "Failed to cache package shared libs", e);
            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
    public static long sumNativeBinariesLI(File apkFile) {
        final String cpuAbi = Build.CPU_ABI;
        final String cpuAbi2 = Build.CPU_ABI2;
        return nativeSumNativeBinaries(apkFile.getPath(), cpuAbi, cpuAbi2);
    }

        return PackageManager.INSTALL_SUCCEEDED;
    }
    private native static int nativeCopyNativeBinaries(String filePath, String sharedLibraryPath,
            String cpuAbi, String cpuAbi2);

    private static void copyNativeBinaryLI(ZipFile zipFile, ZipEntry entry,
            File binaryDir, File binaryFile) throws IOException {
        InputStream inputStream = zipFile.getInputStream(entry);
        try {
            File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
            String tempFilePath = tempFile.getPath();
            // XXX package manager can't change owner, so the executable files for
            // now need to be left as world readable and owned by the system.
            if (!FileUtils.copyToFile(inputStream, tempFile)
                    || !tempFile.setLastModified(entry.getTime())
                    || FileUtils.setPermissions(tempFilePath, FileUtils.S_IRUSR | FileUtils.S_IWUSR
                            | FileUtils.S_IRGRP | FileUtils.S_IXUSR | FileUtils.S_IXGRP
                            | FileUtils.S_IXOTH | FileUtils.S_IROTH, -1, -1) != 0
                    || !tempFile.renameTo(binaryFile)) {
                // Failed to properly write file.
                tempFile.delete();
                throw new IOException("Couldn't create cached binary " + binaryFile + " in "
                        + binaryDir);
            }
        } finally {
            inputStream.close();
        }
    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);
    }

    // Convenience method to call removeNativeBinariesFromDirLI(File)
+3 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ LOCAL_SRC_FILES:= \
	ActivityManager.cpp \
	AndroidRuntime.cpp \
	Time.cpp \
	com_android_internal_content_NativeLibraryHelper.cpp \
	com_google_android_gles_jni_EGLImpl.cpp \
	com_google_android_gles_jni_GLImpl.cpp.arm \
	android_app_NativeActivity.cpp \
@@ -178,6 +179,7 @@ LOCAL_C_INCLUDES += \
	external/icu4c/common \
	external/jpeg \
	external/harfbuzz/src \
	external/zlib \
	frameworks/opt/emoji \
	libcore/include

@@ -211,6 +213,7 @@ LOCAL_SHARED_LIBRARIES := \
	libnfc_ndef \
	libusbhost \
	libharfbuzz \
	libz \

ifeq ($(USE_OPENGL_RENDERER),true)
	LOCAL_SHARED_LIBRARIES += libhwui
+2 −0
Original line number Diff line number Diff line
@@ -175,6 +175,7 @@ extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);

static AndroidRuntime* gCurRuntime = NULL;

@@ -1199,6 +1200,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_content_res_Configuration),

    REG_JNI(register_android_animation_PropertyValuesHolder),
    REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
};

/*
+375 −0

File added.

Preview size limit exceeded, changes collapsed.

+15 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

namespace android {

@@ -173,6 +174,20 @@ public:
    static bool inflateBuffer(int fd, const void* inBuf,
        size_t uncompLen, size_t compLen);

    /*
     * Utility function to convert ZIP's time format to a timespec struct.
     */
    static inline void zipTimeToTimespec(long when, struct tm* timespec) {
        const long date = when >> 16;
        timespec->tm_year = ((date >> 9) & 0x7F) + 80; // Zip is years since 1980
        timespec->tm_mon = (date >> 5) & 0x0F;
        timespec->tm_mday = date & 0x1F;

        timespec->tm_hour = (when >> 11) & 0x1F;
        timespec->tm_min = (when >> 5) & 0x3F;
        timespec->tm_sec = (when & 0x1F) << 1;
    }

    /*
     * Some basic functions for raw data manipulation.  "LE" means
     * Little Endian.
Loading