blob: cec2028dddae2a91d0c84c83cbc80b5d0e9ae4d7 [file] [log] [blame]
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -07001/*
2 * Copyright (C) 2016 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;
18
Jorim Jaggi7119800f2018-07-09 17:57:10 +020019import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
20import static android.app.ActivityManager.UID_OBSERVER_GONE;
21
22import android.annotation.IntDef;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070023import android.annotation.Nullable;
24
Jorim Jaggi7119800f2018-07-09 17:57:10 +020025import android.app.ActivityManager;
26import android.app.ActivityManagerInternal;
27import android.app.IActivityManager;
28import android.app.IUidObserver;
Carmen Jacksonf107a232017-05-16 10:37:26 -070029import android.content.BroadcastReceiver;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070030import android.content.Context;
Jeff Sharkey847bd852016-08-03 17:20:03 -060031import android.content.Intent;
Carmen Jacksonf107a232017-05-16 10:37:26 -070032import android.content.IntentFilter;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070033import android.content.pm.ActivityInfo;
34import android.content.pm.ApplicationInfo;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070035import android.content.pm.PackageManager;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070036import android.content.pm.ResolveInfo;
Carmen Jacksonf107a232017-05-16 10:37:26 -070037import android.net.Uri;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070038import android.os.Binder;
39import android.os.Build;
Philip Cuadraa95cea02016-07-06 16:00:32 -070040import android.os.Handler;
41import android.os.Looper;
42import android.os.Message;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020043import android.os.RemoteException;
Jeff Sharkey847bd852016-08-03 17:20:03 -060044import android.os.UserHandle;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070045import android.provider.MediaStore;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070046import android.system.ErrnoException;
47import android.system.Os;
48import android.system.OsConstants;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020049import android.util.ArrayMap;
Calin Juravle31ce3a82017-05-22 17:49:01 -070050import android.util.ArraySet;
Jeff Sharkey847bd852016-08-03 17:20:03 -060051import android.util.Slog;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070052
Jorim Jaggi7119800f2018-07-09 17:57:10 +020053import com.android.internal.annotations.GuardedBy;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070054import com.android.internal.app.ResolverActivity;
Philip Cuadraa95cea02016-07-06 16:00:32 -070055import com.android.internal.os.BackgroundThread;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060056import com.android.internal.util.DumpUtils;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020057import com.android.internal.util.function.pooled.PooledLambda;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070058
Philip Cuadrad9bd8842016-07-12 17:29:38 -070059import dalvik.system.DexFile;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070060import dalvik.system.VMRuntime;
61
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070062import java.io.FileDescriptor;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070063import java.io.Closeable;
64import java.io.InputStream;
65import java.io.DataInputStream;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070066import java.io.IOException;
67import java.io.PrintWriter;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020068import java.lang.annotation.Retention;
69import java.lang.annotation.RetentionPolicy;
Jeff Sharkey847bd852016-08-03 17:20:03 -060070import java.util.ArrayList;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070071
Daniel Colascione9779b5e2018-03-21 19:13:57 -070072import java.util.zip.ZipFile;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070073import java.util.zip.ZipEntry;
74
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070075/**
76 * <p>PinnerService pins important files for key processes in memory.</p>
77 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070078 * overlay.</p>
79 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070080 */
81public final class PinnerService extends SystemService {
82 private static final boolean DEBUG = false;
83 private static final String TAG = "PinnerService";
Jorim Jaggi7119800f2018-07-09 17:57:10 +020084
Daniel Colascione9779b5e2018-03-21 19:13:57 -070085 private static final String PIN_META_FILENAME = "pinlist.meta";
86 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
Jorim Jaggi7119800f2018-07-09 17:57:10 +020087 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
88 | PackageManager.MATCH_DIRECT_BOOT_AWARE
89 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
90
91 private static final int KEY_CAMERA = 0;
92 private static final int KEY_HOME = 1;
93
94 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
95 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
96
97 @IntDef({KEY_CAMERA, KEY_HOME})
98 @Retention(RetentionPolicy.SOURCE)
99 public @interface AppKey {}
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700100
101 private final Context mContext;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200102 private final ActivityManagerInternal mAmInternal;
103 private final IActivityManager mAm;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700104
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200105 /** The list of the statically pinned files. */
106 @GuardedBy("this")
107 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
108
109 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
110 @GuardedBy("this")
111 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
112
113 /**
114 * The list of the pinned apps that need to be repinned as soon as the all processes of a given
115 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
116 * loaded into the processes once it restarts. So in case background dex opt recompiled these
117 * files, we still need to keep the old ones pinned until the processes restart.
118 * <p>
119 * This is a map from uid to {@link AppKey}
120 */
121 @GuardedBy("this")
122 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
123
124 /**
125 * A set of {@link AppKey} that are configured to be pinned.
126 */
127 private final ArraySet<Integer> mPinKeys = new ArraySet<>();
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700128
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700129 private BinderService mBinderService;
Philip Cuadraa95cea02016-07-06 16:00:32 -0700130 private PinnerHandler mPinnerHandler = null;
131
Carmen Jacksonf107a232017-05-16 10:37:26 -0700132 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
133 @Override
134 public void onReceive(Context context, Intent intent) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200135 // If an app has updated, update pinned files accordingly.
136 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Carmen Jacksonf107a232017-05-16 10:37:26 -0700137 Uri packageUri = intent.getData();
Calin Juravle31ce3a82017-05-22 17:49:01 -0700138 String packageName = packageUri.getSchemeSpecificPart();
139 ArraySet<String> updatedPackages = new ArraySet<>();
140 updatedPackages.add(packageName);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200141 update(updatedPackages, true /* force */);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700142 }
143 }
144 };
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700145
146 public PinnerService(Context context) {
147 super(context);
148
149 mContext = context;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200150 boolean shouldPinCamera = context.getResources().getBoolean(
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700151 com.android.internal.R.bool.config_pinnerCameraApp);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200152 boolean shouldPinHome = context.getResources().getBoolean(
153 com.android.internal.R.bool.config_pinnerHomeApp);
154 if (shouldPinCamera) {
155 mPinKeys.add(KEY_CAMERA);
156 }
157 if (shouldPinHome) {
158 mPinKeys.add(KEY_HOME);
159 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700160 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
Carmen Jacksonf107a232017-05-16 10:37:26 -0700161
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200162 mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
163 mAm = ActivityManager.getService();
164
Carmen Jacksonf107a232017-05-16 10:37:26 -0700165 IntentFilter filter = new IntentFilter();
166 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
167 filter.addDataScheme("package");
168 mContext.registerReceiver(mBroadcastReceiver, filter);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200169 registerUidListener();
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700170 }
171
172 @Override
173 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700174 if (DEBUG) {
175 Slog.i(TAG, "Starting PinnerService");
176 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700177 mBinderService = new BinderService();
178 publishBinderService("pinner", mBinderService);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700179 publishLocalService(PinnerService.class, this);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700180
Jeff Sharkey847bd852016-08-03 17:20:03 -0600181 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200182 sendPinAppsMessage(UserHandle.USER_SYSTEM);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700183 }
184
185 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200186 * Repin apps on user switch.
187 * <p>
188 * If more than one user is using the device each user may set a different preference for the
189 * individual apps. Make sure that user's preference is pinned into memory.
Philip Cuadraa95cea02016-07-06 16:00:32 -0700190 */
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700191 @Override
192 public void onSwitchUser(int userHandle) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200193 sendPinAppsMessage(userHandle);
194 }
195
196 @Override
197 public void onUnlockUser(int userHandle) {
198 sendPinAppsMessage(userHandle);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700199 }
200
Philip Cuadraa95cea02016-07-06 16:00:32 -0700201 /**
Carmen Jacksonf107a232017-05-16 10:37:26 -0700202 * Update the currently pinned files.
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200203 * Specifically, this only updates pinning for the apps that need to be pinned.
Carmen Jacksonf107a232017-05-16 10:37:26 -0700204 * The other files pinned in onStart will not need to be updated.
205 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200206 public void update(ArraySet<String> updatedPackages, boolean force) {
207 int currentUser = ActivityManager.getCurrentUser();
208 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
209 int key = mPinKeys.valueAt(i);
210 ApplicationInfo info = getInfoForKey(key, currentUser);
211 if (info != null && updatedPackages.contains(info.packageName)) {
212 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
213 sendPinAppMessage(key, currentUser, force);
214 }
Calin Juravle31ce3a82017-05-22 17:49:01 -0700215 }
Carmen Jacksonf107a232017-05-16 10:37:26 -0700216 }
217
218 /**
Philip Cuadraa95cea02016-07-06 16:00:32 -0700219 * Handler for on start pinning message
220 */
221 private void handlePinOnStart() {
222 // Files to pin come from the overlay and can be specified per-device config
223 String[] filesToPin = mContext.getResources().getStringArray(
224 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700225 // Continue trying to pin each file even if we fail to pin some of them
226 for (String fileToPin : filesToPin) {
227 PinnedFile pf = pinFile(fileToPin,
228 Integer.MAX_VALUE,
229 /*attemptPinIntrospection=*/false);
230 if (pf == null) {
231 Slog.e(TAG, "Failed to pin file = " + fileToPin);
232 continue;
233 }
234
235 synchronized (this) {
236 mPinnedFiles.add(pf);
Philip Cuadraa95cea02016-07-06 16:00:32 -0700237 }
238 }
239 }
240
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200241 private void registerUidListener() {
242 try {
243 mAm.registerUidObserver(new IUidObserver.Stub() {
244 @Override
245 public void onUidGone(int uid, boolean disabled) throws RemoteException {
246 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
247 PinnerService::handleUidGone, PinnerService.this, uid));
248 }
249
250 @Override
251 public void onUidActive(int uid) throws RemoteException {
252 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
253 PinnerService::handleUidActive, PinnerService.this, uid));
254 }
255
256 @Override
257 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
258 }
259
260 @Override
261 public void onUidStateChanged(int uid, int procState, long procStateSeq)
262 throws RemoteException {
263 }
264
265 @Override
266 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
267 }
268 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
269 } catch (RemoteException e) {
270 Slog.e(TAG, "Failed to register uid observer", e);
271 }
272 }
273
274 private void handleUidGone(int uid) {
275 updateActiveState(uid, false /* active */);
276 int key;
277 synchronized (this) {
278
279 // In case we have a pending repin, repin now. See mPendingRepin for more information.
280 key = mPendingRepin.getOrDefault(uid, -1);
281 if (key == -1) {
282 return;
283 }
284 mPendingRepin.remove(uid);
285 }
286 pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
287 }
288
289 private void handleUidActive(int uid) {
290 updateActiveState(uid, true /* active */);
291 }
292
293 private void updateActiveState(int uid, boolean active) {
294 synchronized (this) {
295 for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
296 PinnedApp app = mPinnedApps.valueAt(i);
297 if (app.uid == uid) {
298 app.active = active;
299 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700300 }
301 }
302 }
303
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200304 private void unpinApp(@AppKey int key) {
305 ArrayList<PinnedFile> pinnedAppFiles;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700306 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200307 PinnedApp app = mPinnedApps.get(key);
308 if (app == null) {
309 return;
310 }
311 mPinnedApps.remove(key);
312 pinnedAppFiles = new ArrayList<>(app.mFiles);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700313 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200314 for (PinnedFile pinnedFile : pinnedAppFiles) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700315 pinnedFile.close();
316 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700317 }
318
319 private boolean isResolverActivity(ActivityInfo info) {
320 return ResolverActivity.class.getName().equals(info.name);
321 }
322
323 private ApplicationInfo getCameraInfo(int userHandle) {
324 // find the camera via an intent
325 // use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE. On a
326 // device without a fbe enabled, the _SECURE intent will never get set.
327 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200328 return getApplicationInfoForIntent(cameraIntent, userHandle);
329 }
330
331 private ApplicationInfo getHomeInfo(int userHandle) {
332 Intent intent = mAmInternal.getHomeIntent();
333 return getApplicationInfoForIntent(intent, userHandle);
334 }
335
336 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle) {
337 if (intent == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700338 return null;
339 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200340 ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(intent,
341 MATCH_FLAGS, userHandle);
342 if (info == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700343 return null;
344 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200345 if (isResolverActivity(info.activityInfo)) {
346 return null;
347 }
348 return info.activityInfo.applicationInfo;
349 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700350
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200351 private void sendPinAppsMessage(int userHandle) {
352 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
353 userHandle));
354 }
355
356 private void pinApps(int userHandle) {
357 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
358 int key = mPinKeys.valueAt(i);
359 pinApp(key, userHandle, true /* force */);
360 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700361 }
362
Carmen Jacksonf107a232017-05-16 10:37:26 -0700363 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200364 * @see #pinApp(int, int, boolean)
Carmen Jacksonf107a232017-05-16 10:37:26 -0700365 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200366 private void sendPinAppMessage(int key, int userHandle, boolean force) {
367 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
368 key, userHandle, force));
369 }
370
371 /**
372 * Pins an app of a specific type {@code key}.
373 *
374 * @param force If false, this will not repin the app if it's currently active. See
375 * {@link #mPendingRepin}.
376 */
377 private void pinApp(int key, int userHandle, boolean force) {
378 int uid = getUidForKey(key);
379
380 // In case the app is currently active, don't repin until next process restart. See
381 // mPendingRepin for more information.
382 if (!force && uid != -1) {
383 synchronized (this) {
384 mPendingRepin.put(uid, key);
385 }
386 return;
387 }
388 unpinApp(key);
389 ApplicationInfo info = getInfoForKey(key, userHandle);
390 if (info != null) {
391 pinApp(key, info);
392 }
393 }
394
395 /**
396 * Checks whether the pinned package with {@code key} is active or not.
397
398 * @return The uid of the pinned app, or {@code -1} otherwise.
399 */
400 private int getUidForKey(@AppKey int key) {
401 synchronized (this) {
402 PinnedApp existing = mPinnedApps.get(key);
403 return existing != null && existing.active
404 ? existing.uid
405 : -1;
406 }
407 }
408
409 /**
410 * Retrieves the current application info for the given app type.
411 *
412 * @param key The app type to retrieve the info for.
413 * @param userHandle The user id of the current user.
414 */
415 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
416 switch (key) {
417 case KEY_CAMERA:
418 return getCameraInfo(userHandle);
419 case KEY_HOME:
420 return getHomeInfo(userHandle);
421 default:
422 return null;
423 }
424 }
425
426 /**
427 * @return The app type name for {@code key}.
428 */
429 private String getNameForKey(@AppKey int key) {
430 switch (key) {
431 case KEY_CAMERA:
432 return "Camera";
433 case KEY_HOME:
434 return "Home";
435 default:
436 return null;
437 }
438 }
439
440 /**
441 * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
442 */
443 private int getSizeLimitForKey(@AppKey int key) {
444 switch (key) {
445 case KEY_CAMERA:
446 return MAX_CAMERA_PIN_SIZE;
447 case KEY_HOME:
448 return MAX_HOME_PIN_SIZE;
449 default:
450 return 0;
451 }
452 }
453
454 /**
455 * Pins an application.
456 *
457 * @param key The key of the app to pin.
458 * @param appInfo The corresponding app info.
459 */
460 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
461 if (appInfo == null) {
462 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700463 }
464
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200465 PinnedApp pinnedApp = new PinnedApp(appInfo);
466 synchronized (this) {
467 mPinnedApps.put(key, pinnedApp);
468 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700469
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200470 // pin APK
471 int pinSizeLimit = getSizeLimitForKey(key);
472 String apk = appInfo.sourceDir;
473 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700474 if (pf == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200475 Slog.e(TAG, "Failed to pin " + apk);
476 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700477 }
478 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700479 Slog.i(TAG, "Pinned " + pf.fileName);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700480 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700481 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200482 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700483 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700484
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700485 // determine the ABI from either ApplicationInfo or Build
486 String arch = "arm";
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200487 if (appInfo.primaryCpuAbi != null) {
488 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
John Eckerdal60b07cd2016-11-03 14:04:47 +0100489 arch = arch + "64";
490 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700491 } else {
492 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
493 arch = arch + "64";
494 }
495 }
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700496
497 // get the path to the odex or oat file
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200498 String baseCodePath = appInfo.getBaseCodePath();
Calin Juravle128721a2017-05-15 20:20:50 -0700499 String[] files = null;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700500 try {
Calin Juravle128721a2017-05-15 20:20:50 -0700501 files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700502 } catch (IOException ioe) {}
Calin Juravle128721a2017-05-15 20:20:50 -0700503 if (files == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200504 return;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700505 }
506
507 //not pinning the oat/odex is not a fatal error
Calin Juravle128721a2017-05-15 20:20:50 -0700508 for (String file : files) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200509 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
Calin Juravle128721a2017-05-15 20:20:50 -0700510 if (pf != null) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700511 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200512 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700513 }
Calin Juravle128721a2017-05-15 20:20:50 -0700514 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700515 Slog.i(TAG, "Pinned " + pf.fileName);
Calin Juravle128721a2017-05-15 20:20:50 -0700516 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700517 }
518 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700519 }
520
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700521 /** mlock length bytes of fileToPin in memory
522 *
523 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
524 * look for a "pinlist.meta" file in the archive root directory. The structure of this
525 * file is a PINLIST_META as described below:
526 *
527 * <pre>
528 * PINLIST_META: PIN_RANGE*
529 * PIN_RANGE: PIN_START PIN_LENGTH
530 * PIN_START: big endian i32: offset in bytes of pin region from file start
531 * PIN_LENGTH: big endian i32: length of pin region in bytes
532 * </pre>
533 *
534 * (We use big endian because that's what DataInputStream is hardcoded to use.)
535 *
536 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
537 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
538 *
539 * After we open a file, we march through the list of pin ranges and attempt to pin
540 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
541 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
542 * before others, file generators can express pins in priority order, making most
543 * effective use of the pinned-page quota.
544 *
545 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
546 * meaningful interpretation. Also, a range locking a single byte of a page locks the
547 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
548 * are legal, but each pin of a byte counts toward the pin quota regardless of whether
549 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
550 * that ranges are non-overlapping.
551 *
552 * @param fileToPin Path to file to pin
553 * @param maxBytesToPin Maximum number of bytes to pin
554 * @param attemptPinIntrospection If true, try to open file as a
555 * zip in order to extract the
556 * @return Pinned memory resource owner thing or null on error
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700557 */
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700558 private static PinnedFile pinFile(String fileToPin,
559 int maxBytesToPin,
560 boolean attemptPinIntrospection) {
561 ZipFile fileAsZip = null;
562 InputStream pinRangeStream = null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700563 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700564 if (attemptPinIntrospection) {
565 fileAsZip = maybeOpenZip(fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700566 }
567
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700568 if (fileAsZip != null) {
569 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700570 }
571
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700572 Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700573
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700574 PinRangeSource pinRangeSource = (pinRangeStream != null)
575 ? new PinRangeSourceStream(pinRangeStream)
576 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
577 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
578 } finally {
579 safeClose(pinRangeStream);
580 safeClose(fileAsZip); // Also closes any streams we've opened
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700581 }
582 }
583
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700584 /**
585 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
586 * error, and return null.
587 */
588 private static ZipFile maybeOpenZip(String fileName) {
589 ZipFile zip = null;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700590 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700591 zip = new ZipFile(fileName);
592 } catch (IOException ex) {
593 Slog.w(TAG,
594 String.format(
595 "could not open \"%s\" as zip: pinning as blob",
596 fileName),
597 ex);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700598 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700599 return zip;
600 }
601
602 /**
603 * Open a pin metadata file in the zip if one is present.
604 *
605 * @param zipFile Zip file to search
606 * @return Open input stream or null on any error
607 */
608 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
609 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
610 InputStream pinMetaStream = null;
611 if (pinMetaEntry != null) {
612 try {
613 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
614 } catch (IOException ex) {
615 Slog.w(TAG,
616 String.format("error reading pin metadata \"%s\": pinning as blob",
617 fileName),
618 ex);
619 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700620 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700621 return pinMetaStream;
622 }
623
624 private static abstract class PinRangeSource {
625 /** Retrive a range to pin.
626 *
627 * @param outPinRange Receives the pin region
628 * @return True if we filled in outPinRange or false if we're out of pin entries
629 */
630 abstract boolean read(PinRange outPinRange);
631 }
632
633 private static final class PinRangeSourceStatic extends PinRangeSource {
634 private final int mPinStart;
635 private final int mPinLength;
636 private boolean mDone = false;
637
638 PinRangeSourceStatic(int pinStart, int pinLength) {
639 mPinStart = pinStart;
640 mPinLength = pinLength;
641 }
642
643 @Override
644 boolean read(PinRange outPinRange) {
645 outPinRange.start = mPinStart;
646 outPinRange.length = mPinLength;
647 boolean done = mDone;
648 mDone = true;
649 return !done;
650 }
651 }
652
653 private static final class PinRangeSourceStream extends PinRangeSource {
654 private final DataInputStream mStream;
655 private boolean mDone = false;
656
657 PinRangeSourceStream(InputStream stream) {
658 mStream = new DataInputStream(stream);
659 }
660
661 @Override
662 boolean read(PinRange outPinRange) {
663 if (!mDone) {
664 try {
665 outPinRange.start = mStream.readInt();
666 outPinRange.length = mStream.readInt();
667 } catch (IOException ex) {
668 mDone = true;
669 }
670 }
671 return !mDone;
672 }
673 }
674
675 /**
676 * Helper for pinFile.
677 *
678 * @param fileToPin Name of file to pin
679 * @param maxBytesToPin Maximum number of bytes to pin
680 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
681 * to pin.
682 * @return PinnedFile or null on error
683 */
684 private static PinnedFile pinFileRanges(
685 String fileToPin,
686 int maxBytesToPin,
687 PinRangeSource pinRangeSource)
688 {
689 FileDescriptor fd = new FileDescriptor();
690 long address = -1;
691 int mapSize = 0;
692
693 try {
694 int openFlags = (OsConstants.O_RDONLY |
695 OsConstants.O_CLOEXEC |
696 OsConstants.O_NOFOLLOW);
697 fd = Os.open(fileToPin, openFlags, 0);
698 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
699 address = Os.mmap(0, mapSize,
700 OsConstants.PROT_READ,
701 OsConstants.MAP_SHARED,
702 fd, /*offset=*/0);
703
704 PinRange pinRange = new PinRange();
705 int bytesPinned = 0;
706
707 // We pin at page granularity, so make sure the limit is page-aligned
708 if (maxBytesToPin % PAGE_SIZE != 0) {
709 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
710 }
711
712 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
713 int pinStart = pinRange.start;
714 int pinLength = pinRange.length;
715 pinStart = clamp(0, pinStart, mapSize);
716 pinLength = clamp(0, pinLength, mapSize - pinStart);
717 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
718
719 // mlock doesn't require the region to be page-aligned, but we snap the
720 // lock region to page boundaries anyway so that we don't under-count
721 // locking a single byte of a page as a charge of one byte even though the
722 // OS will retain the whole page. Thanks to this adjustment, we slightly
723 // over-count the pin charge of back-to-back pins touching the same page,
724 // but better that than undercounting. Besides: nothing stops pin metafile
725 // creators from making the actual regions page-aligned.
726 pinLength += pinStart % PAGE_SIZE;
727 pinStart -= pinStart % PAGE_SIZE;
728 if (pinLength % PAGE_SIZE != 0) {
729 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
730 }
731 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
732
733 if (pinLength > 0) {
734 if (DEBUG) {
735 Slog.d(TAG,
736 String.format(
737 "pinning at %s %s bytes of %s",
738 pinStart, pinLength, fileToPin));
739 }
740 Os.mlock(address + pinStart, pinLength);
741 }
742 bytesPinned += pinLength;
743 }
744
745 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
746 address = -1; // Ownership transferred
747 return pinnedFile;
748 } catch (ErrnoException ex) {
749 Slog.e(TAG, "Could not pin file " + fileToPin, ex);
750 return null;
751 } finally {
752 safeClose(fd);
753 if (address >= 0) {
754 safeMunmap(address, mapSize);
755 }
756 }
757 }
758
759 private static int clamp(int min, int value, int max) {
760 return Math.max(min, Math.min(value, max));
761 }
762
763 private static void safeMunmap(long address, long mapSize) {
764 try {
765 Os.munmap(address, mapSize);
766 } catch (ErrnoException ex) {
767 Slog.w(TAG, "ignoring error in unmap", ex);
768 }
769 }
770
771 /**
772 * Close FD, swallowing irrelevant errors.
773 */
774 private static void safeClose(@Nullable FileDescriptor fd) {
775 if (fd != null && fd.valid()) {
776 try {
777 Os.close(fd);
778 } catch (ErrnoException ex) {
779 // Swallow the exception: non-EBADF errors in close(2)
780 // indicate deferred paging write errors, which we
781 // don't care about here. The underlying file
782 // descriptor is always closed.
783 if (ex.errno == OsConstants.EBADF) {
784 throw new AssertionError(ex);
785 }
786 }
787 }
788 }
789
790 /**
791 * Close closeable thing, swallowing errors.
792 */
793 private static void safeClose(@Nullable Closeable thing) {
794 if (thing != null) {
795 try {
796 thing.close();
797 } catch (IOException ex) {
798 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
799 }
800 }
801 }
802
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700803 private final class BinderService extends Binder {
804 @Override
805 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600806 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200807 synchronized (PinnerService.this) {
808 long totalSize = 0;
809 for (PinnedFile pinnedFile : mPinnedFiles) {
810 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
811 totalSize += pinnedFile.bytesPinned;
812 }
813 pw.println();
814 for (int key : mPinnedApps.keySet()) {
815 PinnedApp app = mPinnedApps.get(key);
816 pw.print(getNameForKey(key));
817 pw.print(" uid="); pw.print(app.uid);
818 pw.print(" active="); pw.print(app.active);
819 pw.println();
820 for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
821 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
822 totalSize += pf.bytesPinned;
823 }
824 }
825 pw.format("Total size: %s\n", totalSize);
826 pw.println();
827 if (!mPendingRepin.isEmpty()) {
828 pw.print("Pending repin: ");
829 for (int key : mPendingRepin.values()) {
830 pw.print(getNameForKey(key)); pw.print(' ');
831 }
832 pw.println();
833 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700834 }
835 }
836 }
837
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700838 private static final class PinnedFile implements AutoCloseable {
839 private long mAddress;
840 final int mapSize;
841 final String fileName;
842 final int bytesPinned;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700843
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700844 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700845 mAddress = address;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700846 this.mapSize = mapSize;
847 this.fileName = fileName;
848 this.bytesPinned = bytesPinned;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700849 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700850
851 @Override
852 public void close() {
853 if (mAddress >= 0) {
854 safeMunmap(mAddress, mapSize);
855 mAddress = -1;
856 }
857 }
858
859 @Override
860 public void finalize() {
861 close();
862 }
863 }
864
865 final static class PinRange {
866 int start;
867 int length;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700868 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700869
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200870 /**
871 * Represents an app that was pinned.
872 */
873 private final class PinnedApp {
874
875 /**
876 * The uid of the package being pinned. This stays constant while the package stays
877 * installed.
878 */
879 final int uid;
880
881 /** Whether it is currently active, i.e. there is a running process from that package. */
882 boolean active;
883
884 /** List of pinned files. */
885 final ArrayList<PinnedFile> mFiles = new ArrayList<>();
886
887 private PinnedApp(ApplicationInfo appInfo) {
888 uid = appInfo.uid;
889 active = mAmInternal.isUidActive(uid);
890 }
891 }
892
Philip Cuadraa95cea02016-07-06 16:00:32 -0700893 final class PinnerHandler extends Handler {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700894 static final int PIN_ONSTART_MSG = 4001;
895
896 public PinnerHandler(Looper looper) {
897 super(looper, null, true);
898 }
899
900 @Override
901 public void handleMessage(Message msg) {
902 switch (msg.what) {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700903 case PIN_ONSTART_MSG:
904 {
905 handlePinOnStart();
906 }
907 break;
908
909 default:
910 super.handleMessage(msg);
911 }
912 }
913 }
914
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700915}