blob: 81786890a436681ba220736316afb4f464738150 [file] [log] [blame]
/*
* Copyright (C) 2017 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.server.pm.dex;
import android.Manifest;
import android.annotation.UserIdInt;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.content.pm.IPackageManager;
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import java.io.File;
import java.io.FileNotFoundException;
/**
* A system service that provides access to runtime and compiler artifacts.
*
* This service is not accessed by users directly, instead one uses an instance of
* {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
* <p/>
* {@code context().getPackageManager().getArtManager();}
* <p class="note">
* Note: Accessing runtime artifacts may require extra permissions. For example querying the
* runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
* which is a system-level permission that will not be granted to normal apps.
*/
public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private static final String TAG = "ArtManagerService";
private static boolean DEBUG = false;
private static boolean DEBUG_IGNORE_PERMISSIONS = false;
private final IPackageManager mPackageManager;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Handler mHandler;
public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
mPackageManager = pm;
mInstaller = installer;
mInstallLock = installLock;
mHandler = new Handler(BackgroundThread.getHandler().getLooper());
}
@Override
public void snapshotRuntimeProfile(String packageName, String codePath,
ISnapshotRuntimeProfileCallback callback) {
// Sanity checks on the arguments.
Preconditions.checkStringNotEmpty(packageName);
Preconditions.checkStringNotEmpty(codePath);
Preconditions.checkNotNull(callback);
// Verify that the caller has the right permissions.
checkReadRuntimeProfilePermission();
if (DEBUG) {
Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
}
PackageInfo info = null;
try {
// Note that we use the default user 0 to retrieve the package info.
// This doesn't really matter because for user 0 we always get a package back (even if
// it's not installed for the user 0). It is ok because we only care about the code
// paths and not if the package is enabled or not for the user.
// TODO(calin): consider adding an API to PMS which can retrieve the
// PackageParser.Package.
info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
} catch (RemoteException ignored) {
// Should not happen.
}
if (info == null) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
return;
}
boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
if (!pathFound && (splitCodePaths != null)) {
for (String path : splitCodePaths) {
if (path.equals(codePath)) {
pathFound = true;
break;
}
}
}
if (!pathFound) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
return;
}
// All good, create the profile snapshot.
createProfileSnapshot(packageName, codePath, callback, info);
// Destroy the snapshot, we no longer need it.
destroyProfileSnapshot(packageName, codePath);
}
private void createProfileSnapshot(String packageName, String codePath,
ISnapshotRuntimeProfileCallback callback, PackageInfo info) {
// Ask the installer to snapshot the profile.
synchronized (mInstallLock) {
try {
if (!mInstaller.createProfileSnapshot(UserHandle.getAppId(info.applicationInfo.uid),
packageName, codePath)) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
} catch (InstallerException e) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
}
// Open the snapshot and invoke the callback.
File snapshotProfile = Environment.getProfileSnapshotPath(packageName, codePath);
ParcelFileDescriptor fd;
try {
fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
postSuccess(packageName, fd, callback);
} catch (FileNotFoundException e) {
Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":" + codePath, e);
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
}
}
private void destroyProfileSnapshot(String packageName, String codePath) {
if (DEBUG) {
Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + codePath);
}
synchronized (mInstallLock) {
try {
mInstaller.destroyProfileSnapshot(packageName, codePath);
} catch (InstallerException e) {
Slog.e(TAG, "Failed to destroy profile snapshot for " +
packageName + ":" + codePath, e);
}
}
}
@Override
public boolean isRuntimeProfilingEnabled() {
// Verify that the caller has the right permissions.
checkReadRuntimeProfilePermission();
return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
}
/**
* Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
* on the internal {@code mHandler}.
*/
private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
int errCode) {
if (DEBUG) {
Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
errCode);
}
mHandler.post(() -> {
try {
callback.onError(errCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
}
});
}
private void postSuccess(String packageName, ParcelFileDescriptor fd,
ISnapshotRuntimeProfileCallback callback) {
if (DEBUG) {
Slog.d(TAG, "Successfully snapshot profile for " + packageName);
}
mHandler.post(() -> {
try {
callback.onSuccess(fd);
} catch (RemoteException e) {
Slog.w(TAG,
"Failed to call onSuccess after profile snapshot for " + packageName, e);
}
});
}
/**
* Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
* If not, it throws a {@link SecurityException}.
*/
private void checkReadRuntimeProfilePermission() {
if (DEBUG_IGNORE_PERMISSIONS) {
return;
}
try {
int result = mPackageManager.checkUidPermission(
Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
if (result != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("You need "
+ Manifest.permission.READ_RUNTIME_PROFILES
+ " permission to snapshot profiles.");
}
} catch (RemoteException e) {
// Should not happen.
}
}
/**
* Prepare the application profiles.
* For all code paths:
* - create the current primary profile to save time at app startup time.
* - copy the profiles from the associated dex metadata file to the reference profile.
*/
public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
if (user < 0) {
Slog.wtf(TAG, "Invalid user id: " + user);
return;
}
if (appId < 0) {
Slog.wtf(TAG, "Invalid app id: " + appId);
return;
}
try {
ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
String codePath = codePathsProfileNames.keyAt(i);
String profileName = codePathsProfileNames.valueAt(i);
File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
synchronized (mInstaller) {
boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
profileName, codePath, dexMetadataPath);
if (!result) {
Slog.e(TAG, "Failed to prepare profile for " +
pkg.packageName + ":" + codePath);
}
}
}
} catch (InstallerException e) {
Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
}
}
/**
* Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
*/
public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
for (int i = 0; i < user.length; i++) {
prepareAppProfiles(pkg, user[i]);
}
}
/**
* Build the profiles names for all the package code paths (excluding resource only paths).
* Return the map [code path -> profile name].
*/
private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
ArrayMap<String, String> result = new ArrayMap<>();
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
}
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
for (int i = 0; i < pkg.splitCodePaths.length; i++) {
if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
}
}
}
return result;
}
}