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