Home | History | Annotate | Download | only in pm
      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 
     28 import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
     29 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
     30 
     31 import android.app.admin.DevicePolicyManager;
     32 import android.content.Context;
     33 import android.content.Intent;
     34 import android.content.IntentSender;
     35 import android.content.pm.ApplicationInfo;
     36 import android.content.pm.IPackageInstallObserver2;
     37 import android.content.pm.IPackageInstallerSession;
     38 import android.content.pm.PackageInfo;
     39 import android.content.pm.PackageInstaller;
     40 import android.content.pm.PackageInstaller.SessionInfo;
     41 import android.content.pm.PackageInstaller.SessionParams;
     42 import android.content.pm.PackageManager;
     43 import android.content.pm.PackageParser;
     44 import android.content.pm.PackageParser.ApkLite;
     45 import android.content.pm.PackageParser.PackageLite;
     46 import android.content.pm.PackageParser.PackageParserException;
     47 import android.content.pm.Signature;
     48 import android.os.Binder;
     49 import android.os.Bundle;
     50 import android.os.FileBridge;
     51 import android.os.FileUtils;
     52 import android.os.Handler;
     53 import android.os.Looper;
     54 import android.os.Message;
     55 import android.os.ParcelFileDescriptor;
     56 import android.os.Process;
     57 import android.os.RemoteException;
     58 import android.os.RevocableFileDescriptor;
     59 import android.os.UserHandle;
     60 import android.os.storage.StorageManager;
     61 import android.system.ErrnoException;
     62 import android.system.Os;
     63 import android.system.OsConstants;
     64 import android.system.StructStat;
     65 import android.text.TextUtils;
     66 import android.util.ArraySet;
     67 import android.util.ExceptionUtils;
     68 import android.util.MathUtils;
     69 import android.util.Slog;
     70 
     71 import com.android.internal.annotations.GuardedBy;
     72 import com.android.internal.content.NativeLibraryHelper;
     73 import com.android.internal.content.PackageHelper;
     74 import com.android.internal.util.ArrayUtils;
     75 import com.android.internal.util.IndentingPrintWriter;
     76 import com.android.internal.util.Preconditions;
     77 import com.android.server.pm.Installer.InstallerException;
     78 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
     79 
     80 import libcore.io.IoUtils;
     81 import libcore.io.Libcore;
     82 
     83 import java.io.File;
     84 import java.io.FileDescriptor;
     85 import java.io.FileFilter;
     86 import java.io.IOException;
     87 import java.security.cert.Certificate;
     88 import java.util.ArrayList;
     89 import java.util.Arrays;
     90 import java.util.List;
     91 import java.util.concurrent.atomic.AtomicInteger;
     92 
     93 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     94     private static final String TAG = "PackageInstaller";
     95     private static final boolean LOGD = true;
     96     private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed";
     97 
     98     private static final int MSG_COMMIT = 0;
     99 
    100     // TODO: enforce INSTALL_ALLOW_TEST
    101     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
    102 
    103     private final PackageInstallerService.InternalCallback mCallback;
    104     private final Context mContext;
    105     private final PackageManagerService mPm;
    106     private final Handler mHandler;
    107     private final boolean mIsInstallerDeviceOwner;
    108 
    109     final int sessionId;
    110     final int userId;
    111     final String installerPackageName;
    112     final int installerUid;
    113     final SessionParams params;
    114     final long createdMillis;
    115     final int defaultContainerGid;
    116 
    117     /** Staging location where client data is written. */
    118     final File stageDir;
    119     final String stageCid;
    120 
    121     private final AtomicInteger mActiveCount = new AtomicInteger();
    122 
    123     private final Object mLock = new Object();
    124 
    125     @GuardedBy("mLock")
    126     private float mClientProgress = 0;
    127     @GuardedBy("mLock")
    128     private float mInternalProgress = 0;
    129 
    130     @GuardedBy("mLock")
    131     private float mProgress = 0;
    132     @GuardedBy("mLock")
    133     private float mReportedProgress = -1;
    134 
    135     @GuardedBy("mLock")
    136     private boolean mPrepared = false;
    137     @GuardedBy("mLock")
    138     private boolean mSealed = false;
    139     @GuardedBy("mLock")
    140     private boolean mPermissionsAccepted = false;
    141     @GuardedBy("mLock")
    142     private boolean mRelinquished = false;
    143     @GuardedBy("mLock")
    144     private boolean mDestroyed = false;
    145 
    146     private int mFinalStatus;
    147     private String mFinalMessage;
    148 
    149     @GuardedBy("mLock")
    150     private final ArrayList<RevocableFileDescriptor> mFds = new ArrayList<>();
    151     @GuardedBy("mLock")
    152     private final ArrayList<FileBridge> mBridges = new ArrayList<>();
    153 
    154     @GuardedBy("mLock")
    155     private IPackageInstallObserver2 mRemoteObserver;
    156 
    157     /** Fields derived from commit parsing */
    158     private String mPackageName;
    159     private int mVersionCode;
    160     private Signature[] mSignatures;
    161     private Certificate[][] mCertificates;
    162 
    163     /**
    164      * Path to the validated base APK for this session, which may point at an
    165      * APK inside the session (when the session defines the base), or it may
    166      * point at the existing base APK (when adding splits to an existing app).
    167      * <p>
    168      * This is used when confirming permissions, since we can't fully stage the
    169      * session inside an ASEC before confirming with user.
    170      */
    171     @GuardedBy("mLock")
    172     private File mResolvedBaseFile;
    173 
    174     @GuardedBy("mLock")
    175     private File mResolvedStageDir;
    176 
    177     @GuardedBy("mLock")
    178     private final List<File> mResolvedStagedFiles = new ArrayList<>();
    179     @GuardedBy("mLock")
    180     private final List<File> mResolvedInheritedFiles = new ArrayList<>();
    181     @GuardedBy("mLock")
    182     private final List<String> mResolvedInstructionSets = new ArrayList<>();
    183     @GuardedBy("mLock")
    184     private File mInheritedFilesBase;
    185 
    186     private static final FileFilter sAddedFilter = new FileFilter() {
    187         @Override
    188         public boolean accept(File file) {
    189             // Installers can't stage directories, so it's fine to ignore
    190             // entries like "lost+found".
    191             if (file.isDirectory()) return false;
    192             if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
    193             return true;
    194         }
    195     };
    196     private static final FileFilter sRemovedFilter = new FileFilter() {
    197         @Override
    198         public boolean accept(File file) {
    199             if (file.isDirectory()) return false;
    200             if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
    201             return true;
    202         }
    203     };
    204 
    205     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
    206         @Override
    207         public boolean handleMessage(Message msg) {
    208             // Cache package manager data without the lock held
    209             final PackageInfo pkgInfo = mPm.getPackageInfo(
    210                     params.appPackageName, PackageManager.GET_SIGNATURES
    211                             | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
    212             final ApplicationInfo appInfo = mPm.getApplicationInfo(
    213                     params.appPackageName, 0, userId);
    214 
    215             synchronized (mLock) {
    216                 if (msg.obj != null) {
    217                     mRemoteObserver = (IPackageInstallObserver2) msg.obj;
    218                 }
    219 
    220                 try {
    221                     commitLocked(pkgInfo, appInfo);
    222                 } catch (PackageManagerException e) {
    223                     final String completeMsg = ExceptionUtils.getCompleteMessage(e);
    224                     Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
    225                     destroyInternal();
    226                     dispatchSessionFinished(e.error, completeMsg, null);
    227                 }
    228 
    229                 return true;
    230             }
    231         }
    232     };
    233 
    234     public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
    235             Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
    236             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
    237             File stageDir, String stageCid, boolean prepared, boolean sealed) {
    238         mCallback = callback;
    239         mContext = context;
    240         mPm = pm;
    241         mHandler = new Handler(looper, mHandlerCallback);
    242 
    243         this.sessionId = sessionId;
    244         this.userId = userId;
    245         this.installerPackageName = installerPackageName;
    246         this.installerUid = installerUid;
    247         this.params = params;
    248         this.createdMillis = createdMillis;
    249         this.stageDir = stageDir;
    250         this.stageCid = stageCid;
    251 
    252         if ((stageDir == null) == (stageCid == null)) {
    253             throw new IllegalArgumentException(
    254                     "Exactly one of stageDir or stageCid stage must be set");
    255         }
    256 
    257         mPrepared = prepared;
    258         mSealed = sealed;
    259 
    260         // Device owners are allowed to silently install packages, so the permission check is
    261         // waived if the installer is the device owner.
    262         DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
    263                 Context.DEVICE_POLICY_SERVICE);
    264         final boolean isPermissionGranted =
    265                 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
    266                         == PackageManager.PERMISSION_GRANTED);
    267         final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
    268         final boolean forcePermissionPrompt =
    269                 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
    270         mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
    271                 installerPackageName);
    272         if ((isPermissionGranted
    273                         || isInstallerRoot
    274                         || mIsInstallerDeviceOwner)
    275                 && !forcePermissionPrompt) {
    276             mPermissionsAccepted = true;
    277         } else {
    278             mPermissionsAccepted = false;
    279         }
    280         final long identity = Binder.clearCallingIdentity();
    281         try {
    282             final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
    283                     PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
    284             defaultContainerGid = UserHandle.getSharedAppGid(uid);
    285         } finally {
    286             Binder.restoreCallingIdentity(identity);
    287         }
    288     }
    289 
    290     public SessionInfo generateInfo() {
    291         final SessionInfo info = new SessionInfo();
    292         synchronized (mLock) {
    293             info.sessionId = sessionId;
    294             info.installerPackageName = installerPackageName;
    295             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
    296                     mResolvedBaseFile.getAbsolutePath() : null;
    297             info.progress = mProgress;
    298             info.sealed = mSealed;
    299             info.active = mActiveCount.get() > 0;
    300 
    301             info.mode = params.mode;
    302             info.installReason = params.installReason;
    303             info.sizeBytes = params.sizeBytes;
    304             info.appPackageName = params.appPackageName;
    305             info.appIcon = params.appIcon;
    306             info.appLabel = params.appLabel;
    307         }
    308         return info;
    309     }
    310 
    311     public boolean isPrepared() {
    312         synchronized (mLock) {
    313             return mPrepared;
    314         }
    315     }
    316 
    317     public boolean isSealed() {
    318         synchronized (mLock) {
    319             return mSealed;
    320         }
    321     }
    322 
    323     private void assertPreparedAndNotSealed(String cookie) {
    324         synchronized (mLock) {
    325             if (!mPrepared) {
    326                 throw new IllegalStateException(cookie + " before prepared");
    327             }
    328             if (mSealed) {
    329                 throw new SecurityException(cookie + " not allowed after commit");
    330             }
    331         }
    332     }
    333 
    334     /**
    335      * Resolve the actual location where staged data should be written. This
    336      * might point at an ASEC mount point, which is why we delay path resolution
    337      * until someone actively works with the session.
    338      */
    339     private File resolveStageDir() throws IOException {
    340         synchronized (mLock) {
    341             if (mResolvedStageDir == null) {
    342                 if (stageDir != null) {
    343                     mResolvedStageDir = stageDir;
    344                 } else {
    345                     final String path = PackageHelper.getSdDir(stageCid);
    346                     if (path != null) {
    347                         mResolvedStageDir = new File(path);
    348                     } else {
    349                         throw new IOException("Failed to resolve path to container " + stageCid);
    350                     }
    351                 }
    352             }
    353             return mResolvedStageDir;
    354         }
    355     }
    356 
    357     @Override
    358     public void setClientProgress(float progress) {
    359         synchronized (mLock) {
    360             // Always publish first staging movement
    361             final boolean forcePublish = (mClientProgress == 0);
    362             mClientProgress = progress;
    363             computeProgressLocked(forcePublish);
    364         }
    365     }
    366 
    367     @Override
    368     public void addClientProgress(float progress) {
    369         synchronized (mLock) {
    370             setClientProgress(mClientProgress + progress);
    371         }
    372     }
    373 
    374     private void computeProgressLocked(boolean forcePublish) {
    375         mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
    376                 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
    377 
    378         // Only publish when meaningful change
    379         if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
    380             mReportedProgress = mProgress;
    381             mCallback.onSessionProgressChanged(this, mProgress);
    382         }
    383     }
    384 
    385     @Override
    386     public String[] getNames() {
    387         assertPreparedAndNotSealed("getNames");
    388         try {
    389             return resolveStageDir().list();
    390         } catch (IOException e) {
    391             throw ExceptionUtils.wrap(e);
    392         }
    393     }
    394 
    395     @Override
    396     public void removeSplit(String splitName) {
    397         if (TextUtils.isEmpty(params.appPackageName)) {
    398             throw new IllegalStateException("Must specify package name to remove a split");
    399         }
    400         try {
    401             createRemoveSplitMarker(splitName);
    402         } catch (IOException e) {
    403             throw ExceptionUtils.wrap(e);
    404         }
    405     }
    406 
    407     private void createRemoveSplitMarker(String splitName) throws IOException {
    408         try {
    409             final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
    410             if (!FileUtils.isValidExtFilename(markerName)) {
    411                 throw new IllegalArgumentException("Invalid marker: " + markerName);
    412             }
    413             final File target = new File(resolveStageDir(), markerName);
    414             target.createNewFile();
    415             Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
    416         } catch (ErrnoException e) {
    417             throw e.rethrowAsIOException();
    418         }
    419     }
    420 
    421     @Override
    422     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
    423         try {
    424             return openWriteInternal(name, offsetBytes, lengthBytes);
    425         } catch (IOException e) {
    426             throw ExceptionUtils.wrap(e);
    427         }
    428     }
    429 
    430     private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
    431             throws IOException {
    432         // Quick sanity check of state, and allocate a pipe for ourselves. We
    433         // then do heavy disk allocation outside the lock, but this open pipe
    434         // will block any attempted install transitions.
    435         final RevocableFileDescriptor fd;
    436         final FileBridge bridge;
    437         synchronized (mLock) {
    438             assertPreparedAndNotSealed("openWrite");
    439 
    440             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
    441                 fd = new RevocableFileDescriptor();
    442                 bridge = null;
    443                 mFds.add(fd);
    444             } else {
    445                 fd = null;
    446                 bridge = new FileBridge();
    447                 mBridges.add(bridge);
    448             }
    449         }
    450 
    451         try {
    452             // Use installer provided name for now; we always rename later
    453             if (!FileUtils.isValidExtFilename(name)) {
    454                 throw new IllegalArgumentException("Invalid name: " + name);
    455             }
    456             final File target;
    457             final long identity = Binder.clearCallingIdentity();
    458             try {
    459                 target = new File(resolveStageDir(), name);
    460             } finally {
    461                 Binder.restoreCallingIdentity(identity);
    462             }
    463 
    464             // TODO: this should delegate to DCS so the system process avoids
    465             // holding open FDs into containers.
    466             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
    467                     O_CREAT | O_WRONLY, 0644);
    468             Os.chmod(target.getAbsolutePath(), 0644);
    469 
    470             // If caller specified a total length, allocate it for them. Free up
    471             // cache space to grow, if needed.
    472             if (stageDir != null && lengthBytes > 0) {
    473                 mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
    474                         PackageHelper.translateAllocateFlags(params.installFlags));
    475             }
    476 
    477             if (offsetBytes > 0) {
    478                 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
    479             }
    480 
    481             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
    482                 fd.init(mContext, targetFd);
    483                 return fd.getRevocableFileDescriptor();
    484             } else {
    485                 bridge.setTargetFile(targetFd);
    486                 bridge.start();
    487                 return new ParcelFileDescriptor(bridge.getClientSocket());
    488             }
    489 
    490         } catch (ErrnoException e) {
    491             throw e.rethrowAsIOException();
    492         }
    493     }
    494 
    495     @Override
    496     public ParcelFileDescriptor openRead(String name) {
    497         try {
    498             return openReadInternal(name);
    499         } catch (IOException e) {
    500             throw ExceptionUtils.wrap(e);
    501         }
    502     }
    503 
    504     private ParcelFileDescriptor openReadInternal(String name) throws IOException {
    505         assertPreparedAndNotSealed("openRead");
    506 
    507         try {
    508             if (!FileUtils.isValidExtFilename(name)) {
    509                 throw new IllegalArgumentException("Invalid name: " + name);
    510             }
    511             final File target = new File(resolveStageDir(), name);
    512 
    513             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
    514             return new ParcelFileDescriptor(targetFd);
    515 
    516         } catch (ErrnoException e) {
    517             throw e.rethrowAsIOException();
    518         }
    519     }
    520 
    521     @Override
    522     public void commit(IntentSender statusReceiver) {
    523         Preconditions.checkNotNull(statusReceiver);
    524 
    525         final boolean wasSealed;
    526         synchronized (mLock) {
    527             wasSealed = mSealed;
    528             if (!mSealed) {
    529                 // Verify that all writers are hands-off
    530                 for (RevocableFileDescriptor fd : mFds) {
    531                     if (!fd.isRevoked()) {
    532                         throw new SecurityException("Files still open");
    533                     }
    534                 }
    535                 for (FileBridge bridge : mBridges) {
    536                     if (!bridge.isClosed()) {
    537                         throw new SecurityException("Files still open");
    538                     }
    539                 }
    540                 mSealed = true;
    541             }
    542 
    543             // Client staging is fully done at this point
    544             mClientProgress = 1f;
    545             computeProgressLocked(true);
    546         }
    547 
    548         if (!wasSealed) {
    549             // Persist the fact that we've sealed ourselves to prevent
    550             // mutations of any hard links we create. We do this without holding
    551             // the session lock, since otherwise it's a lock inversion.
    552             mCallback.onSessionSealedBlocking(this);
    553         }
    554 
    555         // This ongoing commit should keep session active, even though client
    556         // will probably close their end.
    557         mActiveCount.incrementAndGet();
    558 
    559         final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
    560                 statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
    561         mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
    562     }
    563 
    564     private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
    565             throws PackageManagerException {
    566         if (mDestroyed) {
    567             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
    568         }
    569         if (!mSealed) {
    570             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
    571         }
    572 
    573         try {
    574             resolveStageDir();
    575         } catch (IOException e) {
    576             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
    577                     "Failed to resolve stage location", e);
    578         }
    579 
    580         // Verify that stage looks sane with respect to existing application.
    581         // This currently only ensures packageName, versionCode, and certificate
    582         // consistency.
    583         validateInstallLocked(pkgInfo, appInfo);
    584 
    585         Preconditions.checkNotNull(mPackageName);
    586         Preconditions.checkNotNull(mSignatures);
    587         Preconditions.checkNotNull(mResolvedBaseFile);
    588 
    589         if (!mPermissionsAccepted) {
    590             // User needs to accept permissions; give installer an intent they
    591             // can use to involve user.
    592             final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
    593             intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
    594             intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
    595             try {
    596                 mRemoteObserver.onUserActionRequired(intent);
    597             } catch (RemoteException ignored) {
    598             }
    599 
    600             // Commit was keeping session marked as active until now; release
    601             // that extra refcount so session appears idle.
    602             close();
    603             return;
    604         }
    605 
    606         if (stageCid != null) {
    607             // Figure out the final installed size and resize the container once
    608             // and for all. Internally the parser handles straddling between two
    609             // locations when inheriting.
    610             final long finalSize = calculateInstalledSize();
    611             resizeContainer(stageCid, finalSize);
    612         }
    613 
    614         // Inherit any packages and native libraries from existing install that
    615         // haven't been overridden.
    616         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
    617             try {
    618                 final List<File> fromFiles = mResolvedInheritedFiles;
    619                 final File toDir = resolveStageDir();
    620 
    621                 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
    622                 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
    623                     throw new IllegalStateException("mInheritedFilesBase == null");
    624                 }
    625 
    626                 if (isLinkPossible(fromFiles, toDir)) {
    627                     if (!mResolvedInstructionSets.isEmpty()) {
    628                         final File oatDir = new File(toDir, "oat");
    629                         createOatDirs(mResolvedInstructionSets, oatDir);
    630                     }
    631                     linkFiles(fromFiles, toDir, mInheritedFilesBase);
    632                 } else {
    633                     // TODO: this should delegate to DCS so the system process
    634                     // avoids holding open FDs into containers.
    635                     copyFiles(fromFiles, toDir);
    636                 }
    637             } catch (IOException e) {
    638                 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
    639                         "Failed to inherit existing install", e);
    640             }
    641         }
    642 
    643         // TODO: surface more granular state from dexopt
    644         mInternalProgress = 0.5f;
    645         computeProgressLocked(true);
    646 
    647         // Unpack native libraries
    648         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
    649 
    650         // Container is ready to go, let's seal it up!
    651         if (stageCid != null) {
    652             finalizeAndFixContainer(stageCid);
    653         }
    654 
    655         // We've reached point of no return; call into PMS to install the stage.
    656         // Regardless of success or failure we always destroy session.
    657         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
    658             @Override
    659             public void onUserActionRequired(Intent intent) {
    660                 throw new IllegalStateException();
    661             }
    662 
    663             @Override
    664             public void onPackageInstalled(String basePackageName, int returnCode, String msg,
    665                     Bundle extras) {
    666                 destroyInternal();
    667                 dispatchSessionFinished(returnCode, msg, extras);
    668             }
    669         };
    670 
    671         final UserHandle user;
    672         if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
    673             user = UserHandle.ALL;
    674         } else {
    675             user = new UserHandle(userId);
    676         }
    677 
    678         mRelinquished = true;
    679         mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
    680                 installerPackageName, installerUid, user, mCertificates);
    681     }
    682 
    683     /**
    684      * Validate install by confirming that all application packages are have
    685      * consistent package name, version code, and signing certificates.
    686      * <p>
    687      * Clears and populates {@link #mResolvedBaseFile},
    688      * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
    689      * <p>
    690      * Renames package files in stage to match split names defined inside.
    691      * <p>
    692      * Note that upgrade compatibility is still performed by
    693      * {@link PackageManagerService}.
    694      */
    695     private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
    696             throws PackageManagerException {
    697         mPackageName = null;
    698         mVersionCode = -1;
    699         mSignatures = null;
    700 
    701         mResolvedBaseFile = null;
    702         mResolvedStagedFiles.clear();
    703         mResolvedInheritedFiles.clear();
    704 
    705         final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter);
    706         final List<String> removeSplitList = new ArrayList<>();
    707         if (!ArrayUtils.isEmpty(removedFiles)) {
    708             for (File removedFile : removedFiles) {
    709                 final String fileName = removedFile.getName();
    710                 final String splitName = fileName.substring(
    711                         0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length());
    712                 removeSplitList.add(splitName);
    713             }
    714         }
    715 
    716         final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
    717         if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
    718             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
    719         }
    720         // Verify that all staged packages are internally consistent
    721         final ArraySet<String> stagedSplits = new ArraySet<>();
    722         for (File addedFile : addedFiles) {
    723             final ApkLite apk;
    724             try {
    725                 int flags = PackageParser.PARSE_COLLECT_CERTIFICATES;
    726                 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
    727                     flags |= PackageParser.PARSE_IS_EPHEMERAL;
    728                 }
    729                 apk = PackageParser.parseApkLite(addedFile, flags);
    730             } catch (PackageParserException e) {
    731                 throw PackageManagerException.from(e);
    732             }
    733 
    734             if (!stagedSplits.add(apk.splitName)) {
    735                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    736                         "Split " + apk.splitName + " was defined multiple times");
    737             }
    738 
    739             // Use first package to define unknown values
    740             if (mPackageName == null) {
    741                 mPackageName = apk.packageName;
    742                 mVersionCode = apk.versionCode;
    743             }
    744             if (mSignatures == null) {
    745                 mSignatures = apk.signatures;
    746                 mCertificates = apk.certificates;
    747             }
    748 
    749             assertApkConsistent(String.valueOf(addedFile), apk);
    750 
    751             // Take this opportunity to enforce uniform naming
    752             final String targetName;
    753             if (apk.splitName == null) {
    754                 targetName = "base.apk";
    755             } else {
    756                 targetName = "split_" + apk.splitName + ".apk";
    757             }
    758             if (!FileUtils.isValidExtFilename(targetName)) {
    759                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    760                         "Invalid filename: " + targetName);
    761             }
    762 
    763             final File targetFile = new File(mResolvedStageDir, targetName);
    764             if (!addedFile.equals(targetFile)) {
    765                 addedFile.renameTo(targetFile);
    766             }
    767 
    768             // Base is coming from session
    769             if (apk.splitName == null) {
    770                 mResolvedBaseFile = targetFile;
    771             }
    772 
    773             mResolvedStagedFiles.add(targetFile);
    774         }
    775 
    776         if (removeSplitList.size() > 0) {
    777             // validate split names marked for removal
    778             for (String splitName : removeSplitList) {
    779                 if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) {
    780                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    781                             "Split not found: " + splitName);
    782                 }
    783             }
    784 
    785             // ensure we've got appropriate package name, version code and signatures
    786             if (mPackageName == null) {
    787                 mPackageName = pkgInfo.packageName;
    788                 mVersionCode = pkgInfo.versionCode;
    789             }
    790             if (mSignatures == null) {
    791                 mSignatures = pkgInfo.signatures;
    792             }
    793         }
    794 
    795         if (params.mode == SessionParams.MODE_FULL_INSTALL) {
    796             // Full installs must include a base package
    797             if (!stagedSplits.contains(null)) {
    798                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    799                         "Full install must include a base package");
    800             }
    801 
    802         } else {
    803             // Partial installs must be consistent with existing install
    804             if (appInfo == null) {
    805                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    806                         "Missing existing base package for " + mPackageName);
    807             }
    808 
    809             final PackageLite existing;
    810             final ApkLite existingBase;
    811             try {
    812                 existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0);
    813                 existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()),
    814                         PackageParser.PARSE_COLLECT_CERTIFICATES);
    815             } catch (PackageParserException e) {
    816                 throw PackageManagerException.from(e);
    817             }
    818 
    819             assertApkConsistent("Existing base", existingBase);
    820 
    821             // Inherit base if not overridden
    822             if (mResolvedBaseFile == null) {
    823                 mResolvedBaseFile = new File(appInfo.getBaseCodePath());
    824                 mResolvedInheritedFiles.add(mResolvedBaseFile);
    825             }
    826 
    827             // Inherit splits if not overridden
    828             if (!ArrayUtils.isEmpty(existing.splitNames)) {
    829                 for (int i = 0; i < existing.splitNames.length; i++) {
    830                     final String splitName = existing.splitNames[i];
    831                     final File splitFile = new File(existing.splitCodePaths[i]);
    832                     final boolean splitRemoved = removeSplitList.contains(splitName);
    833                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
    834                         mResolvedInheritedFiles.add(splitFile);
    835                     }
    836                 }
    837             }
    838 
    839             // Inherit compiled oat directory.
    840             final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile();
    841             mInheritedFilesBase = packageInstallDir;
    842             final File oatDir = new File(packageInstallDir, "oat");
    843             if (oatDir.exists()) {
    844                 final File[] archSubdirs = oatDir.listFiles();
    845 
    846                 // Keep track of all instruction sets we've seen compiled output for.
    847                 // If we're linking (and not copying) inherited files, we can recreate the
    848                 // instruction set hierarchy and link compiled output.
    849                 if (archSubdirs != null && archSubdirs.length > 0) {
    850                     final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets();
    851                     for (File archSubDir : archSubdirs) {
    852                         // Skip any directory that isn't an ISA subdir.
    853                         if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) {
    854                             continue;
    855                         }
    856 
    857                         mResolvedInstructionSets.add(archSubDir.getName());
    858                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
    859 
    860                         // Only add compiled files associated with the base.
    861                         // Once b/62269291 is resolved, we can add all compiled files again.
    862                         for (File oatFile : oatFiles) {
    863                             if (oatFile.getName().equals("base.art")
    864                                     || oatFile.getName().equals("base.odex")
    865                                     || oatFile.getName().equals("base.vdex")) {
    866                                 mResolvedInheritedFiles.add(oatFile);
    867                             }
    868                         }
    869                     }
    870                 }
    871             }
    872         }
    873     }
    874 
    875     private void assertApkConsistent(String tag, ApkLite apk)
    876             throws PackageManagerException {
    877         if (!mPackageName.equals(apk.packageName)) {
    878             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
    879                     + apk.packageName + " inconsistent with " + mPackageName);
    880         }
    881         if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) {
    882             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
    883                     + " specified package " + params.appPackageName
    884                     + " inconsistent with " + apk.packageName);
    885         }
    886         if (mVersionCode != apk.versionCode) {
    887             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
    888                     + " version code " + apk.versionCode + " inconsistent with "
    889                     + mVersionCode);
    890         }
    891         if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
    892             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    893                     tag + " signatures are inconsistent");
    894         }
    895     }
    896 
    897     /**
    898      * Calculate the final install footprint size, combining both staged and
    899      * existing APKs together and including unpacked native code from both.
    900      */
    901     private long calculateInstalledSize() throws PackageManagerException {
    902         Preconditions.checkNotNull(mResolvedBaseFile);
    903 
    904         final ApkLite baseApk;
    905         try {
    906             baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
    907         } catch (PackageParserException e) {
    908             throw PackageManagerException.from(e);
    909         }
    910 
    911         final List<String> splitPaths = new ArrayList<>();
    912         for (File file : mResolvedStagedFiles) {
    913             if (mResolvedBaseFile.equals(file)) continue;
    914             splitPaths.add(file.getAbsolutePath());
    915         }
    916         for (File file : mResolvedInheritedFiles) {
    917             if (mResolvedBaseFile.equals(file)) continue;
    918             splitPaths.add(file.getAbsolutePath());
    919         }
    920 
    921         // This is kind of hacky; we're creating a half-parsed package that is
    922         // straddled between the inherited and staged APKs.
    923         final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
    924                 splitPaths.toArray(new String[splitPaths.size()]), null);
    925         final boolean isForwardLocked =
    926                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
    927 
    928         try {
    929             return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
    930         } catch (IOException e) {
    931             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
    932                     "Failed to calculate install size", e);
    933         }
    934     }
    935 
    936     /**
    937      * Determine if creating hard links between source and destination is
    938      * possible. That is, do they all live on the same underlying device.
    939      */
    940     private boolean isLinkPossible(List<File> fromFiles, File toDir) {
    941         try {
    942             final StructStat toStat = Os.stat(toDir.getAbsolutePath());
    943             for (File fromFile : fromFiles) {
    944                 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
    945                 if (fromStat.st_dev != toStat.st_dev) {
    946                     return false;
    947                 }
    948             }
    949         } catch (ErrnoException e) {
    950             Slog.w(TAG, "Failed to detect if linking possible: " + e);
    951             return false;
    952         }
    953         return true;
    954     }
    955 
    956     private static String getRelativePath(File file, File base) throws IOException {
    957         final String pathStr = file.getAbsolutePath();
    958         final String baseStr = base.getAbsolutePath();
    959         // Don't allow relative paths.
    960         if (pathStr.contains("/.") ) {
    961             throw new IOException("Invalid path (was relative) : " + pathStr);
    962         }
    963 
    964         if (pathStr.startsWith(baseStr)) {
    965             return pathStr.substring(baseStr.length());
    966         }
    967 
    968         throw new IOException("File: " + pathStr + " outside base: " + baseStr);
    969     }
    970 
    971     private void createOatDirs(List<String> instructionSets, File fromDir)
    972             throws PackageManagerException {
    973         for (String instructionSet : instructionSets) {
    974             try {
    975                 mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
    976             } catch (InstallerException e) {
    977                 throw PackageManagerException.from(e);
    978             }
    979         }
    980     }
    981 
    982     private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
    983             throws IOException {
    984         for (File fromFile : fromFiles) {
    985             final String relativePath = getRelativePath(fromFile, fromDir);
    986             try {
    987                 mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(),
    988                         toDir.getAbsolutePath());
    989             } catch (InstallerException e) {
    990                 throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
    991                         + fromDir + ", " + toDir + ")", e);
    992             }
    993         }
    994 
    995         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
    996     }
    997 
    998     private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
    999         // Remove any partial files from previous attempt
   1000         for (File file : toDir.listFiles()) {
   1001             if (file.getName().endsWith(".tmp")) {
   1002                 file.delete();
   1003             }
   1004         }
   1005 
   1006         for (File fromFile : fromFiles) {
   1007             final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
   1008             if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
   1009             if (!FileUtils.copyFile(fromFile, tmpFile)) {
   1010                 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
   1011             }
   1012             try {
   1013                 Os.chmod(tmpFile.getAbsolutePath(), 0644);
   1014             } catch (ErrnoException e) {
   1015                 throw new IOException("Failed to chmod " + tmpFile);
   1016             }
   1017             final File toFile = new File(toDir, fromFile.getName());
   1018             if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
   1019             if (!tmpFile.renameTo(toFile)) {
   1020                 throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
   1021             }
   1022         }
   1023         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
   1024     }
   1025 
   1026     private static void extractNativeLibraries(File packageDir, String abiOverride)
   1027             throws PackageManagerException {
   1028         // Always start from a clean slate
   1029         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
   1030         NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
   1031 
   1032         NativeLibraryHelper.Handle handle = null;
   1033         try {
   1034             handle = NativeLibraryHelper.Handle.create(packageDir);
   1035             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
   1036                     abiOverride);
   1037             if (res != PackageManager.INSTALL_SUCCEEDED) {
   1038                 throw new PackageManagerException(res,
   1039                         "Failed to extract native libraries, res=" + res);
   1040             }
   1041         } catch (IOException e) {
   1042             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
   1043                     "Failed to extract native libraries", e);
   1044         } finally {
   1045             IoUtils.closeQuietly(handle);
   1046         }
   1047     }
   1048 
   1049     private static void resizeContainer(String cid, long targetSize)
   1050             throws PackageManagerException {
   1051         String path = PackageHelper.getSdDir(cid);
   1052         if (path == null) {
   1053             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1054                     "Failed to find mounted " + cid);
   1055         }
   1056 
   1057         final long currentSize = new File(path).getTotalSpace();
   1058         if (currentSize > targetSize) {
   1059             Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
   1060                     + targetSize + "; skipping resize");
   1061             return;
   1062         }
   1063 
   1064         if (!PackageHelper.unMountSdDir(cid)) {
   1065             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1066                     "Failed to unmount " + cid + " before resize");
   1067         }
   1068 
   1069         if (!PackageHelper.resizeSdDir(targetSize, cid,
   1070                 PackageManagerService.getEncryptKey())) {
   1071             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1072                     "Failed to resize " + cid + " to " + targetSize + " bytes");
   1073         }
   1074 
   1075         path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
   1076                 Process.SYSTEM_UID, false);
   1077         if (path == null) {
   1078             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1079                     "Failed to mount " + cid + " after resize");
   1080         }
   1081     }
   1082 
   1083     private void finalizeAndFixContainer(String cid) throws PackageManagerException {
   1084         if (!PackageHelper.finalizeSdDir(cid)) {
   1085             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1086                     "Failed to finalize container " + cid);
   1087         }
   1088 
   1089         if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) {
   1090             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
   1091                     "Failed to fix permissions on container " + cid);
   1092         }
   1093     }
   1094 
   1095     void setPermissionsResult(boolean accepted) {
   1096         if (!mSealed) {
   1097             throw new SecurityException("Must be sealed to accept permissions");
   1098         }
   1099 
   1100         if (accepted) {
   1101             // Mark and kick off another install pass
   1102             synchronized (mLock) {
   1103                 mPermissionsAccepted = true;
   1104             }
   1105             mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
   1106         } else {
   1107             destroyInternal();
   1108             dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
   1109         }
   1110     }
   1111 
   1112     public void open() throws IOException {
   1113         if (mActiveCount.getAndIncrement() == 0) {
   1114             mCallback.onSessionActiveChanged(this, true);
   1115         }
   1116 
   1117         synchronized (mLock) {
   1118             if (!mPrepared) {
   1119                 if (stageDir != null) {
   1120                     prepareStageDir(stageDir);
   1121                 } else if (stageCid != null) {
   1122                     final long identity = Binder.clearCallingIdentity();
   1123                     try {
   1124                         prepareExternalStageCid(stageCid, params.sizeBytes);
   1125                     } finally {
   1126                         Binder.restoreCallingIdentity(identity);
   1127                     }
   1128 
   1129                     // TODO: deliver more granular progress for ASEC allocation
   1130                     mInternalProgress = 0.25f;
   1131                     computeProgressLocked(true);
   1132                 } else {
   1133                     throw new IllegalArgumentException(
   1134                             "Exactly one of stageDir or stageCid stage must be set");
   1135                 }
   1136 
   1137                 mPrepared = true;
   1138                 mCallback.onSessionPrepared(this);
   1139             }
   1140         }
   1141     }
   1142 
   1143     @Override
   1144     public void close() {
   1145         if (mActiveCount.decrementAndGet() == 0) {
   1146             mCallback.onSessionActiveChanged(this, false);
   1147         }
   1148     }
   1149 
   1150     @Override
   1151     public void abandon() {
   1152         if (mRelinquished) {
   1153             Slog.d(TAG, "Ignoring abandon after commit relinquished control");
   1154             return;
   1155         }
   1156         destroyInternal();
   1157         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
   1158     }
   1159 
   1160     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
   1161         mFinalStatus = returnCode;
   1162         mFinalMessage = msg;
   1163 
   1164         if (mRemoteObserver != null) {
   1165             try {
   1166                 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
   1167             } catch (RemoteException ignored) {
   1168             }
   1169         }
   1170 
   1171         final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
   1172 
   1173         // Send broadcast to default launcher only if it's a new install
   1174         final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
   1175         if (success && isNewInstall) {
   1176             mPm.sendSessionCommitBroadcast(generateInfo(), userId);
   1177         }
   1178 
   1179         mCallback.onSessionFinished(this, success);
   1180     }
   1181 
   1182     private void destroyInternal() {
   1183         synchronized (mLock) {
   1184             mSealed = true;
   1185             mDestroyed = true;
   1186 
   1187             // Force shut down all bridges
   1188             for (RevocableFileDescriptor fd : mFds) {
   1189                 fd.revoke();
   1190             }
   1191             for (FileBridge bridge : mBridges) {
   1192                 bridge.forceClose();
   1193             }
   1194         }
   1195         if (stageDir != null) {
   1196             try {
   1197                 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
   1198             } catch (InstallerException ignored) {
   1199             }
   1200         }
   1201         if (stageCid != null) {
   1202             PackageHelper.destroySdDir(stageCid);
   1203         }
   1204     }
   1205 
   1206     void dump(IndentingPrintWriter pw) {
   1207         synchronized (mLock) {
   1208             dumpLocked(pw);
   1209         }
   1210     }
   1211 
   1212     private void dumpLocked(IndentingPrintWriter pw) {
   1213         pw.println("Session " + sessionId + ":");
   1214         pw.increaseIndent();
   1215 
   1216         pw.printPair("userId", userId);
   1217         pw.printPair("installerPackageName", installerPackageName);
   1218         pw.printPair("installerUid", installerUid);
   1219         pw.printPair("createdMillis", createdMillis);
   1220         pw.printPair("stageDir", stageDir);
   1221         pw.printPair("stageCid", stageCid);
   1222         pw.println();
   1223 
   1224         params.dump(pw);
   1225 
   1226         pw.printPair("mClientProgress", mClientProgress);
   1227         pw.printPair("mProgress", mProgress);
   1228         pw.printPair("mSealed", mSealed);
   1229         pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
   1230         pw.printPair("mRelinquished", mRelinquished);
   1231         pw.printPair("mDestroyed", mDestroyed);
   1232         pw.printPair("mFds", mFds.size());
   1233         pw.printPair("mBridges", mBridges.size());
   1234         pw.printPair("mFinalStatus", mFinalStatus);
   1235         pw.printPair("mFinalMessage", mFinalMessage);
   1236         pw.println();
   1237 
   1238         pw.decreaseIndent();
   1239     }
   1240 }
   1241