/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; import static com.android.server.pm.PackageInstallerService.prepareStageDir; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; import android.os.Bundle; import android.os.FileBridge; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.system.StructStat; import android.text.TextUtils; import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; import libcore.io.IoUtils; import libcore.io.Libcore; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.os.InstallerConnection.InstallerException; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; import java.io.File; import java.io.FileDescriptor; import java.io.FileFilter; import java.io.IOException; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String TAG = "PackageInstaller"; private static final boolean LOGD = true; private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed"; private static final int MSG_COMMIT = 0; // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE private final PackageInstallerService.InternalCallback mCallback; private final Context mContext; private final PackageManagerService mPm; private final Handler mHandler; private final boolean mIsInstallerDeviceOwner; final int sessionId; final int userId; final String installerPackageName; final int installerUid; final SessionParams params; final long createdMillis; /** Staging location where client data is written. */ final File stageDir; final String stageCid; private final AtomicInteger mActiveCount = new AtomicInteger(); private final Object mLock = new Object(); @GuardedBy("mLock") private float mClientProgress = 0; @GuardedBy("mLock") private float mInternalProgress = 0; @GuardedBy("mLock") private float mProgress = 0; @GuardedBy("mLock") private float mReportedProgress = -1; @GuardedBy("mLock") private boolean mPrepared = false; @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") private boolean mPermissionsAccepted = false; @GuardedBy("mLock") private boolean mRelinquished = false; @GuardedBy("mLock") private boolean mDestroyed = false; private int mFinalStatus; private String mFinalMessage; @GuardedBy("mLock") private ArrayList mBridges = new ArrayList<>(); @GuardedBy("mLock") private IPackageInstallObserver2 mRemoteObserver; /** Fields derived from commit parsing */ private String mPackageName; private int mVersionCode; private Signature[] mSignatures; private Certificate[][] mCertificates; /** * Path to the validated base APK for this session, which may point at an * APK inside the session (when the session defines the base), or it may * point at the existing base APK (when adding splits to an existing app). *

* This is used when confirming permissions, since we can't fully stage the * session inside an ASEC before confirming with user. */ @GuardedBy("mLock") private File mResolvedBaseFile; @GuardedBy("mLock") private File mResolvedStageDir; @GuardedBy("mLock") private final List mResolvedStagedFiles = new ArrayList<>(); @GuardedBy("mLock") private final List mResolvedInheritedFiles = new ArrayList<>(); @GuardedBy("mLock") private final List mResolvedInstructionSets = new ArrayList<>(); @GuardedBy("mLock") private File mInheritedFilesBase; private static final FileFilter sAddedFilter = new FileFilter() { @Override public boolean accept(File file) { // Installers can't stage directories, so it's fine to ignore // entries like "lost+found". if (file.isDirectory()) return false; if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; return true; } }; private static final FileFilter sRemovedFilter = new FileFilter() { @Override public boolean accept(File file) { if (file.isDirectory()) return false; if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; return true; } }; private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { synchronized (mLock) { if (msg.obj != null) { mRemoteObserver = (IPackageInstallObserver2) msg.obj; } try { commitLocked(); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); destroyInternal(); dispatchSessionFinished(e.error, completeMsg, null); } return true; } } }; public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; mHandler = new Handler(looper, mHandlerCallback); this.sessionId = sessionId; this.userId = userId; this.installerPackageName = installerPackageName; this.installerUid = installerUid; this.params = params; this.createdMillis = createdMillis; this.stageDir = stageDir; this.stageCid = stageCid; if ((stageDir == null) == (stageCid == null)) { throw new IllegalArgumentException( "Exactly one of stageDir or stageCid stage must be set"); } mPrepared = prepared; mSealed = sealed; // Device owners are allowed to silently install packages, so the permission check is // waived if the installer is the device owner. DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); final boolean isPermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) == PackageManager.PERMISSION_GRANTED); final boolean isInstallerRoot = (installerUid == Process.ROOT_UID); final boolean forcePermissionPrompt = (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0; mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser( installerPackageName); if ((isPermissionGranted || isInstallerRoot || mIsInstallerDeviceOwner) && !forcePermissionPrompt) { mPermissionsAccepted = true; } else { mPermissionsAccepted = false; } } public SessionInfo generateInfo() { final SessionInfo info = new SessionInfo(); synchronized (mLock) { info.sessionId = sessionId; info.installerPackageName = installerPackageName; info.resolvedBaseCodePath = (mResolvedBaseFile != null) ? mResolvedBaseFile.getAbsolutePath() : null; info.progress = mProgress; info.sealed = mSealed; info.active = mActiveCount.get() > 0; info.mode = params.mode; info.sizeBytes = params.sizeBytes; info.appPackageName = params.appPackageName; info.appIcon = params.appIcon; info.appLabel = params.appLabel; } return info; } public boolean isPrepared() { synchronized (mLock) { return mPrepared; } } public boolean isSealed() { synchronized (mLock) { return mSealed; } } private void assertPreparedAndNotSealed(String cookie) { synchronized (mLock) { if (!mPrepared) { throw new IllegalStateException(cookie + " before prepared"); } if (mSealed) { throw new SecurityException(cookie + " not allowed after commit"); } } } /** * Resolve the actual location where staged data should be written. This * might point at an ASEC mount point, which is why we delay path resolution * until someone actively works with the session. */ private File resolveStageDir() throws IOException { synchronized (mLock) { if (mResolvedStageDir == null) { if (stageDir != null) { mResolvedStageDir = stageDir; } else { final String path = PackageHelper.getSdDir(stageCid); if (path != null) { mResolvedStageDir = new File(path); } else { throw new IOException("Failed to resolve path to container " + stageCid); } } } return mResolvedStageDir; } } @Override public void setClientProgress(float progress) { synchronized (mLock) { // Always publish first staging movement final boolean forcePublish = (mClientProgress == 0); mClientProgress = progress; computeProgressLocked(forcePublish); } } @Override public void addClientProgress(float progress) { synchronized (mLock) { setClientProgress(mClientProgress + progress); } } private void computeProgressLocked(boolean forcePublish) { mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); // Only publish when meaningful change if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { mReportedProgress = mProgress; mCallback.onSessionProgressChanged(this, mProgress); } } @Override public String[] getNames() { assertPreparedAndNotSealed("getNames"); try { return resolveStageDir().list(); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } @Override public void removeSplit(String splitName) { if (TextUtils.isEmpty(params.appPackageName)) { throw new IllegalStateException("Must specify package name to remove a split"); } try { createRemoveSplitMarker(splitName); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } private void createRemoveSplitMarker(String splitName) throws IOException { try { final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION; if (!FileUtils.isValidExtFilename(markerName)) { throw new IllegalArgumentException("Invalid marker: " + markerName); } final File target = new File(resolveStageDir(), markerName); target.createNewFile(); Os.chmod(target.getAbsolutePath(), 0 /*mode*/); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } @Override public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { try { return openWriteInternal(name, offsetBytes, lengthBytes); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) throws IOException { // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. final FileBridge bridge; synchronized (mLock) { assertPreparedAndNotSealed("openWrite"); bridge = new FileBridge(); mBridges.add(bridge); } try { // Use installer provided name for now; we always rename later if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } final File target = new File(resolveStageDir(), name); // TODO: this should delegate to DCS so the system process avoids // holding open FDs into containers. final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644); Os.chmod(target.getAbsolutePath(), 0644); // If caller specified a total length, allocate it for them. Free up // cache space to grow, if needed. if (lengthBytes > 0) { final StructStat stat = Libcore.os.fstat(targetFd); final long deltaBytes = lengthBytes - stat.st_size; // Only need to free up space when writing to internal stage if (stageDir != null && deltaBytes > 0) { mPm.freeStorage(params.volumeUuid, deltaBytes); } Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); } if (offsetBytes > 0) { Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); } bridge.setTargetFile(targetFd); bridge.start(); return new ParcelFileDescriptor(bridge.getClientSocket()); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } @Override public ParcelFileDescriptor openRead(String name) { try { return openReadInternal(name); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } private ParcelFileDescriptor openReadInternal(String name) throws IOException { assertPreparedAndNotSealed("openRead"); try { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } final File target = new File(resolveStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); return new ParcelFileDescriptor(targetFd); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } @Override public void commit(IntentSender statusReceiver) { Preconditions.checkNotNull(statusReceiver); final boolean wasSealed; synchronized (mLock) { wasSealed = mSealed; if (!mSealed) { // Verify that all writers are hands-off for (FileBridge bridge : mBridges) { if (!bridge.isClosed()) { throw new SecurityException("Files still open"); } } mSealed = true; } // Client staging is fully done at this point mClientProgress = 1f; computeProgressLocked(true); } if (!wasSealed) { // Persist the fact that we've sealed ourselves to prevent // mutations of any hard links we create. We do this without holding // the session lock, since otherwise it's a lock inversion. mCallback.onSessionSealedBlocking(this); } // This ongoing commit should keep session active, even though client // will probably close their end. mActiveCount.incrementAndGet(); final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, statusReceiver, sessionId, mIsInstallerDeviceOwner, userId); mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); } private void commitLocked() throws PackageManagerException { if (mDestroyed) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); } if (!mSealed) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); } try { resolveStageDir(); } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to resolve stage location", e); } // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. validateInstallLocked(); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); Preconditions.checkNotNull(mResolvedBaseFile); if (!mPermissionsAccepted) { // User needs to accept permissions; give installer an intent they // can use to involve user. final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName()); intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); try { mRemoteObserver.onUserActionRequired(intent); } catch (RemoteException ignored) { } // Commit was keeping session marked as active until now; release // that extra refcount so session appears idle. close(); return; } if (stageCid != null) { // Figure out the final installed size and resize the container once // and for all. Internally the parser handles straddling between two // locations when inheriting. final long finalSize = calculateInstalledSize(); resizeContainer(stageCid, finalSize); } // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { try { final List fromFiles = mResolvedInheritedFiles; final File toDir = resolveStageDir(); if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { throw new IllegalStateException("mInheritedFilesBase == null"); } if (isLinkPossible(fromFiles, toDir)) { if (!mResolvedInstructionSets.isEmpty()) { final File oatDir = new File(toDir, "oat"); createOatDirs(mResolvedInstructionSets, oatDir); } linkFiles(fromFiles, toDir, mInheritedFilesBase); } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. copyFiles(fromFiles, toDir); } } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed to inherit existing install", e); } } // TODO: surface more granular state from dexopt mInternalProgress = 0.5f; computeProgressLocked(true); // Unpack native libraries extractNativeLibraries(mResolvedStageDir, params.abiOverride); // Container is ready to go, let's seal it up! if (stageCid != null) { finalizeAndFixContainer(stageCid); } // We've reached point of no return; call into PMS to install the stage. // Regardless of success or failure we always destroy session. final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { @Override public void onUserActionRequired(Intent intent) { throw new IllegalStateException(); } @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { destroyInternal(); dispatchSessionFinished(returnCode, msg, extras); } }; final UserHandle user; if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) { user = UserHandle.ALL; } else { user = new UserHandle(userId); } mRelinquished = true; mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, installerPackageName, installerUid, user, mCertificates); } /** * Validate install by confirming that all application packages are have * consistent package name, version code, and signing certificates. *

* Clears and populates {@link #mResolvedBaseFile}, * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}. *

* Renames package files in stage to match split names defined inside. *

* Note that upgrade compatibility is still performed by * {@link PackageManagerService}. */ private void validateInstallLocked() throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; mResolvedBaseFile = null; mResolvedStagedFiles.clear(); mResolvedInheritedFiles.clear(); final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter); final List removeSplitList = new ArrayList<>(); if (!ArrayUtils.isEmpty(removedFiles)) { for (File removedFile : removedFiles) { final String fileName = removedFile.getName(); final String splitName = fileName.substring( 0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length()); removeSplitList.add(splitName); } } final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter); if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); } // Verify that all staged packages are internally consistent final ArraySet stagedSplits = new ArraySet<>(); for (File addedFile : addedFiles) { final ApkLite apk; try { apk = PackageParser.parseApkLite( addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); } if (!stagedSplits.add(apk.splitName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Split " + apk.splitName + " was defined multiple times"); } // Use first package to define unknown values if (mPackageName == null) { mPackageName = apk.packageName; mVersionCode = apk.versionCode; } if (mSignatures == null) { mSignatures = apk.signatures; mCertificates = apk.certificates; } assertApkConsistent(String.valueOf(addedFile), apk); // Take this opportunity to enforce uniform naming final String targetName; if (apk.splitName == null) { targetName = "base.apk"; } else { targetName = "split_" + apk.splitName + ".apk"; } if (!FileUtils.isValidExtFilename(targetName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Invalid filename: " + targetName); } final File targetFile = new File(mResolvedStageDir, targetName); if (!addedFile.equals(targetFile)) { addedFile.renameTo(targetFile); } // Base is coming from session if (apk.splitName == null) { mResolvedBaseFile = targetFile; } mResolvedStagedFiles.add(targetFile); } if (removeSplitList.size() > 0) { // validate split names marked for removal final int flags = mSignatures == null ? PackageManager.GET_SIGNATURES : 0; final PackageInfo pkg = mPm.getPackageInfo(params.appPackageName, flags, userId); for (String splitName : removeSplitList) { if (!ArrayUtils.contains(pkg.splitNames, splitName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Split not found: " + splitName); } } // ensure we've got appropriate package name, version code and signatures if (mPackageName == null) { mPackageName = pkg.packageName; mVersionCode = pkg.versionCode; } if (mSignatures == null) { mSignatures = pkg.signatures; } } if (params.mode == SessionParams.MODE_FULL_INSTALL) { // Full installs must include a base package if (!stagedSplits.contains(null)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Full install must include a base package"); } } else { // Partial installs must be consistent with existing install final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); if (app == null) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Missing existing base package for " + mPackageName); } final PackageLite existing; final ApkLite existingBase; try { existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0); existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()), PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); } assertApkConsistent("Existing base", existingBase); // Inherit base if not overridden if (mResolvedBaseFile == null) { mResolvedBaseFile = new File(app.getBaseCodePath()); mResolvedInheritedFiles.add(mResolvedBaseFile); } // Inherit splits if not overridden if (!ArrayUtils.isEmpty(existing.splitNames)) { for (int i = 0; i < existing.splitNames.length; i++) { final String splitName = existing.splitNames[i]; final File splitFile = new File(existing.splitCodePaths[i]); final boolean splitRemoved = removeSplitList.contains(splitName); if (!stagedSplits.contains(splitName) && !splitRemoved) { mResolvedInheritedFiles.add(splitFile); } } } // Inherit compiled oat directory. final File packageInstallDir = (new File(app.getBaseCodePath())).getParentFile(); mInheritedFilesBase = packageInstallDir; final File oatDir = new File(packageInstallDir, "oat"); if (oatDir.exists()) { final File[] archSubdirs = oatDir.listFiles(); // Keep track of all instruction sets we've seen compiled output for. // If we're linking (and not copying) inherited files, we can recreate the // instruction set hierarchy and link compiled output. if (archSubdirs != null && archSubdirs.length > 0) { final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); for (File archSubDir : archSubdirs) { // Skip any directory that isn't an ISA subdir. if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { continue; } mResolvedInstructionSets.add(archSubDir.getName()); List oatFiles = Arrays.asList(archSubDir.listFiles()); if (!oatFiles.isEmpty()) { mResolvedInheritedFiles.addAll(oatFiles); } } } } } } private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException { if (!mPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " + apk.packageName + " inconsistent with " + mPackageName); } if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " specified package " + params.appPackageName + " inconsistent with " + apk.packageName); } if (mVersionCode != apk.versionCode) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " version code " + apk.versionCode + " inconsistent with " + mVersionCode); } if (!Signature.areExactMatch(mSignatures, apk.signatures)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " signatures are inconsistent"); } } /** * Calculate the final install footprint size, combining both staged and * existing APKs together and including unpacked native code from both. */ private long calculateInstalledSize() throws PackageManagerException { Preconditions.checkNotNull(mResolvedBaseFile); final ApkLite baseApk; try { baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); } catch (PackageParserException e) { throw PackageManagerException.from(e); } final List splitPaths = new ArrayList<>(); for (File file : mResolvedStagedFiles) { if (mResolvedBaseFile.equals(file)) continue; splitPaths.add(file.getAbsolutePath()); } for (File file : mResolvedInheritedFiles) { if (mResolvedBaseFile.equals(file)) continue; splitPaths.add(file.getAbsolutePath()); } // This is kind of hacky; we're creating a half-parsed package that is // straddled between the inherited and staged APKs. final PackageLite pkg = new PackageLite(null, baseApk, null, splitPaths.toArray(new String[splitPaths.size()]), null); final boolean isForwardLocked = (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; try { return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride); } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Failed to calculate install size", e); } } /** * Determine if creating hard links between source and destination is * possible. That is, do they all live on the same underlying device. */ private boolean isLinkPossible(List fromFiles, File toDir) { try { final StructStat toStat = Os.stat(toDir.getAbsolutePath()); for (File fromFile : fromFiles) { final StructStat fromStat = Os.stat(fromFile.getAbsolutePath()); if (fromStat.st_dev != toStat.st_dev) { return false; } } } catch (ErrnoException e) { Slog.w(TAG, "Failed to detect if linking possible: " + e); return false; } return true; } private static String getRelativePath(File file, File base) throws IOException { final String pathStr = file.getAbsolutePath(); final String baseStr = base.getAbsolutePath(); // Don't allow relative paths. if (pathStr.contains("/.") ) { throw new IOException("Invalid path (was relative) : " + pathStr); } if (pathStr.startsWith(baseStr)) { return pathStr.substring(baseStr.length()); } throw new IOException("File: " + pathStr + " outside base: " + baseStr); } private void createOatDirs(List instructionSets, File fromDir) throws PackageManagerException { for (String instructionSet : instructionSets) { try { mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); } catch (InstallerException e) { throw PackageManagerException.from(e); } } } private void linkFiles(List fromFiles, File toDir, File fromDir) throws IOException { for (File fromFile : fromFiles) { final String relativePath = getRelativePath(fromFile, fromDir); try { mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(), toDir.getAbsolutePath()); } catch (InstallerException e) { throw new IOException("failed linkOrCreateDir(" + relativePath + ", " + fromDir + ", " + toDir + ")", e); } } Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); } private static void copyFiles(List fromFiles, File toDir) throws IOException { // Remove any partial files from previous attempt for (File file : toDir.listFiles()) { if (file.getName().endsWith(".tmp")) { file.delete(); } } for (File fromFile : fromFiles) { final File tmpFile = File.createTempFile("inherit", ".tmp", toDir); if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile); if (!FileUtils.copyFile(fromFile, tmpFile)) { throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); } try { Os.chmod(tmpFile.getAbsolutePath(), 0644); } catch (ErrnoException e) { throw new IOException("Failed to chmod " + tmpFile); } final File toFile = new File(toDir, fromFile.getName()); if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); if (!tmpFile.renameTo(toFile)) { throw new IOException("Failed to rename " + tmpFile + " to " + toFile); } } Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); } private static void extractNativeLibraries(File packageDir, String abiOverride) throws PackageManagerException { // Always start from a clean slate final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(packageDir); final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, abiOverride); if (res != PackageManager.INSTALL_SUCCEEDED) { throw new PackageManagerException(res, "Failed to extract native libraries, res=" + res); } } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Failed to extract native libraries", e); } finally { IoUtils.closeQuietly(handle); } } private static void resizeContainer(String cid, long targetSize) throws PackageManagerException { String path = PackageHelper.getSdDir(cid); if (path == null) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to find mounted " + cid); } final long currentSize = new File(path).getTotalSpace(); if (currentSize > targetSize) { Slog.w(TAG, "Current size " + currentSize + " is larger than target size " + targetSize + "; skipping resize"); return; } if (!PackageHelper.unMountSdDir(cid)) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to unmount " + cid + " before resize"); } if (!PackageHelper.resizeSdDir(targetSize, cid, PackageManagerService.getEncryptKey())) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to resize " + cid + " to " + targetSize + " bytes"); } path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, false); if (path == null) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to mount " + cid + " after resize"); } } private void finalizeAndFixContainer(String cid) throws PackageManagerException { if (!PackageHelper.finalizeSdDir(cid)) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to finalize container " + cid); } final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); final int gid = UserHandle.getSharedAppGid(uid); if (!PackageHelper.fixSdPermissions(cid, gid, null)) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to fix permissions on container " + cid); } } void setPermissionsResult(boolean accepted) { if (!mSealed) { throw new SecurityException("Must be sealed to accept permissions"); } if (accepted) { // Mark and kick off another install pass synchronized (mLock) { mPermissionsAccepted = true; } mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); } else { destroyInternal(); dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); } } public void open() throws IOException { if (mActiveCount.getAndIncrement() == 0) { mCallback.onSessionActiveChanged(this, true); } synchronized (mLock) { if (!mPrepared) { if (stageDir != null) { prepareStageDir(stageDir); } else if (stageCid != null) { prepareExternalStageCid(stageCid, params.sizeBytes); // TODO: deliver more granular progress for ASEC allocation mInternalProgress = 0.25f; computeProgressLocked(true); } else { throw new IllegalArgumentException( "Exactly one of stageDir or stageCid stage must be set"); } mPrepared = true; mCallback.onSessionPrepared(this); } } } @Override public void close() { if (mActiveCount.decrementAndGet() == 0) { mCallback.onSessionActiveChanged(this, false); } } @Override public void abandon() { if (mRelinquished) { Slog.d(TAG, "Ignoring abandon after commit relinquished control"); return; } destroyInternal(); dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { mFinalStatus = returnCode; mFinalMessage = msg; if (mRemoteObserver != null) { try { mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); } catch (RemoteException ignored) { } } final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); mCallback.onSessionFinished(this, success); } private void destroyInternal() { synchronized (mLock) { mSealed = true; mDestroyed = true; // Force shut down all bridges for (FileBridge bridge : mBridges) { bridge.forceClose(); } } if (stageDir != null) { try { mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath()); } catch (InstallerException ignored) { } } if (stageCid != null) { PackageHelper.destroySdDir(stageCid); } } void dump(IndentingPrintWriter pw) { synchronized (mLock) { dumpLocked(pw); } } private void dumpLocked(IndentingPrintWriter pw) { pw.println("Session " + sessionId + ":"); pw.increaseIndent(); pw.printPair("userId", userId); pw.printPair("installerPackageName", installerPackageName); pw.printPair("installerUid", installerUid); pw.printPair("createdMillis", createdMillis); pw.printPair("stageDir", stageDir); pw.printPair("stageCid", stageCid); pw.println(); params.dump(pw); pw.printPair("mClientProgress", mClientProgress); pw.printPair("mProgress", mProgress); pw.printPair("mSealed", mSealed); pw.printPair("mPermissionsAccepted", mPermissionsAccepted); pw.printPair("mRelinquished", mRelinquished); pw.printPair("mDestroyed", mDestroyed); pw.printPair("mBridges", mBridges.size()); pw.printPair("mFinalStatus", mFinalStatus); pw.printPair("mFinalMessage", mFinalMessage); pw.println(); pw.decreaseIndent(); } }