• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server;
18 
19 import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
20 import static android.app.ActivityManager.UID_OBSERVER_GONE;
21 
22 import android.annotation.IntDef;
23 import android.annotation.Nullable;
24 
25 import android.app.ActivityManager;
26 import android.app.ActivityManagerInternal;
27 import android.app.IActivityManager;
28 import android.app.IUidObserver;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.database.ContentObserver;
38 import android.net.Uri;
39 import android.os.Binder;
40 import android.os.Build;
41 import android.os.Handler;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.RemoteException;
45 import android.os.SystemProperties;
46 import android.os.UserHandle;
47 import android.os.UserManager;
48 import android.provider.MediaStore;
49 import android.provider.Settings;
50 import android.system.ErrnoException;
51 import android.system.Os;
52 import android.system.OsConstants;
53 import android.util.ArrayMap;
54 import android.util.ArraySet;
55 import android.util.Slog;
56 
57 import com.android.internal.annotations.GuardedBy;
58 import com.android.internal.app.ResolverActivity;
59 import com.android.internal.os.BackgroundThread;
60 import com.android.internal.util.DumpUtils;
61 import com.android.internal.util.function.pooled.PooledLambda;
62 
63 import com.android.server.wm.ActivityTaskManagerInternal;
64 import dalvik.system.DexFile;
65 import dalvik.system.VMRuntime;
66 
67 import java.io.FileDescriptor;
68 import java.io.Closeable;
69 import java.io.InputStream;
70 import java.io.DataInputStream;
71 import java.io.IOException;
72 import java.io.PrintWriter;
73 import java.lang.annotation.Retention;
74 import java.lang.annotation.RetentionPolicy;
75 import java.util.List;
76 import java.util.ArrayList;
77 
78 import java.util.zip.ZipFile;
79 import java.util.zip.ZipEntry;
80 
81 /**
82  * <p>PinnerService pins important files for key processes in memory.</p>
83  * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
84  * overlay.</p>
85  * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
86  */
87 public final class PinnerService extends SystemService {
88     private static final boolean DEBUG = false;
89     private static final String TAG = "PinnerService";
90 
91     private static final String PIN_META_FILENAME = "pinlist.meta";
92     private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
93     private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
94             | PackageManager.MATCH_DIRECT_BOOT_AWARE
95             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
96 
97     private static final int KEY_CAMERA = 0;
98     private static final int KEY_HOME = 1;
99 
100     private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
101     private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
102 
103     @IntDef({KEY_CAMERA, KEY_HOME})
104     @Retention(RetentionPolicy.SOURCE)
105     public @interface AppKey {}
106 
107     private final Context mContext;
108     private final ActivityTaskManagerInternal mAtmInternal;
109     private final ActivityManagerInternal mAmInternal;
110     private final IActivityManager mAm;
111     private final UserManager mUserManager;
112 
113     /** The list of the statically pinned files. */
114     @GuardedBy("this")
115     private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
116 
117     /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
118     @GuardedBy("this")
119     private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
120 
121     /**
122      * The list of the pinned apps that need to be repinned as soon as the all processes of a given
123      * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
124      * loaded into the processes once it restarts. So in case background dex opt recompiled these
125      * files, we still need to keep the old ones pinned until the processes restart.
126      * <p>
127      * This is a map from uid to {@link AppKey}
128      */
129     @GuardedBy("this")
130     private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
131 
132     /**
133      * A set of {@link AppKey} that are configured to be pinned.
134      */
135     private final ArraySet<Integer> mPinKeys = new ArraySet<>();
136 
137     private BinderService mBinderService;
138     private PinnerHandler mPinnerHandler = null;
139 
140     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
141         @Override
142         public void onReceive(Context context, Intent intent) {
143           // If an app has updated, update pinned files accordingly.
144           if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
145                 Uri packageUri = intent.getData();
146                 String packageName = packageUri.getSchemeSpecificPart();
147                 ArraySet<String> updatedPackages = new ArraySet<>();
148                 updatedPackages.add(packageName);
149                 update(updatedPackages, true /* force */);
150             }
151         }
152     };
153 
PinnerService(Context context)154     public PinnerService(Context context) {
155         super(context);
156 
157         mContext = context;
158         boolean shouldPinCamera = context.getResources().getBoolean(
159                 com.android.internal.R.bool.config_pinnerCameraApp);
160         boolean shouldPinHome = context.getResources().getBoolean(
161                 com.android.internal.R.bool.config_pinnerHomeApp);
162         if (shouldPinCamera) {
163             mPinKeys.add(KEY_CAMERA);
164         }
165         if (shouldPinHome) {
166             mPinKeys.add(KEY_HOME);
167         }
168         mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
169 
170         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
171         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
172         mAm = ActivityManager.getService();
173 
174         mUserManager = mContext.getSystemService(UserManager.class);
175 
176         IntentFilter filter = new IntentFilter();
177         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
178         filter.addDataScheme("package");
179         mContext.registerReceiver(mBroadcastReceiver, filter);
180 
181         registerUidListener();
182         registerUserSetupCompleteListener();
183     }
184 
185     @Override
onStart()186     public void onStart() {
187         if (DEBUG) {
188             Slog.i(TAG, "Starting PinnerService");
189         }
190         mBinderService = new BinderService();
191         publishBinderService("pinner", mBinderService);
192         publishLocalService(PinnerService.class, this);
193 
194         mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
195         sendPinAppsMessage(UserHandle.USER_SYSTEM);
196     }
197 
198     /**
199      * Repin apps on user switch.
200      * <p>
201      * If more than one user is using the device each user may set a different preference for the
202      * individual apps. Make sure that user's preference is pinned into memory.
203      */
204     @Override
onSwitchUser(int userHandle)205     public void onSwitchUser(int userHandle) {
206         if (!mUserManager.isManagedProfile(userHandle)) {
207             sendPinAppsMessage(userHandle);
208         }
209     }
210 
211     @Override
onUnlockUser(int userHandle)212     public void onUnlockUser(int userHandle) {
213         if (!mUserManager.isManagedProfile(userHandle)) {
214             sendPinAppsMessage(userHandle);
215         }
216     }
217 
218     /**
219      * Update the currently pinned files.
220      * Specifically, this only updates pinning for the apps that need to be pinned.
221      * The other files pinned in onStart will not need to be updated.
222      */
update(ArraySet<String> updatedPackages, boolean force)223     public void update(ArraySet<String> updatedPackages, boolean force) {
224         int currentUser = ActivityManager.getCurrentUser();
225         for (int i = mPinKeys.size() - 1; i >= 0; i--) {
226             int key = mPinKeys.valueAt(i);
227             ApplicationInfo info = getInfoForKey(key, currentUser);
228             if (info != null && updatedPackages.contains(info.packageName)) {
229                 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
230                 sendPinAppMessage(key, currentUser, force);
231             }
232         }
233     }
234 
235     /**
236      * Handler for on start pinning message
237      */
handlePinOnStart()238     private void handlePinOnStart() {
239         final String bootImage = SystemProperties.get("dalvik.vm.boot-image", "");
240         String[] filesToPin = null;
241         if (bootImage.endsWith("apex.art")) {
242             // Use the files listed for that specific boot image
243             filesToPin = mContext.getResources().getStringArray(
244                   com.android.internal.R.array.config_apexBootImagePinnerServiceFiles);
245         } else {
246             // Files to pin come from the overlay and can be specified per-device config
247             filesToPin = mContext.getResources().getStringArray(
248                   com.android.internal.R.array.config_defaultPinnerServiceFiles);
249         }
250         // Continue trying to pin each file even if we fail to pin some of them
251         for (String fileToPin : filesToPin) {
252             PinnedFile pf = pinFile(fileToPin,
253                                     Integer.MAX_VALUE,
254                                     /*attemptPinIntrospection=*/false);
255             if (pf == null) {
256                 Slog.e(TAG, "Failed to pin file = " + fileToPin);
257                 continue;
258             }
259 
260             synchronized (this) {
261                 mPinnedFiles.add(pf);
262             }
263         }
264     }
265 
266     /**
267      * Registers a listener to repin the home app when user setup is complete, as the home intent
268      * initially resolves to setup wizard, but once setup is complete, it will resolve to the
269      * regular home app.
270      */
registerUserSetupCompleteListener()271     private void registerUserSetupCompleteListener() {
272         Uri userSetupCompleteUri = Settings.Secure.getUriFor(
273                 Settings.Secure.USER_SETUP_COMPLETE);
274         mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
275                 false, new ContentObserver(null) {
276                     @Override
277                     public void onChange(boolean selfChange, Uri uri) {
278                         if (userSetupCompleteUri.equals(uri)) {
279                             sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
280                                     true /* force */);
281                         }
282                     }
283                 }, UserHandle.USER_ALL);
284     }
285 
registerUidListener()286     private void registerUidListener() {
287         try {
288             mAm.registerUidObserver(new IUidObserver.Stub() {
289                 @Override
290                 public void onUidGone(int uid, boolean disabled) throws RemoteException {
291                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
292                             PinnerService::handleUidGone, PinnerService.this, uid));
293                 }
294 
295                 @Override
296                 public void onUidActive(int uid) throws RemoteException {
297                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
298                             PinnerService::handleUidActive, PinnerService.this, uid));
299                 }
300 
301                 @Override
302                 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
303                 }
304 
305                 @Override
306                 public void onUidStateChanged(int uid, int procState, long procStateSeq)
307                         throws RemoteException {
308                 }
309 
310                 @Override
311                 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
312                 }
313             }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
314         } catch (RemoteException e) {
315             Slog.e(TAG, "Failed to register uid observer", e);
316         }
317     }
318 
handleUidGone(int uid)319     private void handleUidGone(int uid) {
320         updateActiveState(uid, false /* active */);
321         int key;
322         synchronized (this) {
323 
324             // In case we have a pending repin, repin now. See mPendingRepin for more information.
325             key = mPendingRepin.getOrDefault(uid, -1);
326             if (key == -1) {
327                 return;
328             }
329             mPendingRepin.remove(uid);
330         }
331         pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
332     }
333 
handleUidActive(int uid)334     private void handleUidActive(int uid) {
335         updateActiveState(uid, true /* active */);
336     }
337 
updateActiveState(int uid, boolean active)338     private void updateActiveState(int uid, boolean active) {
339         synchronized (this) {
340             for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
341                 PinnedApp app = mPinnedApps.valueAt(i);
342                 if (app.uid == uid) {
343                     app.active = active;
344                 }
345             }
346         }
347     }
348 
unpinApp(@ppKey int key)349     private void unpinApp(@AppKey int key) {
350         ArrayList<PinnedFile> pinnedAppFiles;
351         synchronized (this) {
352             PinnedApp app = mPinnedApps.get(key);
353             if (app == null) {
354                 return;
355             }
356             mPinnedApps.remove(key);
357             pinnedAppFiles = new ArrayList<>(app.mFiles);
358         }
359         for (PinnedFile pinnedFile : pinnedAppFiles) {
360             pinnedFile.close();
361         }
362     }
363 
isResolverActivity(ActivityInfo info)364     private boolean isResolverActivity(ActivityInfo info) {
365         return ResolverActivity.class.getName().equals(info.name);
366     }
367 
getCameraInfo(int userHandle)368     private ApplicationInfo getCameraInfo(int userHandle) {
369         Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
370         ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
371             false /* defaultToSystemApp */);
372 
373         // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
374         // We don't use _SECURE first because it will never get set on a device
375         // without File-based Encryption. But if the user has only set the intent
376         // before unlocking their device, we may still be able to identify their
377         // preference using this intent.
378         if (info == null) {
379             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
380             info = getApplicationInfoForIntent(cameraIntent, userHandle,
381                 false /* defaultToSystemApp */);
382         }
383 
384         // If the _SECURE intent doesn't resolve, try the original intent but request
385         // the system app for camera if there was more than one result.
386         if (info == null) {
387             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
388             info = getApplicationInfoForIntent(cameraIntent, userHandle,
389                 true /* defaultToSystemApp */);
390         }
391         return info;
392     }
393 
getHomeInfo(int userHandle)394     private ApplicationInfo getHomeInfo(int userHandle) {
395         Intent intent = mAtmInternal.getHomeIntent();
396         return getApplicationInfoForIntent(intent, userHandle, false);
397     }
398 
getApplicationInfoForIntent(Intent intent, int userHandle, boolean defaultToSystemApp)399     private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
400             boolean defaultToSystemApp) {
401         if (intent == null) {
402             return null;
403         }
404 
405         ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
406                 MATCH_FLAGS, userHandle);
407 
408         // If this intent can resolve to only one app, choose that one.
409         // Otherwise, if we've requested to default to the system app, return it;
410         // if we have not requested that default, return null if there's more than one option.
411         // If there's more than one system app, return null since we don't know which to pick.
412         if (resolveInfo == null) {
413             return null;
414         }
415 
416         if (!isResolverActivity(resolveInfo.activityInfo)) {
417             return resolveInfo.activityInfo.applicationInfo;
418         }
419 
420         if (defaultToSystemApp) {
421             List<ResolveInfo> infoList = mContext.getPackageManager()
422                 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
423             ApplicationInfo systemAppInfo = null;
424             for (ResolveInfo info : infoList) {
425                 if ((info.activityInfo.applicationInfo.flags
426                       & ApplicationInfo.FLAG_SYSTEM) != 0) {
427                     if (systemAppInfo == null) {
428                         systemAppInfo = info.activityInfo.applicationInfo;
429                     } else {
430                         // If there's more than one system app, return null due to ambiguity.
431                         return null;
432                     }
433                 }
434             }
435             return systemAppInfo;
436         }
437 
438         return null;
439     }
440 
sendPinAppsMessage(int userHandle)441     private void sendPinAppsMessage(int userHandle) {
442         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
443                 userHandle));
444     }
445 
pinApps(int userHandle)446     private void pinApps(int userHandle) {
447         for (int i = mPinKeys.size() - 1; i >= 0; i--) {
448             int key = mPinKeys.valueAt(i);
449             pinApp(key, userHandle, true /* force */);
450         }
451     }
452 
453     /**
454      * @see #pinApp(int, int, boolean)
455      */
sendPinAppMessage(int key, int userHandle, boolean force)456     private void sendPinAppMessage(int key, int userHandle, boolean force) {
457         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
458                 key, userHandle, force));
459     }
460 
461     /**
462      * Pins an app of a specific type {@code key}.
463      *
464      * @param force If false, this will not repin the app if it's currently active. See
465      *              {@link #mPendingRepin}.
466      */
pinApp(int key, int userHandle, boolean force)467     private void pinApp(int key, int userHandle, boolean force) {
468         int uid = getUidForKey(key);
469 
470         // In case the app is currently active, don't repin until next process restart. See
471         // mPendingRepin for more information.
472         if (!force && uid != -1) {
473             synchronized (this) {
474                 mPendingRepin.put(uid, key);
475             }
476             return;
477         }
478         unpinApp(key);
479         ApplicationInfo info = getInfoForKey(key, userHandle);
480         if (info != null) {
481             pinApp(key, info);
482         }
483     }
484 
485     /**
486      * Checks whether the pinned package with {@code key} is active or not.
487 
488      * @return The uid of the pinned app, or {@code -1} otherwise.
489      */
getUidForKey(@ppKey int key)490     private int getUidForKey(@AppKey int key) {
491         synchronized (this) {
492             PinnedApp existing = mPinnedApps.get(key);
493             return existing != null && existing.active
494                     ? existing.uid
495                     : -1;
496         }
497     }
498 
499     /**
500      * Retrieves the current application info for the given app type.
501      *
502      * @param key The app type to retrieve the info for.
503      * @param userHandle The user id of the current user.
504      */
getInfoForKey(@ppKey int key, int userHandle)505     private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
506         switch (key) {
507             case KEY_CAMERA:
508                 return getCameraInfo(userHandle);
509             case KEY_HOME:
510                 return getHomeInfo(userHandle);
511             default:
512                 return null;
513         }
514     }
515 
516     /**
517      * @return The app type name for {@code key}.
518      */
getNameForKey(@ppKey int key)519     private String getNameForKey(@AppKey int key) {
520         switch (key) {
521             case KEY_CAMERA:
522                 return "Camera";
523             case KEY_HOME:
524                 return "Home";
525             default:
526                 return null;
527         }
528     }
529 
530     /**
531      * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
532      */
getSizeLimitForKey(@ppKey int key)533     private int getSizeLimitForKey(@AppKey int key) {
534         switch (key) {
535             case KEY_CAMERA:
536                 return MAX_CAMERA_PIN_SIZE;
537             case KEY_HOME:
538                 return MAX_HOME_PIN_SIZE;
539             default:
540                 return 0;
541         }
542     }
543 
544     /**
545      * Pins an application.
546      *
547      * @param key The key of the app to pin.
548      * @param appInfo The corresponding app info.
549      */
pinApp(@ppKey int key, @Nullable ApplicationInfo appInfo)550     private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
551         if (appInfo == null) {
552             return;
553         }
554 
555         PinnedApp pinnedApp = new PinnedApp(appInfo);
556         synchronized (this) {
557             mPinnedApps.put(key, pinnedApp);
558         }
559 
560         // pin APK
561         int pinSizeLimit = getSizeLimitForKey(key);
562         String apk = appInfo.sourceDir;
563         PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
564         if (pf == null) {
565             Slog.e(TAG, "Failed to pin " + apk);
566             return;
567         }
568         if (DEBUG) {
569             Slog.i(TAG, "Pinned " + pf.fileName);
570         }
571         synchronized (this) {
572             pinnedApp.mFiles.add(pf);
573         }
574 
575         // determine the ABI from either ApplicationInfo or Build
576         String arch = "arm";
577         if (appInfo.primaryCpuAbi != null) {
578             if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
579                 arch = arch + "64";
580             }
581         } else {
582             if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
583                 arch = arch + "64";
584             }
585         }
586 
587         // get the path to the odex or oat file
588         String baseCodePath = appInfo.getBaseCodePath();
589         String[] files = null;
590         try {
591             files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
592         } catch (IOException ioe) {}
593         if (files == null) {
594             return;
595         }
596 
597         //not pinning the oat/odex is not a fatal error
598         for (String file : files) {
599             pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
600             if (pf != null) {
601                 synchronized (this) {
602                     pinnedApp.mFiles.add(pf);
603                 }
604                 if (DEBUG) {
605                     Slog.i(TAG, "Pinned " + pf.fileName);
606                 }
607             }
608         }
609     }
610 
611     /** mlock length bytes of fileToPin in memory
612      *
613      * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
614      * look for a "pinlist.meta" file in the archive root directory. The structure of this
615      * file is a PINLIST_META as described below:
616      *
617      * <pre>
618      *   PINLIST_META: PIN_RANGE*
619      *   PIN_RANGE: PIN_START PIN_LENGTH
620      *   PIN_START: big endian i32: offset in bytes of pin region from file start
621      *   PIN_LENGTH: big endian i32: length of pin region in bytes
622      * </pre>
623      *
624      * (We use big endian because that's what DataInputStream is hardcoded to use.)
625      *
626      * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
627      * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
628      *
629      * After we open a file, we march through the list of pin ranges and attempt to pin
630      * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
631      * pinned range to fit.)  In this way, by choosing to emit certain PIN_RANGE pairs
632      * before others, file generators can express pins in priority order, making most
633      * effective use of the pinned-page quota.
634      *
635      * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
636      * meaningful interpretation. Also, a range locking a single byte of a page locks the
637      * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
638      * are legal, but each pin of a byte counts toward the pin quota regardless of whether
639      * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
640      * that ranges are non-overlapping.
641      *
642      * @param fileToPin Path to file to pin
643      * @param maxBytesToPin Maximum number of bytes to pin
644      * @param attemptPinIntrospection If true, try to open file as a
645      *   zip in order to extract the
646      * @return Pinned memory resource owner thing or null on error
647      */
pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection)648     private static PinnedFile pinFile(String fileToPin,
649                                       int maxBytesToPin,
650                                       boolean attemptPinIntrospection) {
651         ZipFile fileAsZip = null;
652         InputStream pinRangeStream = null;
653         try {
654             if (attemptPinIntrospection) {
655                 fileAsZip = maybeOpenZip(fileToPin);
656             }
657 
658             if (fileAsZip != null) {
659                 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
660             }
661 
662             Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
663 
664             PinRangeSource pinRangeSource = (pinRangeStream != null)
665                 ? new PinRangeSourceStream(pinRangeStream)
666                 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
667             return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
668         } finally {
669             safeClose(pinRangeStream);
670             safeClose(fileAsZip);  // Also closes any streams we've opened
671         }
672     }
673 
674     /**
675      * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
676      * error, and return null.
677      */
maybeOpenZip(String fileName)678     private static ZipFile maybeOpenZip(String fileName) {
679         ZipFile zip = null;
680         try {
681             zip = new ZipFile(fileName);
682         } catch (IOException ex) {
683             Slog.w(TAG,
684                    String.format(
685                        "could not open \"%s\" as zip: pinning as blob",
686                                  fileName),
687                    ex);
688         }
689         return zip;
690     }
691 
692     /**
693      * Open a pin metadata file in the zip if one is present.
694      *
695      * @param zipFile Zip file to search
696      * @return Open input stream or null on any error
697      */
maybeOpenPinMetaInZip(ZipFile zipFile, String fileName)698     private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
699         ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
700         InputStream pinMetaStream = null;
701         if (pinMetaEntry != null) {
702             try {
703                 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
704             } catch (IOException ex) {
705                 Slog.w(TAG,
706                        String.format("error reading pin metadata \"%s\": pinning as blob",
707                                      fileName),
708                        ex);
709             }
710         }
711         return pinMetaStream;
712     }
713 
714     private static abstract class PinRangeSource {
715         /** Retrive a range to pin.
716          *
717          * @param outPinRange Receives the pin region
718          * @return True if we filled in outPinRange or false if we're out of pin entries
719          */
read(PinRange outPinRange)720         abstract boolean read(PinRange outPinRange);
721     }
722 
723     private static final class PinRangeSourceStatic extends PinRangeSource {
724         private final int mPinStart;
725         private final int mPinLength;
726         private boolean mDone = false;
727 
PinRangeSourceStatic(int pinStart, int pinLength)728         PinRangeSourceStatic(int pinStart, int pinLength) {
729             mPinStart = pinStart;
730             mPinLength = pinLength;
731         }
732 
733         @Override
read(PinRange outPinRange)734         boolean read(PinRange outPinRange) {
735             outPinRange.start = mPinStart;
736             outPinRange.length = mPinLength;
737             boolean done = mDone;
738             mDone = true;
739             return !done;
740         }
741     }
742 
743     private static final class PinRangeSourceStream extends PinRangeSource {
744         private final DataInputStream mStream;
745         private boolean mDone = false;
746 
PinRangeSourceStream(InputStream stream)747         PinRangeSourceStream(InputStream stream) {
748             mStream = new DataInputStream(stream);
749         }
750 
751         @Override
read(PinRange outPinRange)752         boolean read(PinRange outPinRange) {
753             if (!mDone) {
754                 try {
755                     outPinRange.start = mStream.readInt();
756                     outPinRange.length = mStream.readInt();
757                 } catch (IOException ex) {
758                     mDone = true;
759                 }
760             }
761             return !mDone;
762         }
763     }
764 
765     /**
766      * Helper for pinFile.
767      *
768      * @param fileToPin Name of file to pin
769      * @param maxBytesToPin Maximum number of bytes to pin
770      * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
771      *   to pin.
772      * @return PinnedFile or null on error
773      */
pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource)774     private static PinnedFile pinFileRanges(
775         String fileToPin,
776         int maxBytesToPin,
777         PinRangeSource pinRangeSource)
778     {
779         FileDescriptor fd = new FileDescriptor();
780         long address = -1;
781         int mapSize = 0;
782 
783         try {
784             int openFlags = (OsConstants.O_RDONLY |
785                              OsConstants.O_CLOEXEC |
786                              OsConstants.O_NOFOLLOW);
787             fd = Os.open(fileToPin, openFlags, 0);
788             mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
789             address = Os.mmap(0, mapSize,
790                               OsConstants.PROT_READ,
791                               OsConstants.MAP_SHARED,
792                               fd, /*offset=*/0);
793 
794             PinRange pinRange = new PinRange();
795             int bytesPinned = 0;
796 
797             // We pin at page granularity, so make sure the limit is page-aligned
798             if (maxBytesToPin % PAGE_SIZE != 0) {
799                 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
800             }
801 
802             while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
803                 int pinStart = pinRange.start;
804                 int pinLength = pinRange.length;
805                 pinStart = clamp(0, pinStart, mapSize);
806                 pinLength = clamp(0, pinLength, mapSize - pinStart);
807                 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
808 
809                 // mlock doesn't require the region to be page-aligned, but we snap the
810                 // lock region to page boundaries anyway so that we don't under-count
811                 // locking a single byte of a page as a charge of one byte even though the
812                 // OS will retain the whole page. Thanks to this adjustment, we slightly
813                 // over-count the pin charge of back-to-back pins touching the same page,
814                 // but better that than undercounting. Besides: nothing stops pin metafile
815                 // creators from making the actual regions page-aligned.
816                 pinLength += pinStart % PAGE_SIZE;
817                 pinStart -= pinStart % PAGE_SIZE;
818                 if (pinLength % PAGE_SIZE != 0) {
819                     pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
820                 }
821                 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
822 
823                 if (pinLength > 0) {
824                     if (DEBUG) {
825                         Slog.d(TAG,
826                                String.format(
827                                    "pinning at %s %s bytes of %s",
828                                    pinStart, pinLength, fileToPin));
829                     }
830                     Os.mlock(address + pinStart, pinLength);
831                 }
832                 bytesPinned += pinLength;
833             }
834 
835             PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
836             address = -1;  // Ownership transferred
837             return pinnedFile;
838         } catch (ErrnoException ex) {
839             Slog.e(TAG, "Could not pin file " + fileToPin, ex);
840             return null;
841         } finally {
842             safeClose(fd);
843             if (address >= 0) {
844                 safeMunmap(address, mapSize);
845             }
846         }
847     }
848 
clamp(int min, int value, int max)849     private static int clamp(int min, int value, int max) {
850         return Math.max(min, Math.min(value, max));
851     }
852 
safeMunmap(long address, long mapSize)853     private static void safeMunmap(long address, long mapSize) {
854         try {
855             Os.munmap(address, mapSize);
856         } catch (ErrnoException ex) {
857             Slog.w(TAG, "ignoring error in unmap", ex);
858         }
859     }
860 
861     /**
862      * Close FD, swallowing irrelevant errors.
863      */
safeClose(@ullable FileDescriptor fd)864     private static void safeClose(@Nullable FileDescriptor fd) {
865         if (fd != null && fd.valid()) {
866             try {
867                 Os.close(fd);
868             } catch (ErrnoException ex) {
869                 // Swallow the exception: non-EBADF errors in close(2)
870                 // indicate deferred paging write errors, which we
871                 // don't care about here. The underlying file
872                 // descriptor is always closed.
873                 if (ex.errno == OsConstants.EBADF) {
874                     throw new AssertionError(ex);
875                 }
876             }
877         }
878     }
879 
880     /**
881      * Close closeable thing, swallowing errors.
882      */
safeClose(@ullable Closeable thing)883     private static void safeClose(@Nullable Closeable thing) {
884         if (thing != null) {
885             try {
886                 thing.close();
887             } catch (IOException ex) {
888                 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
889             }
890         }
891     }
892 
893     private final class BinderService extends Binder {
894         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)895         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
896             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
897             synchronized (PinnerService.this) {
898                 long totalSize = 0;
899                 for (PinnedFile pinnedFile : mPinnedFiles) {
900                     pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
901                     totalSize += pinnedFile.bytesPinned;
902                 }
903                 pw.println();
904                 for (int key : mPinnedApps.keySet()) {
905                     PinnedApp app = mPinnedApps.get(key);
906                     pw.print(getNameForKey(key));
907                     pw.print(" uid="); pw.print(app.uid);
908                     pw.print(" active="); pw.print(app.active);
909                     pw.println();
910                     for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
911                         pw.print("  "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
912                         totalSize += pf.bytesPinned;
913                     }
914                 }
915                 pw.format("Total size: %s\n", totalSize);
916                 pw.println();
917                 if (!mPendingRepin.isEmpty()) {
918                     pw.print("Pending repin: ");
919                     for (int key : mPendingRepin.values()) {
920                         pw.print(getNameForKey(key)); pw.print(' ');
921                     }
922                     pw.println();
923                 }
924             }
925         }
926     }
927 
928     private static final class PinnedFile implements AutoCloseable {
929         private long mAddress;
930         final int mapSize;
931         final String fileName;
932         final int bytesPinned;
933 
PinnedFile(long address, int mapSize, String fileName, int bytesPinned)934         PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
935              mAddress = address;
936              this.mapSize = mapSize;
937              this.fileName = fileName;
938              this.bytesPinned = bytesPinned;
939         }
940 
941         @Override
close()942         public void close() {
943             if (mAddress >= 0) {
944                 safeMunmap(mAddress, mapSize);
945                 mAddress = -1;
946             }
947         }
948 
949         @Override
finalize()950         public void finalize() {
951             close();
952         }
953     }
954 
955     final static class PinRange {
956         int start;
957         int length;
958     }
959 
960     /**
961      * Represents an app that was pinned.
962      */
963     private final class PinnedApp {
964 
965         /**
966          * The uid of the package being pinned. This stays constant while the package stays
967          * installed.
968          */
969         final int uid;
970 
971         /** Whether it is currently active, i.e. there is a running process from that package. */
972         boolean active;
973 
974         /** List of pinned files. */
975         final ArrayList<PinnedFile> mFiles = new ArrayList<>();
976 
PinnedApp(ApplicationInfo appInfo)977         private PinnedApp(ApplicationInfo appInfo) {
978             uid = appInfo.uid;
979             active = mAmInternal.isUidActive(uid);
980         }
981     }
982 
983     final class PinnerHandler extends Handler {
984         static final int PIN_ONSTART_MSG = 4001;
985 
PinnerHandler(Looper looper)986         public PinnerHandler(Looper looper) {
987             super(looper, null, true);
988         }
989 
990         @Override
handleMessage(Message msg)991         public void handleMessage(Message msg) {
992             switch (msg.what) {
993                 case PIN_ONSTART_MSG:
994                 {
995                     handlePinOnStart();
996                 }
997                 break;
998 
999                 default:
1000                     super.handleMessage(msg);
1001             }
1002         }
1003     }
1004 
1005 }
1006