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