Files
frameworks_base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
Jeff Sharkey b18f899241 Use sendfile() and splice() to speed up copying.
There are several places across the OS where Java code is simply
copying data between two points, which requires bringing that data
out into userspace before going back into the kernel.  (That's pretty
lame.)  The patches for the recent Meltdown/Spectre security issues
have made this overhead even worse, so it's finally time to move this
copying directly into the kernel.

This change adds a couple new FileUtils.copy() methods which inspect
the given streams/FDs, and attempt to do as much optimization as
possible before falling back to a slower userspace-based copy.

Benchmarks are showing typical improvements of 44% for 32KB files,
50% for 32MB files, and 35% for 32MB pipes.

Plenty of tests are included, and there's a simple kill-switch that
can be used to enable/disable the feature if it starts causing any
trouble.  (A future CL will enable the optimizations.)

Test: bit FrameworksCoreTests:android.os.FileUtilsTest
Test: vogar --mode app_process --benchmark frameworks/base/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
Bug: 71932978
Change-Id: I52518d529da5d961610998b9f61399064d8025cd
2018-01-31 21:47:22 -07:00

270 lines
10 KiB
Java

/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.android.defcontainer;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.res.ObbInfo;
import android.content.res.ObbScanner;
import android.os.Binder;
import android.os.Environment.UserEnvironment;
import android.os.FileUtils;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.IParcelFileDescriptorFactory;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Service that offers to inspect and copy files that may reside on removable
* storage. This is designed to prevent the system process from holding onto
* open files that cause the kernel to kill it when the underlying device is
* removed.
*/
public class DefaultContainerService extends IntentService {
private static final String TAG = "DefContainer";
// TODO: migrate native code unpacking to always be a derivative work
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
/**
* Copy package to the target location.
*
* @param packagePath absolute path to the package to be copied. Can be
* a single monolithic APK file or a cluster directory
* containing one or more APKs.
* @return returns status code according to those in
* {@link PackageManager}
*/
@Override
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
if (packagePath == null || target == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
PackageLite pkg = null;
try {
final File packageFile = new File(packagePath);
pkg = PackageParser.parsePackageLite(packageFile, 0);
return copyPackageInner(pkg, target);
} catch (PackageParserException | IOException | RemoteException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}
/**
* Parse given package and return minimal details.
*
* @param packagePath absolute path to the package to be copied. Can be
* a single monolithic APK file or a cluster directory
* containing one or more APKs.
*/
@Override
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
String abiOverride) {
final Context context = DefaultContainerService.this;
PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg;
final long sizeBytes;
try {
pkg = PackageParser.parsePackageLite(packageFile, 0);
sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
if (!packageFile.exists()) {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
} else {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
final int recommendedInstallLocation;
final long token = Binder.clearCallingIdentity();
try {
recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
ret.packageName = pkg.packageName;
ret.splitNames = pkg.splitNames;
ret.versionCode = pkg.versionCode;
ret.versionCodeMajor = pkg.versionCodeMajor;
ret.baseRevisionCode = pkg.baseRevisionCode;
ret.splitRevisionCodes = pkg.splitRevisionCodes;
ret.installLocation = pkg.installLocation;
ret.verifiers = pkg.verifiers;
ret.recommendedInstallLocation = recommendedInstallLocation;
ret.multiArch = pkg.multiArch;
return ret;
}
@Override
public ObbInfo getObbInfo(String filename) {
try {
return ObbScanner.getObbInfo(filename);
} catch (IOException e) {
Slog.d(TAG, "Couldn't get OBB info for " + filename);
return null;
}
}
@Override
public void clearDirectory(String path) throws RemoteException {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
final File directory = new File(path);
if (directory.exists() && directory.isDirectory()) {
eraseFiles(directory);
}
}
/**
* Calculate estimated footprint of given package post-installation.
*
* @param packagePath absolute path to the package to be copied. Can be
* a single monolithic APK file or a cluster directory
* containing one or more APKs.
*/
@Override
public long calculateInstalledSize(String packagePath, String abiOverride)
throws RemoteException {
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg;
try {
pkg = PackageParser.parsePackageLite(packageFile, 0);
return PackageHelper.calculateInstalledSize(pkg, abiOverride);
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to calculate installed size: " + e);
return Long.MAX_VALUE;
}
}
};
public DefaultContainerService() {
super("DefaultContainerService");
setIntentRedelivery(true);
}
@Override
protected void onHandleIntent(Intent intent) {
if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
final IPackageManager pm = IPackageManager.Stub.asInterface(
ServiceManager.getService("package"));
PackageCleanItem item = null;
try {
while ((item = pm.nextPackageToClean(item)) != null) {
final UserEnvironment userEnv = new UserEnvironment(item.userId);
eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
if (item.andCode) {
eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
}
}
} catch (RemoteException e) {
}
}
}
void eraseFiles(File[] paths) {
for (File path : paths) {
eraseFiles(path);
}
}
void eraseFiles(File path) {
if (path.isDirectory()) {
String[] files = path.list();
if (files != null) {
for (String file : files) {
eraseFiles(new File(path, file));
}
}
}
path.delete();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
throws IOException, RemoteException {
copyFile(pkg.baseCodePath, target, "base.apk");
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
for (int i = 0; i < pkg.splitNames.length; i++) {
copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
}
}
return PackageManager.INSTALL_SUCCEEDED;
}
private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
throws IOException, RemoteException {
Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(sourcePath);
out = new ParcelFileDescriptor.AutoCloseOutputStream(
target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
FileUtils.copy(in, out);
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
}
}
}