Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 395e63d2 authored by Songchun Fan's avatar Songchun Fan
Browse files

[pm] refactor InstallArgs

1/n of install flow refactor

Made InstallArgs a final data class and moved all methods to caller classes.

+ Move processInstallRequests to InstallingSession which will become the
entry point of installation requests.
+ Methods that involve mLock or mInstallLock are still kept in
  InstallPackageHalper and RemovePackageHelper.
+ Some errorprone cleanup.

BUG: 238678399
Test: presubmit
Change-Id: I1216cf8254711cc68a50806b7229a63f5df387bd
parent 2438f7ee
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ final class AppDataHelper {
     * <p>
     * <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
     */
    @GuardedBy("mPm.mInstallLock")
    public void prepareAppDataAfterInstallLIF(AndroidPackage pkg) {
        prepareAppDataPostCommitLIF(pkg, 0 /* previousAppId */);
    }
@@ -97,6 +98,7 @@ final class AppDataHelper {
     * {@link #prepareAppData(Installer.Batch, AndroidPackage, int, int, int)}
     * @see #prepareAppDataAfterInstallLIF(AndroidPackage)
     */
    @GuardedBy("mPm.mInstallLock")
    public void prepareAppDataPostCommitLIF(AndroidPackage pkg, int previousAppId) {
        final PackageSetting ps;
        synchronized (mPm.mLock) {
+22 −5
Original line number Diff line number Diff line
@@ -271,7 +271,7 @@ final class DeletePackageHelper {
        // other processes clean up before deleting resources.
        synchronized (mPm.mInstallLock) {
            if (info.mArgs != null) {
                info.mArgs.doPostDeleteLI(true);
                mRemovePackageHelper.cleanUpResources(info.mArgs);
            }

            boolean reEnableStub = false;
@@ -341,6 +341,7 @@ final class DeletePackageHelper {
    /*
     * This method handles package deletion in general
     */
    @GuardedBy("mPm.mInstallLock")
    public boolean deletePackageLIF(@NonNull String packageName, UserHandle user,
            boolean deleteCodeAndResources, @NonNull int[] allUserHandles, int flags,
            PackageRemovedInfo outInfo, boolean writeSettings) {
@@ -393,8 +394,18 @@ final class DeletePackageHelper {
        return new DeletePackageAction(ps, disabledPs, outInfo, flags, user);
    }

    public void executeDeletePackage(DeletePackageAction action, String packageName,
            boolean deleteCodeAndResources, @NonNull int[] allUserHandles, boolean writeSettings)
            throws SystemDeleteException {
        synchronized (mPm.mInstallLock) {
            executeDeletePackageLIF(action, packageName, deleteCodeAndResources, allUserHandles,
                    writeSettings);
        }
    }

    /** Deletes a package. Only throws when install of a disabled package fails. */
    public void executeDeletePackageLIF(DeletePackageAction action,
    @GuardedBy("mPm.mInstallLock")
    private void executeDeletePackageLIF(DeletePackageAction action,
            String packageName, boolean deleteCodeAndResources,
            @NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
        final PackageSetting ps = action.mDeletingPs;
@@ -487,9 +498,11 @@ final class DeletePackageHelper {

        // Take a note whether we deleted the package for all users
        if (outInfo != null) {
            synchronized (mPm.mLock) {
                outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null;
            }
        }
    }

    private void clearPackageStateForUserLIF(PackageSetting ps, int userId,
            PackageRemovedInfo outInfo, int flags) {
@@ -538,6 +551,7 @@ final class DeletePackageHelper {
        }
    }

    @GuardedBy("mPm.mInstallLock")
    private void deleteInstalledPackageLIF(PackageSetting ps,
            boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
            PackageRemovedInfo outInfo, boolean writeSettings) {
@@ -556,7 +570,7 @@ final class DeletePackageHelper {

        // Delete application code and resources only for parent packages
        if (deleteCodeAndResources && (outInfo != null)) {
            outInfo.mArgs = new FileInstallArgs(
            outInfo.mArgs = new InstallArgs(
                    ps.getPathString(), getAppDexInstructionSets(
                            ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()), mPm);
            if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
@@ -636,7 +650,10 @@ final class DeletePackageHelper {
            flags |= PackageManager.DELETE_KEEP_DATA;
        }

        deleteInstalledPackageLIF(deletedPs, true, flags, allUserHandles, outInfo, writeSettings);
        synchronized (mPm.mInstallLock) {
            deleteInstalledPackageLIF(deletedPs, true, flags, allUserHandles, outInfo,
                    writeSettings);
        }
    }

    public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
+0 −268
Original line number Diff line number Diff line
/*
 * 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(InstallingSession 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 && !SELinux.restoreconRecursive(afterCodeFile)) {
            Slog.w(TAG, "Failed to restorecon");
            return false;
        }

        // 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;
    }
}
+19 −21
Original line number Diff line number Diff line
@@ -16,19 +16,24 @@

package com.android.server.pm;

import static android.app.AppOpsManager.MODE_DEFAULT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.DataLoaderType;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.os.UserHandle;

import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.pkg.ParsedPackage;

import java.io.File;
import java.util.List;

abstract class InstallArgs {
final class InstallArgs {
    File mCodeFile;
    /** @see InstallingSession#mOriginInfo */
    final OriginInfo mOriginInfo;
    /** @see InstallingSession#mMoveInfo */
@@ -108,28 +113,21 @@ abstract class InstallArgs {
                params.mDataLoaderType, params.mPackageSource, params.mPm);
    }

    abstract int copyApk();
    abstract int doPreInstall(int status);

    /**
     * Rename package into final resting place. All paths on the given
     * scanned package should be updated to reflect the rename.
     * Create args that describe an existing installed package. Typically used
     * when cleaning up old installs, or used as a move source.
     */
    abstract boolean doRename(int status, ParsedPackage parsedPackage);
    abstract int doPostInstall(int status, int uid);

    /** @see PackageSettingBase#getPath() */
    abstract String getCodePath();

    // Need installer lock especially for dex file removal.
    abstract void cleanUpResourcesLI();
    abstract boolean doPostDeleteLI(boolean delete);

    protected boolean isEphemeral() {
        return (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
    InstallArgs(String codePath, String[] instructionSets, PackageManagerService pm) {
        this(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;
    }

    UserHandle getUser() {
        return mUser;
    /** @see PackageSettingBase#getPath() */
    String getCodePath() {
        return (mCodeFile != null) ? mCodeFile.getAbsolutePath() : null;
    }
}
+178 −212

File changed.

Preview size limit exceeded, changes collapsed.

Loading