ArtManagerService.java revision fcbb74a4296fd808e1058ecebd91fac56582e799
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.pm.dex;
18
19import android.Manifest;
20import android.annotation.UserIdInt;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageParser;
25import android.content.pm.dex.ArtManager;
26import android.content.pm.dex.ArtManager.ProfileType;
27import android.content.pm.dex.DexMetadataHelper;
28import android.os.Binder;
29import android.os.Build;
30import android.os.Handler;
31import android.os.ParcelFileDescriptor;
32import android.os.RemoteException;
33import android.content.pm.IPackageManager;
34import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
35import android.os.SystemProperties;
36import android.os.UserHandle;
37import android.system.Os;
38import android.util.ArrayMap;
39import android.util.Slog;
40
41import com.android.internal.annotations.GuardedBy;
42import com.android.internal.os.BackgroundThread;
43import com.android.internal.util.ArrayUtils;
44import com.android.internal.util.Preconditions;
45import com.android.server.pm.Installer;
46import com.android.server.pm.Installer.InstallerException;
47import java.io.File;
48import java.io.FileNotFoundException;
49import libcore.io.IoUtils;
50import libcore.util.NonNull;
51import libcore.util.Nullable;
52
53/**
54 * A system service that provides access to runtime and compiler artifacts.
55 *
56 * This service is not accessed by users directly, instead one uses an instance of
57 * {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
58 * <p/>
59 * {@code context().getPackageManager().getArtManager();}
60 * <p class="note">
61 * Note: Accessing runtime artifacts may require extra permissions. For example querying the
62 * runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
63 * which is a system-level permission that will not be granted to normal apps.
64 */
65public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
66    private static final String TAG = "ArtManagerService";
67
68    private static boolean DEBUG = false;
69    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
70
71    // Package name used to create the profile directory layout when
72    // taking a snapshot of the boot image profile.
73    private static final String BOOT_IMAGE_ANDROID_PACKAGE = "android";
74    // Profile name used for the boot image profile.
75    private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
76
77    private final IPackageManager mPackageManager;
78    private final Object mInstallLock;
79    @GuardedBy("mInstallLock")
80    private final Installer mInstaller;
81
82    private final Handler mHandler;
83
84    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
85        mPackageManager = pm;
86        mInstaller = installer;
87        mInstallLock = installLock;
88        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
89    }
90
91    @Override
92    public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
93            @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
94        // Sanity checks on the arguments.
95        Preconditions.checkNotNull(callback);
96
97        boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
98        if (!bootImageProfile) {
99            Preconditions.checkStringNotEmpty(codePath);
100            Preconditions.checkStringNotEmpty(packageName);
101        }
102
103        // Verify that the caller has the right permissions and that the runtime profiling is
104        // enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
105        if (!isRuntimeProfilingEnabled(profileType)) {
106            throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
107        }
108
109        if (DEBUG) {
110            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
111        }
112
113        if (bootImageProfile) {
114            snapshotBootImageProfile(callback);
115        } else {
116            snapshotAppProfile(packageName, codePath, callback);
117        }
118    }
119
120    private void snapshotAppProfile(String packageName, String codePath,
121            ISnapshotRuntimeProfileCallback callback) {
122        PackageInfo info = null;
123        try {
124            // Note that we use the default user 0 to retrieve the package info.
125            // This doesn't really matter because for user 0 we always get a package back (even if
126            // it's not installed for the user 0). It is ok because we only care about the code
127            // paths and not if the package is enabled or not for the user.
128
129            // TODO(calin): consider adding an API to PMS which can retrieve the
130            // PackageParser.Package.
131            info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
132        } catch (RemoteException ignored) {
133            // Should not happen.
134        }
135        if (info == null) {
136            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
137            return;
138        }
139
140        boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
141        String splitName = null;
142        String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
143        if (!pathFound && (splitCodePaths != null)) {
144            for (int i = splitCodePaths.length - 1; i >= 0; i--) {
145                if (splitCodePaths[i].equals(codePath)) {
146                    pathFound = true;
147                    splitName = info.applicationInfo.splitNames[i];
148                    break;
149                }
150            }
151        }
152        if (!pathFound) {
153            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
154            return;
155        }
156
157        // All good, create the profile snapshot.
158        int appId = UserHandle.getAppId(info.applicationInfo.uid);
159        if (appId < 0) {
160            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
161            Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
162            return;
163        }
164
165        createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
166                appId, callback);
167        // Destroy the snapshot, we no longer need it.
168        destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
169    }
170
171    private void createProfileSnapshot(String packageName, String profileName, String classpath,
172            int appId, ISnapshotRuntimeProfileCallback callback) {
173        // Ask the installer to snapshot the profile.
174        synchronized (mInstallLock) {
175            try {
176                if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
177                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
178                    return;
179                }
180            } catch (InstallerException e) {
181                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
182                return;
183            }
184        }
185
186        // Open the snapshot and invoke the callback.
187        File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
188
189        ParcelFileDescriptor fd = null;
190        try {
191            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
192            postSuccess(packageName, fd, callback);
193        } catch (FileNotFoundException e) {
194            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
195                    + snapshotProfile, e);
196            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
197        } finally {
198            IoUtils.closeQuietly(fd);
199        }
200    }
201
202    private void destroyProfileSnapshot(String packageName, String profileName) {
203        if (DEBUG) {
204            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
205        }
206
207        synchronized (mInstallLock) {
208            try {
209                mInstaller.destroyProfileSnapshot(packageName, profileName);
210            } catch (InstallerException e) {
211                Slog.e(TAG, "Failed to destroy profile snapshot for " +
212                    packageName + ":" + profileName, e);
213            }
214        }
215    }
216
217    @Override
218    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
219        // Verify that the caller has the right permissions.
220        checkReadRuntimeProfilePermission();
221
222        switch (profileType) {
223            case ArtManager.PROFILE_APPS :
224                return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
225            case ArtManager.PROFILE_BOOT_IMAGE:
226                return (Build.IS_USERDEBUG || Build.IS_ENG) &&
227                        SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
228                        SystemProperties.getBoolean("dalvik.vm.profilebootimage", false);
229            default:
230                throw new IllegalArgumentException("Invalid profile type:" + profileType);
231        }
232    }
233
234    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
235        // Combine the profiles for boot classpath and system server classpath.
236        // This avoids having yet another type of profiles and simplifies the processing.
237        String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
238                Os.getenv("SYSTEMSERVERCLASSPATH"));
239
240        // Create the snapshot.
241        createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
242                /*appId*/ -1, callback);
243        // Destroy the snapshot, we no longer need it.
244        destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
245    }
246
247    /**
248     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
249     * on the internal {@code mHandler}.
250     */
251    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
252            int errCode) {
253        if (DEBUG) {
254            Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
255                    errCode);
256        }
257        mHandler.post(() -> {
258            try {
259                callback.onError(errCode);
260            } catch (RemoteException e) {
261                Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
262            }
263        });
264    }
265
266    private void postSuccess(String packageName, ParcelFileDescriptor fd,
267            ISnapshotRuntimeProfileCallback callback) {
268        if (DEBUG) {
269            Slog.d(TAG, "Successfully snapshot profile for " + packageName);
270        }
271        mHandler.post(() -> {
272            try {
273                callback.onSuccess(fd);
274            } catch (RemoteException e) {
275                Slog.w(TAG,
276                        "Failed to call onSuccess after profile snapshot for " + packageName, e);
277            }
278        });
279    }
280
281    /**
282     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
283     * If not, it throws a {@link SecurityException}.
284     */
285    private void checkReadRuntimeProfilePermission() {
286        if (DEBUG_IGNORE_PERMISSIONS) {
287            return;
288        }
289        try {
290            int result = mPackageManager.checkUidPermission(
291                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
292            if (result != PackageManager.PERMISSION_GRANTED) {
293                throw new SecurityException("You need "
294                        + Manifest.permission.READ_RUNTIME_PROFILES
295                        + " permission to snapshot profiles.");
296            }
297        } catch (RemoteException e) {
298            // Should not happen.
299        }
300    }
301
302    /**
303     * Prepare the application profiles.
304     * For all code paths:
305     *   - create the current primary profile to save time at app startup time.
306     *   - copy the profiles from the associated dex metadata file to the reference profile.
307     */
308    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
309        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
310        if (user < 0) {
311            Slog.wtf(TAG, "Invalid user id: " + user);
312            return;
313        }
314        if (appId < 0) {
315            Slog.wtf(TAG, "Invalid app id: " + appId);
316            return;
317        }
318        try {
319            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
320            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
321                String codePath = codePathsProfileNames.keyAt(i);
322                String profileName = codePathsProfileNames.valueAt(i);
323                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
324                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
325                synchronized (mInstaller) {
326                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
327                            profileName, codePath, dexMetadataPath);
328                    if (!result) {
329                        Slog.e(TAG, "Failed to prepare profile for " +
330                                pkg.packageName + ":" + codePath);
331                    }
332                }
333            }
334        } catch (InstallerException e) {
335            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
336        }
337    }
338
339    /**
340     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
341     */
342    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
343        for (int i = 0; i < user.length; i++) {
344            prepareAppProfiles(pkg, user[i]);
345        }
346    }
347
348    /**
349     * Clear the profiles for the given package.
350     */
351    public void clearAppProfiles(PackageParser.Package pkg) {
352        try {
353            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
354            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
355                String profileName = packageProfileNames.valueAt(i);
356                mInstaller.clearAppProfiles(pkg.packageName, profileName);
357            }
358        } catch (InstallerException e) {
359            Slog.w(TAG, String.valueOf(e));
360        }
361    }
362
363    /**
364     * Dumps the profiles for the given package.
365     */
366    public void dumpProfiles(PackageParser.Package pkg) {
367        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
368        try {
369            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
370            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
371                String codePath = packageProfileNames.keyAt(i);
372                String profileName = packageProfileNames.valueAt(i);
373                synchronized (mInstallLock) {
374                    mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
375                }
376            }
377        } catch (InstallerException e) {
378            Slog.w(TAG, "Failed to dump profiles", e);
379        }
380    }
381
382    /**
383     * Build the profiles names for all the package code paths (excluding resource only paths).
384     * Return the map [code path -> profile name].
385     */
386    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
387        ArrayMap<String, String> result = new ArrayMap<>();
388        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
389            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
390        }
391        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
392            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
393                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
394                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
395                }
396            }
397        }
398        return result;
399    }
400}
401