ArtManagerService.java revision 5bbe26ee01bc9785487fe5e748e624b6fc5bd3a4
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.DexMetadataHelper;
27import android.os.Binder;
28import android.os.Environment;
29import android.os.Handler;
30import android.os.ParcelFileDescriptor;
31import android.os.RemoteException;
32import android.content.pm.IPackageManager;
33import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
34import android.os.SystemProperties;
35import android.os.UserHandle;
36import android.util.ArrayMap;
37import android.util.Slog;
38
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.os.BackgroundThread;
41import com.android.internal.util.ArrayUtils;
42import com.android.internal.util.Preconditions;
43import com.android.server.pm.Installer;
44import com.android.server.pm.Installer.InstallerException;
45import java.io.File;
46import java.io.FileNotFoundException;
47
48/**
49 * A system service that provides access to runtime and compiler artifacts.
50 *
51 * This service is not accessed by users directly, instead one uses an instance of
52 * {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
53 * <p/>
54 * {@code context().getPackageManager().getArtManager();}
55 * <p class="note">
56 * Note: Accessing runtime artifacts may require extra permissions. For example querying the
57 * runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
58 * which is a system-level permission that will not be granted to normal apps.
59 */
60public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
61    private static final String TAG = "ArtManagerService";
62
63    private static boolean DEBUG = false;
64    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
65
66    private final IPackageManager mPackageManager;
67    private final Object mInstallLock;
68    @GuardedBy("mInstallLock")
69    private final Installer mInstaller;
70
71    private final Handler mHandler;
72
73    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
74        mPackageManager = pm;
75        mInstaller = installer;
76        mInstallLock = installLock;
77        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
78    }
79
80    @Override
81    public void snapshotRuntimeProfile(String packageName, String codePath,
82            ISnapshotRuntimeProfileCallback callback) {
83        // Sanity checks on the arguments.
84        Preconditions.checkStringNotEmpty(packageName);
85        Preconditions.checkStringNotEmpty(codePath);
86        Preconditions.checkNotNull(callback);
87
88        // Verify that the caller has the right permissions.
89        checkReadRuntimeProfilePermission();
90
91        if (DEBUG) {
92            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
93        }
94
95        PackageInfo info = null;
96        try {
97            // Note that we use the default user 0 to retrieve the package info.
98            // This doesn't really matter because for user 0 we always get a package back (even if
99            // it's not installed for the user 0). It is ok because we only care about the code
100            // paths and not if the package is enabled or not for the user.
101
102            // TODO(calin): consider adding an API to PMS which can retrieve the
103            // PackageParser.Package.
104            info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
105        } catch (RemoteException ignored) {
106            // Should not happen.
107        }
108        if (info == null) {
109            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
110            return;
111        }
112
113        boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
114        String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
115        if (!pathFound && (splitCodePaths != null)) {
116            for (String path : splitCodePaths) {
117                if (path.equals(codePath)) {
118                    pathFound = true;
119                    break;
120                }
121            }
122        }
123        if (!pathFound) {
124            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
125            return;
126        }
127
128        // All good, create the profile snapshot.
129        createProfileSnapshot(packageName, codePath, callback, info);
130        // Destroy the snapshot, we no longer need it.
131        destroyProfileSnapshot(packageName, codePath);
132    }
133
134    private void createProfileSnapshot(String packageName, String codePath,
135            ISnapshotRuntimeProfileCallback callback, PackageInfo info) {
136        // Ask the installer to snapshot the profile.
137        synchronized (mInstallLock) {
138            try {
139                if (!mInstaller.createProfileSnapshot(UserHandle.getAppId(info.applicationInfo.uid),
140                        packageName, codePath)) {
141                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
142                    return;
143                }
144            } catch (InstallerException e) {
145                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
146                return;
147            }
148        }
149
150        // Open the snapshot and invoke the callback.
151        File snapshotProfile = Environment.getProfileSnapshotPath(packageName, codePath);
152        ParcelFileDescriptor fd;
153        try {
154            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
155            postSuccess(packageName, fd, callback);
156        } catch (FileNotFoundException e) {
157            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":" + codePath, e);
158            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
159        }
160    }
161
162    private void destroyProfileSnapshot(String packageName, String codePath) {
163        if (DEBUG) {
164            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + codePath);
165        }
166
167        synchronized (mInstallLock) {
168            try {
169                mInstaller.destroyProfileSnapshot(packageName, codePath);
170            } catch (InstallerException e) {
171                Slog.e(TAG, "Failed to destroy profile snapshot for " +
172                    packageName + ":" + codePath, e);
173            }
174        }
175    }
176
177    @Override
178    public boolean isRuntimeProfilingEnabled() {
179        // Verify that the caller has the right permissions.
180        checkReadRuntimeProfilePermission();
181
182        return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
183    }
184
185    /**
186     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
187     * on the internal {@code mHandler}.
188     */
189    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
190            int errCode) {
191        if (DEBUG) {
192            Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
193                    errCode);
194        }
195        mHandler.post(() -> {
196            try {
197                callback.onError(errCode);
198            } catch (RemoteException e) {
199                Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
200            }
201        });
202    }
203
204    private void postSuccess(String packageName, ParcelFileDescriptor fd,
205            ISnapshotRuntimeProfileCallback callback) {
206        if (DEBUG) {
207            Slog.d(TAG, "Successfully snapshot profile for " + packageName);
208        }
209        mHandler.post(() -> {
210            try {
211                callback.onSuccess(fd);
212            } catch (RemoteException e) {
213                Slog.w(TAG,
214                        "Failed to call onSuccess after profile snapshot for " + packageName, e);
215            }
216        });
217    }
218
219    /**
220     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
221     * If not, it throws a {@link SecurityException}.
222     */
223    private void checkReadRuntimeProfilePermission() {
224        if (DEBUG_IGNORE_PERMISSIONS) {
225            return;
226        }
227        try {
228            int result = mPackageManager.checkUidPermission(
229                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
230            if (result != PackageManager.PERMISSION_GRANTED) {
231                throw new SecurityException("You need "
232                        + Manifest.permission.READ_RUNTIME_PROFILES
233                        + " permission to snapshot profiles.");
234            }
235        } catch (RemoteException e) {
236            // Should not happen.
237        }
238    }
239
240    /**
241     * Prepare the application profiles.
242     * For all code paths:
243     *   - create the current primary profile to save time at app startup time.
244     *   - copy the profiles from the associated dex metadata file to the reference profile.
245     */
246    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
247        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
248        if (user < 0) {
249            Slog.wtf(TAG, "Invalid user id: " + user);
250            return;
251        }
252        if (appId < 0) {
253            Slog.wtf(TAG, "Invalid app id: " + appId);
254            return;
255        }
256        try {
257            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
258            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
259                String codePath = codePathsProfileNames.keyAt(i);
260                String profileName = codePathsProfileNames.valueAt(i);
261                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
262                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
263                synchronized (mInstaller) {
264                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
265                            profileName, codePath, dexMetadataPath);
266                    if (!result) {
267                        Slog.e(TAG, "Failed to prepare profile for " +
268                                pkg.packageName + ":" + codePath);
269                    }
270                }
271            }
272        } catch (InstallerException e) {
273            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
274        }
275    }
276
277    /**
278     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
279     */
280    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
281        for (int i = 0; i < user.length; i++) {
282            prepareAppProfiles(pkg, user[i]);
283        }
284    }
285
286    /**
287     * Build the profiles names for all the package code paths (excluding resource only paths).
288     * Return the map [code path -> profile name].
289     */
290    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
291        ArrayMap<String, String> result = new ArrayMap<>();
292        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
293            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
294        }
295        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
296            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
297                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
298                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
299                }
300            }
301        }
302        return result;
303    }
304}
305