/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.soloader; import java.io.File; import java.io.IOException; import android.content.Context; import java.util.jar.JarFile; import java.util.jar.JarEntry; import java.util.regex.Pattern; import java.util.regex.Matcher; import android.os.Build; import android.system.Os; import android.system.ErrnoException; import java.util.HashMap; import java.util.Map; import java.util.Enumeration; import java.io.InputStream; import java.io.FileOutputStream; import java.io.FileInputStream; import java.io.BufferedReader; import java.io.FileReader; import android.util.Log; /** * {@link SoSource} that retrieves libraries from an exopackage repository. */ public class ExoSoSource extends DirectorySoSource { private static final String TAG = SoLoader.TAG; private static final boolean DEBUG = SoLoader.DEBUG; /** * @param context Application context */ public ExoSoSource(Context context) throws IOException { // // Initialize a normal DirectorySoSource that will load from our extraction directory. At this // point, the directory may be empty or contain obsolete libraries, but that's okay. // super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES); // // Synchronize the contents of that directory with the library payload in our APK, deleting and // extracting as needed. // File libsDir = super.soDirectory; if (DEBUG) { Log.v(TAG, "synchronizing log directory: " + libsDir); } Map<String, File> providedLibraries = findProvidedLibraries(context); try (FileLocker lock = SysUtil.lockLibsDirectory(context)) { // Delete files in libsDir that we don't provide or that are out of date. Forget about any // libraries that are up-to-date already so we don't unpack them below. File extantFiles[] = libsDir.listFiles(); for (int i = 0; i < extantFiles.length; ++i) { File extantFile = extantFiles[i]; if (DEBUG) { Log.v(TAG, "considering libdir file: " + extantFile); } String name = extantFile.getName(); File sourceFile = providedLibraries.get(name); boolean shouldDelete = (sourceFile == null || sourceFile.length() != extantFile.length() || sourceFile.lastModified() != extantFile.lastModified()); boolean upToDate = (sourceFile != null && !shouldDelete); if (shouldDelete) { if (DEBUG) { Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile); } SysUtil.deleteOrThrow(extantFile); } if (upToDate) { if (DEBUG) { Log.v(TAG, "found up-to-date library: " + extantFile); } providedLibraries.remove(name); } } // Now extract any libraries left in providedLibraries; we removed all the up-to-date ones. for (String soName : providedLibraries.keySet()) { File sourceFile = providedLibraries.get(soName); try (InputStream is = new FileInputStream(sourceFile)) { if (DEBUG) { Log.v(TAG, "extracting library: " + soName); } SysUtil.reliablyCopyExecutable( is, new File(libsDir, soName), sourceFile.length(), sourceFile.lastModified()); } SysUtil.freeCopyBuffer(); } } } /** * Find the shared libraries provided through the exopackage directory and supported on this * system. Each returend SoInfo points to the most preferred version of that library included in * our exopackage directory: for example, if we're on an armv7-a system and we have both arm and * armv7-a versions of libfoo, the returned entry for libfoo points to the armv7-a version of * libfoo. * * The caller owns the returned value and may mutate it. * * @param context Application context * @return Map of sonames to providing files */ private static Map<String, File> findProvidedLibraries(Context context) throws IOException { File exoDir = new File( "/data/local/tmp/exopackage/" + context.getPackageName() + "/native-libs/"); HashMap<String, File> providedLibraries = new HashMap<>(); for (String abi : SysUtil.getSupportedAbis()) { File abiDir = new File(exoDir, abi); if (!abiDir.isDirectory()) { continue; } File metadata = new File(abiDir, "metadata.txt"); if (!metadata.isFile()) { continue; } try (FileReader fr = new FileReader(metadata); BufferedReader br = new BufferedReader(fr)) { String line; while ((line = br.readLine()) != null) { if (line.length() == 0) { continue; } int sep = line.indexOf(' '); if (sep == -1) { throw new RuntimeException("illegal line in exopackage metadata: [" + line + "]"); } String soName = line.substring(0, sep) + ".so"; String backingFile = line.substring(sep + 1); if (!providedLibraries.containsKey(soName)) { providedLibraries.put(soName, new File(abiDir, backingFile)); } } } } return providedLibraries; } }