blob: e3ceccd1abb86e3284600b75987a46bfa401f135 [file] [log] [blame]
/*
* Copyright (C) 2021 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.app.AppOpsManager.MODE_DEFAULT;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceUtils.makeDirRecursive;
import android.content.pm.DataLoaderType;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Environment;
import android.os.FileUtils;
import android.os.SELinux;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Slog;
import com.android.internal.content.NativeLibraryHelper;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import libcore.io.IoUtils;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* Logic to handle installation of new applications, including copying
* and renaming logic.
*/
class FileInstallArgs extends InstallArgs {
private File mCodeFile;
// Example topology:
// /data/app/com.example/base.apk
// /data/app/com.example/split_foo.apk
// /data/app/com.example/lib/arm/libfoo.so
// /data/app/com.example/lib/arm64/libfoo.so
// /data/app/com.example/dalvik/arm/base.apk@classes.dex
/** New install */
FileInstallArgs(InstallParams params) {
super(params);
}
/**
* Create args that describe an existing installed package. Typically used
* when cleaning up old installs, or used as a move source.
*/
FileInstallArgs(String codePath, String[] instructionSets, PackageManagerService pm) {
super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.INSTALL_SCENARIO_DEFAULT,
false, DataLoaderType.NONE,
PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, pm);
mCodeFile = (codePath != null) ? new File(codePath) : null;
}
int copyApk() {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
try {
return doCopyApk();
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private int doCopyApk() {
if (mOriginInfo.mStaged) {
if (DEBUG_INSTALL) Slog.d(TAG, mOriginInfo.mFile + " already staged; skipping copy");
mCodeFile = mOriginInfo.mFile;
return PackageManager.INSTALL_SUCCEEDED;
}
try {
final boolean isEphemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final File tempDir =
mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral);
mCodeFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
int ret = PackageManagerServiceUtils.copyPackage(
mOriginInfo.mFile.getAbsolutePath(), mCodeFile);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
final boolean isIncremental = isIncrementalPath(mCodeFile.getAbsolutePath());
final File libraryRoot = new File(mCodeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(mCodeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
mAbiOverride, isIncremental);
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
}
return ret;
}
int doPreInstall(int status) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
}
return status;
}
@Override
boolean doRename(int status, ParsedPackage parsedPackage) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
return false;
}
final File targetDir = resolveTargetDir();
final File beforeCodeFile = mCodeFile;
final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
parsedPackage.getPackageName());
if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
final boolean onIncremental = mPm.mIncrementalManager != null
&& isIncrementalPath(beforeCodeFile.getAbsolutePath());
try {
makeDirRecursive(afterCodeFile.getParentFile(), 0775);
if (onIncremental) {
// Just link files here. The stage dir will be removed when the installation
// session is completed.
mPm.mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile);
} else {
Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
}
} catch (IOException | ErrnoException e) {
Slog.w(TAG, "Failed to rename", e);
return false;
}
if (onIncremental) {
Slog.i(TAG, PackageManagerServiceUtils.SELINUX_BUG
+ ": Skipping restorecon for Incremental install of " + beforeCodeFile);
} else {
try {
if (!SELinux.restoreconRecursive(afterCodeFile)) {
Slog.w(TAG, "Failed to restorecon");
return false;
}
PackageManagerServiceUtils.verifySelinuxLabels(afterCodeFile.getAbsolutePath());
} catch (Exception e) {
Slog.e(TAG,
PackageManagerServiceUtils.SELINUX_BUG + ": Exception from restorecon on "
+ beforeCodeFile, e);
throw e;
}
}
// Reflect the rename internally
mCodeFile = afterCodeFile;
// Reflect the rename in scanned details
try {
parsedPackage.setPath(afterCodeFile.getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
return false;
}
parsedPackage.setBaseApkPath(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, parsedPackage.getBaseApkPath()));
parsedPackage.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, parsedPackage.getSplitCodePaths()));
return true;
}
// TODO(b/168126411): Once staged install flow starts using the same folder as non-staged
// flow, we won't need this method anymore.
private File resolveTargetDir() {
boolean isStagedInstall = (mInstallFlags & INSTALL_STAGED) != 0;
if (isStagedInstall) {
return Environment.getDataAppDirectory(null);
} else {
return mCodeFile.getParentFile();
}
}
int doPostInstall(int status, int uid) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
}
return status;
}
@Override
String getCodePath() {
return (mCodeFile != null) ? mCodeFile.getAbsolutePath() : null;
}
private boolean cleanUp() {
if (mCodeFile == null || !mCodeFile.exists()) {
return false;
}
mRemovePackageHelper.removeCodePathLI(mCodeFile);
return true;
}
void cleanUpResourcesLI() {
// Try enumerating all code paths before deleting
List<String> allCodePaths = Collections.EMPTY_LIST;
if (mCodeFile != null && mCodeFile.exists()) {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
input.reset(), mCodeFile, /* flags */ 0);
if (result.isSuccess()) {
// Ignore error; we tried our best
allCodePaths = result.getResult().getAllApkPaths();
}
}
cleanUp();
removeDexFiles(allCodePaths, mInstructionSets);
}
void removeDexFiles(List<String> allCodePaths, String[] instructionSets) {
if (!allCodePaths.isEmpty()) {
if (instructionSets == null) {
throw new IllegalStateException("instructionSet == null");
}
String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
for (String codePath : allCodePaths) {
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
try {
mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
} catch (Installer.InstallerException ignored) {
}
}
}
}
}
boolean doPostDeleteLI(boolean delete) {
// XXX err, shouldn't we respect the delete flag?
cleanUpResourcesLI();
return true;
}
}