1 /*
2  * Copyright (C) 2014 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.pm;
18 
19 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
21 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
22 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
23 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
24 import static android.system.OsConstants.O_CREAT;
25 import static android.system.OsConstants.O_RDONLY;
26 import static android.system.OsConstants.O_WRONLY;
27 import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
28 import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir;
29 
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentSender;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.IPackageInstallObserver2;
35 import android.content.pm.IPackageInstallerSession;
36 import android.content.pm.PackageInstaller;
37 import android.content.pm.PackageInstaller.SessionInfo;
38 import android.content.pm.PackageInstaller.SessionParams;
39 import android.content.pm.PackageManager;
40 import android.content.pm.PackageParser;
41 import android.content.pm.PackageParser.ApkLite;
42 import android.content.pm.PackageParser.PackageLite;
43 import android.content.pm.PackageParser.PackageParserException;
44 import android.content.pm.Signature;
45 import android.os.Bundle;
46 import android.os.FileBridge;
47 import android.os.FileUtils;
48 import android.os.Handler;
49 import android.os.Looper;
50 import android.os.Message;
51 import android.os.ParcelFileDescriptor;
52 import android.os.Process;
53 import android.os.RemoteException;
54 import android.os.UserHandle;
55 import android.system.ErrnoException;
56 import android.system.Os;
57 import android.system.OsConstants;
58 import android.system.StructStat;
59 import android.util.ArraySet;
60 import android.util.ExceptionUtils;
61 import android.util.MathUtils;
62 import android.util.Slog;
63 
64 import com.android.internal.annotations.GuardedBy;
65 import com.android.internal.content.NativeLibraryHelper;
66 import com.android.internal.content.PackageHelper;
67 import com.android.internal.util.ArrayUtils;
68 import com.android.internal.util.IndentingPrintWriter;
69 import com.android.internal.util.Preconditions;
70 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
71 
72 import libcore.io.IoUtils;
73 import libcore.io.Libcore;
74 
75 import java.io.File;
76 import java.io.FileDescriptor;
77 import java.io.IOException;
78 import java.util.ArrayList;
79 import java.util.List;
80 import java.util.concurrent.atomic.AtomicInteger;
81 
82 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
83     private static final String TAG = "PackageInstaller";
84     private static final boolean LOGD = true;
85 
86     private static final int MSG_COMMIT = 0;
87 
88     // TODO: enforce INSTALL_ALLOW_TEST
89     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
90 
91     private final PackageInstallerService.InternalCallback mCallback;
92     private final Context mContext;
93     private final PackageManagerService mPm;
94     private final Handler mHandler;
95 
96     final int sessionId;
97     final int userId;
98     final String installerPackageName;
99     final int installerUid;
100     final SessionParams params;
101     final long createdMillis;
102 
103     /** Staging location where client data is written. */
104     final File stageDir;
105     final String stageCid;
106 
107     private final AtomicInteger mActiveCount = new AtomicInteger();
108 
109     private final Object mLock = new Object();
110 
111     @GuardedBy("mLock")
112     private float mClientProgress = 0;
113     @GuardedBy("mLock")
114     private float mInternalProgress = 0;
115 
116     @GuardedBy("mLock")
117     private float mProgress = 0;
118     @GuardedBy("mLock")
119     private float mReportedProgress = -1;
120 
121     @GuardedBy("mLock")
122     private boolean mPrepared = false;
123     @GuardedBy("mLock")
124     private boolean mSealed = false;
125     @GuardedBy("mLock")
126     private boolean mPermissionsAccepted = false;
127     @GuardedBy("mLock")
128     private boolean mDestroyed = false;
129 
130     private int mFinalStatus;
131     private String mFinalMessage;
132 
133     @GuardedBy("mLock")
134     private ArrayList<FileBridge> mBridges = new ArrayList<>();
135 
136     @GuardedBy("mLock")
137     private IPackageInstallObserver2 mRemoteObserver;
138 
139     /** Fields derived from commit parsing */
140     private String mPackageName;
141     private int mVersionCode;
142     private Signature[] mSignatures;
143 
144     /**
145      * Path to the validated base APK for this session, which may point at an
146      * APK inside the session (when the session defines the base), or it may
147      * point at the existing base APK (when adding splits to an existing app).
148      * <p>
149      * This is used when confirming permissions, since we can't fully stage the
150      * session inside an ASEC before confirming with user.
151      */
152     @GuardedBy("mLock")
153     private File mResolvedBaseFile;
154 
155     @GuardedBy("mLock")
156     private File mResolvedStageDir;
157 
158     @GuardedBy("mLock")
159     private final List<File> mResolvedStagedFiles = new ArrayList<>();
160     @GuardedBy("mLock")
161     private final List<File> mResolvedInheritedFiles = new ArrayList<>();
162 
163     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
164         @Override
165         public boolean handleMessage(Message msg) {
166             synchronized (mLock) {
167                 if (msg.obj != null) {
168                     mRemoteObserver = (IPackageInstallObserver2) msg.obj;
169                 }
170 
171                 try {
172                     commitLocked();
173                 } catch (PackageManagerException e) {
174                     final String completeMsg = ExceptionUtils.getCompleteMessage(e);
175                     Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
176                     destroyInternal();
177                     dispatchSessionFinished(e.error, completeMsg, null);
178                 }
179 
180                 return true;
181             }
182         }
183     };
184 
PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed)185     public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
186             Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
187             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
188             File stageDir, String stageCid, boolean prepared, boolean sealed) {
189         mCallback = callback;
190         mContext = context;
191         mPm = pm;
192         mHandler = new Handler(looper, mHandlerCallback);
193 
194         this.sessionId = sessionId;
195         this.userId = userId;
196         this.installerPackageName = installerPackageName;
197         this.installerUid = installerUid;
198         this.params = params;
199         this.createdMillis = createdMillis;
200         this.stageDir = stageDir;
201         this.stageCid = stageCid;
202 
203         if ((stageDir == null) == (stageCid == null)) {
204             throw new IllegalArgumentException(
205                     "Exactly one of stageDir or stageCid stage must be set");
206         }
207 
208         mPrepared = prepared;
209         mSealed = sealed;
210 
211         if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
212                 == PackageManager.PERMISSION_GRANTED) || (installerUid == Process.ROOT_UID)) {
213             mPermissionsAccepted = true;
214         } else {
215             mPermissionsAccepted = false;
216         }
217     }
218 
generateInfo()219     public SessionInfo generateInfo() {
220         final SessionInfo info = new SessionInfo();
221         synchronized (mLock) {
222             info.sessionId = sessionId;
223             info.installerPackageName = installerPackageName;
224             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
225                     mResolvedBaseFile.getAbsolutePath() : null;
226             info.progress = mProgress;
227             info.sealed = mSealed;
228             info.active = mActiveCount.get() > 0;
229 
230             info.mode = params.mode;
231             info.sizeBytes = params.sizeBytes;
232             info.appPackageName = params.appPackageName;
233             info.appIcon = params.appIcon;
234             info.appLabel = params.appLabel;
235         }
236         return info;
237     }
238 
isPrepared()239     public boolean isPrepared() {
240         synchronized (mLock) {
241             return mPrepared;
242         }
243     }
244 
isSealed()245     public boolean isSealed() {
246         synchronized (mLock) {
247             return mSealed;
248         }
249     }
250 
assertPreparedAndNotSealed(String cookie)251     private void assertPreparedAndNotSealed(String cookie) {
252         synchronized (mLock) {
253             if (!mPrepared) {
254                 throw new IllegalStateException(cookie + " before prepared");
255             }
256             if (mSealed) {
257                 throw new SecurityException(cookie + " not allowed after commit");
258             }
259         }
260     }
261 
262     /**
263      * Resolve the actual location where staged data should be written. This
264      * might point at an ASEC mount point, which is why we delay path resolution
265      * until someone actively works with the session.
266      */
resolveStageDir()267     private File resolveStageDir() throws IOException {
268         synchronized (mLock) {
269             if (mResolvedStageDir == null) {
270                 if (stageDir != null) {
271                     mResolvedStageDir = stageDir;
272                 } else {
273                     final String path = PackageHelper.getSdDir(stageCid);
274                     if (path != null) {
275                         mResolvedStageDir = new File(path);
276                     } else {
277                         throw new IOException("Failed to resolve path to container " + stageCid);
278                     }
279                 }
280             }
281             return mResolvedStageDir;
282         }
283     }
284 
285     @Override
setClientProgress(float progress)286     public void setClientProgress(float progress) {
287         synchronized (mLock) {
288             // Always publish first staging movement
289             final boolean forcePublish = (mClientProgress == 0);
290             mClientProgress = progress;
291             computeProgressLocked(forcePublish);
292         }
293     }
294 
295     @Override
addClientProgress(float progress)296     public void addClientProgress(float progress) {
297         synchronized (mLock) {
298             setClientProgress(mClientProgress + progress);
299         }
300     }
301 
computeProgressLocked(boolean forcePublish)302     private void computeProgressLocked(boolean forcePublish) {
303         mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
304                 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
305 
306         // Only publish when meaningful change
307         if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
308             mReportedProgress = mProgress;
309             mCallback.onSessionProgressChanged(this, mProgress);
310         }
311     }
312 
313     @Override
getNames()314     public String[] getNames() {
315         assertPreparedAndNotSealed("getNames");
316         try {
317             return resolveStageDir().list();
318         } catch (IOException e) {
319             throw ExceptionUtils.wrap(e);
320         }
321     }
322 
323     @Override
openWrite(String name, long offsetBytes, long lengthBytes)324     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
325         try {
326             return openWriteInternal(name, offsetBytes, lengthBytes);
327         } catch (IOException e) {
328             throw ExceptionUtils.wrap(e);
329         }
330     }
331 
openWriteInternal(String name, long offsetBytes, long lengthBytes)332     private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
333             throws IOException {
334         // Quick sanity check of state, and allocate a pipe for ourselves. We
335         // then do heavy disk allocation outside the lock, but this open pipe
336         // will block any attempted install transitions.
337         final FileBridge bridge;
338         synchronized (mLock) {
339             assertPreparedAndNotSealed("openWrite");
340 
341             bridge = new FileBridge();
342             mBridges.add(bridge);
343         }
344 
345         try {
346             // Use installer provided name for now; we always rename later
347             if (!FileUtils.isValidExtFilename(name)) {
348                 throw new IllegalArgumentException("Invalid name: " + name);
349             }
350             final File target = new File(resolveStageDir(), name);
351 
352             // TODO: this should delegate to DCS so the system process avoids
353             // holding open FDs into containers.
354             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
355                     O_CREAT | O_WRONLY, 0644);
356             Os.chmod(target.getAbsolutePath(), 0644);
357 
358             // If caller specified a total length, allocate it for them. Free up
359             // cache space to grow, if needed.
360             if (lengthBytes > 0) {
361                 final StructStat stat = Libcore.os.fstat(targetFd);
362                 final long deltaBytes = lengthBytes - stat.st_size;
363                 // Only need to free up space when writing to internal stage
364                 if (stageDir != null && deltaBytes > 0) {
365                     mPm.freeStorage(deltaBytes);
366                 }
367                 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
368             }
369 
370             if (offsetBytes > 0) {
371                 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
372             }
373 
374             bridge.setTargetFile(targetFd);
375             bridge.start();
376             return new ParcelFileDescriptor(bridge.getClientSocket());
377 
378         } catch (ErrnoException e) {
379             throw e.rethrowAsIOException();
380         }
381     }
382 
383     @Override
openRead(String name)384     public ParcelFileDescriptor openRead(String name) {
385         try {
386             return openReadInternal(name);
387         } catch (IOException e) {
388             throw ExceptionUtils.wrap(e);
389         }
390     }
391 
openReadInternal(String name)392     private ParcelFileDescriptor openReadInternal(String name) throws IOException {
393         assertPreparedAndNotSealed("openRead");
394 
395         try {
396             if (!FileUtils.isValidExtFilename(name)) {
397                 throw new IllegalArgumentException("Invalid name: " + name);
398             }
399             final File target = new File(resolveStageDir(), name);
400 
401             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
402             return new ParcelFileDescriptor(targetFd);
403 
404         } catch (ErrnoException e) {
405             throw e.rethrowAsIOException();
406         }
407     }
408 
409     @Override
commit(IntentSender statusReceiver)410     public void commit(IntentSender statusReceiver) {
411         Preconditions.checkNotNull(statusReceiver);
412 
413         final boolean wasSealed;
414         synchronized (mLock) {
415             wasSealed = mSealed;
416             if (!mSealed) {
417                 // Verify that all writers are hands-off
418                 for (FileBridge bridge : mBridges) {
419                     if (!bridge.isClosed()) {
420                         throw new SecurityException("Files still open");
421                     }
422                 }
423                 mSealed = true;
424             }
425 
426             // Client staging is fully done at this point
427             mClientProgress = 1f;
428             computeProgressLocked(true);
429         }
430 
431         if (!wasSealed) {
432             // Persist the fact that we've sealed ourselves to prevent
433             // mutations of any hard links we create. We do this without holding
434             // the session lock, since otherwise it's a lock inversion.
435             mCallback.onSessionSealedBlocking(this);
436         }
437 
438         // This ongoing commit should keep session active, even though client
439         // will probably close their end.
440         mActiveCount.incrementAndGet();
441 
442         final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
443                 statusReceiver, sessionId);
444         mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
445     }
446 
commitLocked()447     private void commitLocked() throws PackageManagerException {
448         if (mDestroyed) {
449             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
450         }
451         if (!mSealed) {
452             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
453         }
454 
455         try {
456             resolveStageDir();
457         } catch (IOException e) {
458             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
459                     "Failed to resolve stage location", e);
460         }
461 
462         // Verify that stage looks sane with respect to existing application.
463         // This currently only ensures packageName, versionCode, and certificate
464         // consistency.
465         validateInstallLocked();
466 
467         Preconditions.checkNotNull(mPackageName);
468         Preconditions.checkNotNull(mSignatures);
469         Preconditions.checkNotNull(mResolvedBaseFile);
470 
471         if (!mPermissionsAccepted) {
472             // User needs to accept permissions; give installer an intent they
473             // can use to involve user.
474             final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
475             intent.setPackage("com.android.packageinstaller");
476             intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
477             try {
478                 mRemoteObserver.onUserActionRequired(intent);
479             } catch (RemoteException ignored) {
480             }
481 
482             // Commit was keeping session marked as active until now; release
483             // that extra refcount so session appears idle.
484             close();
485             return;
486         }
487 
488         if (stageCid != null) {
489             // Figure out the final installed size and resize the container once
490             // and for all. Internally the parser handles straddling between two
491             // locations when inheriting.
492             final long finalSize = calculateInstalledSize();
493             resizeContainer(stageCid, finalSize);
494         }
495 
496         // Inherit any packages and native libraries from existing install that
497         // haven't been overridden.
498         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
499             try {
500                 final List<File> fromFiles = mResolvedInheritedFiles;
501                 final File toDir = resolveStageDir();
502 
503                 if (isLinkPossible(fromFiles, toDir)) {
504                     linkFiles(fromFiles, toDir);
505                 } else {
506                     // TODO: this should delegate to DCS so the system process
507                     // avoids holding open FDs into containers.
508                     copyFiles(fromFiles, toDir);
509                 }
510             } catch (IOException e) {
511                 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
512                         "Failed to inherit existing install", e);
513             }
514         }
515 
516         // TODO: surface more granular state from dexopt
517         mInternalProgress = 0.5f;
518         computeProgressLocked(true);
519 
520         // Unpack native libraries
521         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
522 
523         // Container is ready to go, let's seal it up!
524         if (stageCid != null) {
525             finalizeAndFixContainer(stageCid);
526         }
527 
528         // We've reached point of no return; call into PMS to install the stage.
529         // Regardless of success or failure we always destroy session.
530         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
531             @Override
532             public void onUserActionRequired(Intent intent) {
533                 throw new IllegalStateException();
534             }
535 
536             @Override
537             public void onPackageInstalled(String basePackageName, int returnCode, String msg,
538                     Bundle extras) {
539                 destroyInternal();
540                 dispatchSessionFinished(returnCode, msg, extras);
541             }
542         };
543 
544         final UserHandle user;
545         if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
546             user = UserHandle.ALL;
547         } else {
548             user = new UserHandle(userId);
549         }
550 
551         mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
552                 installerPackageName, installerUid, user);
553     }
554 
555     /**
556      * Validate install by confirming that all application packages are have
557      * consistent package name, version code, and signing certificates.
558      * <p>
559      * Clears and populates {@link #mResolvedBaseFile},
560      * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
561      * <p>
562      * Renames package files in stage to match split names defined inside.
563      * <p>
564      * Note that upgrade compatibility is still performed by
565      * {@link PackageManagerService}.
566      */
validateInstallLocked()567     private void validateInstallLocked() throws PackageManagerException {
568         mPackageName = null;
569         mVersionCode = -1;
570         mSignatures = null;
571 
572         mResolvedBaseFile = null;
573         mResolvedStagedFiles.clear();
574         mResolvedInheritedFiles.clear();
575 
576         final File[] files = mResolvedStageDir.listFiles();
577         if (ArrayUtils.isEmpty(files)) {
578             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
579         }
580 
581         // Verify that all staged packages are internally consistent
582         final ArraySet<String> stagedSplits = new ArraySet<>();
583         for (File file : files) {
584 
585             // Installers can't stage directories, so it's fine to ignore
586             // entries like "lost+found".
587             if (file.isDirectory()) continue;
588 
589             final ApkLite apk;
590             try {
591                 apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
592             } catch (PackageParserException e) {
593                 throw PackageManagerException.from(e);
594             }
595 
596             if (!stagedSplits.add(apk.splitName)) {
597                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
598                         "Split " + apk.splitName + " was defined multiple times");
599             }
600 
601             // Use first package to define unknown values
602             if (mPackageName == null) {
603                 mPackageName = apk.packageName;
604                 mVersionCode = apk.versionCode;
605             }
606             if (mSignatures == null) {
607                 mSignatures = apk.signatures;
608             }
609 
610             assertApkConsistent(String.valueOf(file), apk);
611 
612             // Take this opportunity to enforce uniform naming
613             final String targetName;
614             if (apk.splitName == null) {
615                 targetName = "base.apk";
616             } else {
617                 targetName = "split_" + apk.splitName + ".apk";
618             }
619             if (!FileUtils.isValidExtFilename(targetName)) {
620                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
621                         "Invalid filename: " + targetName);
622             }
623 
624             final File targetFile = new File(mResolvedStageDir, targetName);
625             if (!file.equals(targetFile)) {
626                 file.renameTo(targetFile);
627             }
628 
629             // Base is coming from session
630             if (apk.splitName == null) {
631                 mResolvedBaseFile = targetFile;
632             }
633 
634             mResolvedStagedFiles.add(targetFile);
635         }
636 
637         if (params.mode == SessionParams.MODE_FULL_INSTALL) {
638             // Full installs must include a base package
639             if (!stagedSplits.contains(null)) {
640                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
641                         "Full install must include a base package");
642             }
643 
644         } else {
645             // Partial installs must be consistent with existing install
646             final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
647             if (app == null) {
648                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
649                         "Missing existing base package for " + mPackageName);
650             }
651 
652             final PackageLite existing;
653             final ApkLite existingBase;
654             try {
655                 existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
656                 existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
657                         PackageParser.PARSE_COLLECT_CERTIFICATES);
658             } catch (PackageParserException e) {
659                 throw PackageManagerException.from(e);
660             }
661 
662             assertApkConsistent("Existing base", existingBase);
663 
664             // Inherit base if not overridden
665             if (mResolvedBaseFile == null) {
666                 mResolvedBaseFile = new File(app.getBaseCodePath());
667                 mResolvedInheritedFiles.add(mResolvedBaseFile);
668             }
669 
670             // Inherit splits if not overridden
671             if (!ArrayUtils.isEmpty(existing.splitNames)) {
672                 for (int i = 0; i < existing.splitNames.length; i++) {
673                     final String splitName = existing.splitNames[i];
674                     final File splitFile = new File(existing.splitCodePaths[i]);
675 
676                     if (!stagedSplits.contains(splitName)) {
677                         mResolvedInheritedFiles.add(splitFile);
678                     }
679                 }
680             }
681         }
682     }
683 
assertApkConsistent(String tag, ApkLite apk)684     private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
685         if (!mPackageName.equals(apk.packageName)) {
686             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
687                     + apk.packageName + " inconsistent with " + mPackageName);
688         }
689         if (mVersionCode != apk.versionCode) {
690             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
691                     + " version code " + apk.versionCode + " inconsistent with "
692                     + mVersionCode);
693         }
694         if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
695             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
696                     tag + " signatures are inconsistent");
697         }
698     }
699 
700     /**
701      * Calculate the final install footprint size, combining both staged and
702      * existing APKs together and including unpacked native code from both.
703      */
calculateInstalledSize()704     private long calculateInstalledSize() throws PackageManagerException {
705         Preconditions.checkNotNull(mResolvedBaseFile);
706 
707         final ApkLite baseApk;
708         try {
709             baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
710         } catch (PackageParserException e) {
711             throw PackageManagerException.from(e);
712         }
713 
714         final List<String> splitPaths = new ArrayList<>();
715         for (File file : mResolvedStagedFiles) {
716             if (mResolvedBaseFile.equals(file)) continue;
717             splitPaths.add(file.getAbsolutePath());
718         }
719         for (File file : mResolvedInheritedFiles) {
720             if (mResolvedBaseFile.equals(file)) continue;
721             splitPaths.add(file.getAbsolutePath());
722         }
723 
724         // This is kind of hacky; we're creating a half-parsed package that is
725         // straddled between the inherited and staged APKs.
726         final PackageLite pkg = new PackageLite(null, baseApk, null,
727                 splitPaths.toArray(new String[splitPaths.size()]), null);
728         final boolean isForwardLocked =
729                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
730 
731         try {
732             return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
733         } catch (IOException e) {
734             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
735                     "Failed to calculate install size", e);
736         }
737     }
738 
739     /**
740      * Determine if creating hard links between source and destination is
741      * possible. That is, do they all live on the same underlying device.
742      */
isLinkPossible(List<File> fromFiles, File toDir)743     private boolean isLinkPossible(List<File> fromFiles, File toDir) {
744         try {
745             final StructStat toStat = Os.stat(toDir.getAbsolutePath());
746             for (File fromFile : fromFiles) {
747                 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
748                 if (fromStat.st_dev != toStat.st_dev) {
749                     return false;
750                 }
751             }
752         } catch (ErrnoException e) {
753             Slog.w(TAG, "Failed to detect if linking possible: " + e);
754             return false;
755         }
756         return true;
757     }
758 
linkFiles(List<File> fromFiles, File toDir)759     private static void linkFiles(List<File> fromFiles, File toDir) throws IOException {
760         for (File fromFile : fromFiles) {
761             final File toFile = new File(toDir, fromFile.getName());
762             try {
763                 if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile);
764                 Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
765             } catch (ErrnoException e) {
766                 throw new IOException("Failed to link " + fromFile + " to " + toFile, e);
767             }
768         }
769         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
770     }
771 
copyFiles(List<File> fromFiles, File toDir)772     private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
773         // Remove any partial files from previous attempt
774         for (File file : toDir.listFiles()) {
775             if (file.getName().endsWith(".tmp")) {
776                 file.delete();
777             }
778         }
779 
780         for (File fromFile : fromFiles) {
781             final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
782             if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
783             if (!FileUtils.copyFile(fromFile, tmpFile)) {
784                 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
785             }
786             try {
787                 Os.chmod(tmpFile.getAbsolutePath(), 0644);
788             } catch (ErrnoException e) {
789                 throw new IOException("Failed to chmod " + tmpFile);
790             }
791             final File toFile = new File(toDir, fromFile.getName());
792             if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
793             if (!tmpFile.renameTo(toFile)) {
794                 throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
795             }
796         }
797         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
798     }
799 
extractNativeLibraries(File packageDir, String abiOverride)800     private static void extractNativeLibraries(File packageDir, String abiOverride)
801             throws PackageManagerException {
802         // Always start from a clean slate
803         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
804         NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
805 
806         NativeLibraryHelper.Handle handle = null;
807         try {
808             handle = NativeLibraryHelper.Handle.create(packageDir);
809             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
810                     abiOverride);
811             if (res != PackageManager.INSTALL_SUCCEEDED) {
812                 throw new PackageManagerException(res,
813                         "Failed to extract native libraries, res=" + res);
814             }
815         } catch (IOException e) {
816             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
817                     "Failed to extract native libraries", e);
818         } finally {
819             IoUtils.closeQuietly(handle);
820         }
821     }
822 
resizeContainer(String cid, long targetSize)823     private static void resizeContainer(String cid, long targetSize)
824             throws PackageManagerException {
825         String path = PackageHelper.getSdDir(cid);
826         if (path == null) {
827             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
828                     "Failed to find mounted " + cid);
829         }
830 
831         final long currentSize = new File(path).getTotalSpace();
832         if (currentSize > targetSize) {
833             Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
834                     + targetSize + "; skipping resize");
835             return;
836         }
837 
838         if (!PackageHelper.unMountSdDir(cid)) {
839             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
840                     "Failed to unmount " + cid + " before resize");
841         }
842 
843         if (!PackageHelper.resizeSdDir(targetSize, cid,
844                 PackageManagerService.getEncryptKey())) {
845             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
846                     "Failed to resize " + cid + " to " + targetSize + " bytes");
847         }
848 
849         path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
850                 Process.SYSTEM_UID, false);
851         if (path == null) {
852             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
853                     "Failed to mount " + cid + " after resize");
854         }
855     }
856 
finalizeAndFixContainer(String cid)857     private void finalizeAndFixContainer(String cid) throws PackageManagerException {
858         if (!PackageHelper.finalizeSdDir(cid)) {
859             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
860                     "Failed to finalize container " + cid);
861         }
862 
863         final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
864                 UserHandle.USER_OWNER);
865         final int gid = UserHandle.getSharedAppGid(uid);
866         if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
867             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
868                     "Failed to fix permissions on container " + cid);
869         }
870     }
871 
setPermissionsResult(boolean accepted)872     void setPermissionsResult(boolean accepted) {
873         if (!mSealed) {
874             throw new SecurityException("Must be sealed to accept permissions");
875         }
876 
877         if (accepted) {
878             // Mark and kick off another install pass
879             mPermissionsAccepted = true;
880             mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
881         } else {
882             destroyInternal();
883             dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
884         }
885     }
886 
open()887     public void open() throws IOException {
888         if (mActiveCount.getAndIncrement() == 0) {
889             mCallback.onSessionActiveChanged(this, true);
890         }
891 
892         synchronized (mLock) {
893             if (!mPrepared) {
894                 if (stageDir != null) {
895                     prepareInternalStageDir(stageDir);
896                 } else if (stageCid != null) {
897                     prepareExternalStageCid(stageCid, params.sizeBytes);
898 
899                     // TODO: deliver more granular progress for ASEC allocation
900                     mInternalProgress = 0.25f;
901                     computeProgressLocked(true);
902                 } else {
903                     throw new IllegalArgumentException(
904                             "Exactly one of stageDir or stageCid stage must be set");
905                 }
906 
907                 mPrepared = true;
908                 mCallback.onSessionPrepared(this);
909             }
910         }
911     }
912 
913     @Override
close()914     public void close() {
915         if (mActiveCount.decrementAndGet() == 0) {
916             mCallback.onSessionActiveChanged(this, false);
917         }
918     }
919 
920     @Override
abandon()921     public void abandon() {
922         destroyInternal();
923         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
924     }
925 
dispatchSessionFinished(int returnCode, String msg, Bundle extras)926     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
927         mFinalStatus = returnCode;
928         mFinalMessage = msg;
929 
930         if (mRemoteObserver != null) {
931             try {
932                 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
933             } catch (RemoteException ignored) {
934             }
935         }
936 
937         final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
938         mCallback.onSessionFinished(this, success);
939     }
940 
destroyInternal()941     private void destroyInternal() {
942         synchronized (mLock) {
943             mSealed = true;
944             mDestroyed = true;
945 
946             // Force shut down all bridges
947             for (FileBridge bridge : mBridges) {
948                 bridge.forceClose();
949             }
950         }
951         if (stageDir != null) {
952             FileUtils.deleteContents(stageDir);
953             stageDir.delete();
954         }
955         if (stageCid != null) {
956             PackageHelper.destroySdDir(stageCid);
957         }
958     }
959 
dump(IndentingPrintWriter pw)960     void dump(IndentingPrintWriter pw) {
961         synchronized (mLock) {
962             dumpLocked(pw);
963         }
964     }
965 
dumpLocked(IndentingPrintWriter pw)966     private void dumpLocked(IndentingPrintWriter pw) {
967         pw.println("Session " + sessionId + ":");
968         pw.increaseIndent();
969 
970         pw.printPair("userId", userId);
971         pw.printPair("installerPackageName", installerPackageName);
972         pw.printPair("installerUid", installerUid);
973         pw.printPair("createdMillis", createdMillis);
974         pw.printPair("stageDir", stageDir);
975         pw.printPair("stageCid", stageCid);
976         pw.println();
977 
978         params.dump(pw);
979 
980         pw.printPair("mClientProgress", mClientProgress);
981         pw.printPair("mProgress", mProgress);
982         pw.printPair("mSealed", mSealed);
983         pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
984         pw.printPair("mDestroyed", mDestroyed);
985         pw.printPair("mBridges", mBridges.size());
986         pw.printPair("mFinalStatus", mFinalStatus);
987         pw.printPair("mFinalMessage", mFinalMessage);
988         pw.println();
989 
990         pw.decreaseIndent();
991     }
992 }
993