/**
* 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.FileDescriptor;
/*package*/ final class SysUtil {
private static byte[] cachedBuffer = null;
/**
* Copy from an inputstream to a named filesystem file. Take care to ensure that we can detect
* incomplete copies and that the copied bytes make it to stable storage before returning.
* The destination file will be marked executable.
*
* This routine caches an internal buffer between invocations; after making a sequence of calls
* {@link #reliablyCopyExecutable} calls, call {@link #freeCopyBuffer} to release this buffer.
*
* @param is Stream from which to copy
* @param destination File to which to write
* @param expectedSize Number of bytes we expect to write; -1 if unknown
* @param time Modification time to which to set file on success; must be in the past
*/
public static void reliablyCopyExecutable(
InputStream is,
File destination,
long expectedSize,
long time) throws IOException {
destination.delete();
try (FileOutputStream os = new FileOutputStream(destination)) {
byte buffer[];
if (cachedBuffer == null) {
cachedBuffer = buffer = new byte[16384];
} else {
buffer = cachedBuffer;
}
int nrBytes;
if (expectedSize > 0) {
fallocateIfSupported(os.getFD(), expectedSize);
}
while ((nrBytes = is.read(buffer, 0, buffer.length)) >= 0) {
os.write(buffer, 0, nrBytes);
}
os.getFD().sync();
destination.setExecutable(true);
destination.setLastModified(time);
os.getFD().sync();
}
}
/**
* Free the internal buffer cache for {@link #reliablyCopyExecutable}.
*/
public static void freeCopyBuffer() {
cachedBuffer = null;
}
/**
* Determine how preferred a given ABI is on this system.
*
* @param supportedAbis ABIs on this system
* @param abi ABI of a shared library we might want to unpack
* @return -1 if not supported or an integer, smaller being more preferred
*/
public static int findAbiScore(String[] supportedAbis, String abi) {
for (int i = 0; i < supportedAbis.length; ++i) {
if (supportedAbis[i] != null && abi.equals(supportedAbis[i])) {
return i;
}
}
return -1;
}
public static void deleteOrThrow(File file) throws IOException {
if (!file.delete()) {
throw new IOException("could not delete file " + file);
}
}
/**
* Return an list of ABIs we supported on this device ordered according to preference. Use a
* separate inner class to isolate the version-dependent call where it won't cause the whole
* class to fail preverification.
*
* @return Ordered array of supported ABIs
*/
public static String[] getSupportedAbis() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return new String[]{Build.CPU_ABI, Build.CPU_ABI2};
} else {
return LollipopSysdeps.getSupportedAbis();
}
}
/**
* Pre-allocate disk space for a file if we can do that
* on this version of the OS.
*
* @param fd File descriptor for file
* @param length Number of bytes to allocate.
*/
public static void fallocateIfSupported(FileDescriptor fd, long length) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
LollipopSysdeps.fallocate(fd, length);
}
}
public static FileLocker lockLibsDirectory(Context context) throws IOException {
File lockFile = new File(context.getApplicationInfo().dataDir, "libs-dir-lock");
return FileLocker.lock(lockFile);
}
/**
* Return the directory into which we put our self-extracted native libraries.
*
* @param context Application context
* @return File pointing to an existing directory
*/
/* package */ static File getLibsDirectory(Context context) {
return new File(context.getApplicationInfo().dataDir, "app_libs");
}
/**
* Return the directory into which we put our self-extracted native libraries and make sure it
* exists.
*/
/* package */ static File createLibsDirectory(Context context) {
File libsDirectory = getLibsDirectory(context);
if (!libsDirectory.isDirectory() && !libsDirectory.mkdirs()) {
throw new RuntimeException("could not create libs directory");
}
return libsDirectory;
}
/**
* Delete a directory and its contents.
*
* WARNING: Java APIs do not let us distinguish directories from symbolic links to directories.
* Consequently, if the directory contains symbolic links to directories, we will attempt to
* delete the contents of pointed-to directories.
*
* @param file File or directory to delete
*/
/* package */ static void dumbDeleteRecrusive(File file) throws IOException {
if (file.isDirectory()) {
for (File entry : file.listFiles()) {
dumbDeleteRecrusive(entry);
}
}
if (!file.delete() && file.exists()) {
throw new IOException("could not delete: " + file);
}
}
/**
* Encapsulate Lollipop-specific calls into an independent class so we don't fail preverification
* downlevel.
*/
private static final class LollipopSysdeps {
public static String[] getSupportedAbis() {
return Build.SUPPORTED_32_BIT_ABIS; // We ain't doing no newfangled 64-bit
}
public static void fallocate(FileDescriptor fd, long length) throws IOException {
try {
Os.posix_fallocate(fd, 0, length);
} catch (ErrnoException ex) {
throw new IOException(ex.toString(), ex);
}
}
}
}