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

Commit a8f4b131 authored by Dario Freni's avatar Dario Freni
Browse files

Write staged sessions to /data/staging.

If a session is marked as isStaged, write the package files directly on
a directory named /data/staging/session_${SESSION_ID}, instead of the
/data/app temporary directory. This allows us to set different SELinux
policy (e.g. allowing apexd to read that directory), and to persist the
data across reboots without altering the existing code.

To ensure we cover existing workflows, in this CL we try as much as
possible to re-use existing code to create staging directories and wire
data into them.

Bug: 118865310
Test: Verified that adb install --staged file.apk successfully creates a
/data/staging/session_SESSION_ID/base.apk file. If --apex is passed, the
file is named base.apex.
Change-Id: Iacfd7cfb90b738eeb752fec3e6a4e38ea307259a
parent 8e19d422
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -381,6 +381,11 @@ public class Environment {
        return new File(getDataDirectory(volumeUuid), "app");
    }

    /** {@hide} */
    public static File getDataStagingDirectory(String volumeUuid) {
        return new File(getDataDirectory(volumeUuid), "staging");
    }

    /** {@hide} */
    public static File getDataUserCeDirectory(String volumeUuid) {
        return new File(getDataDirectory(volumeUuid), "user");
+20 −15
Original line number Diff line number Diff line
@@ -209,8 +209,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        synchronized (mSessions) {
            readSessionsLocked();

            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/);
            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/);
            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);

            final ArraySet<File> unclaimedIcons = newArraySet(
                    mSessionsDir.listFiles());
@@ -230,8 +229,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    }

    @GuardedBy("mSessions")
    private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) {
        final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
    private void reconcileStagesLocked(String volumeUuid) {
        final File stagingDir = getTmpSessionDir(volumeUuid);
        final ArraySet<File> unclaimedStages = newArraySet(
                stagingDir.listFiles(sStageFilter));

@@ -252,7 +251,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements

    public void onPrivateVolumeMounted(String volumeUuid) {
        synchronized (mSessions) {
            reconcileStagesLocked(volumeUuid, false /*isInstant*/);
            reconcileStagesLocked(volumeUuid);
        }
    }

@@ -269,9 +268,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
            try {
                final int sessionId = allocateSessionIdLocked();
                mLegacySessions.put(sessionId, true);
                final File stageDir = buildStageDir(volumeUuid, sessionId, isEphemeral);
                prepareStageDir(stageDir);
                return stageDir;
                final File sessionStageDir = buildTmpSessionDir(sessionId, volumeUuid);
                prepareStageDir(sessionStageDir);
                return sessionStageDir;
            } catch (IllegalStateException e) {
                throw new IOException(e);
            }
@@ -526,9 +525,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        String stageCid = null;
        if (!params.isMultiPackage) {
            if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
                final boolean isInstant =
                        (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
                stageDir = buildSessionDir(sessionId, params);
            } else {
                stageCid = buildExternalStageCid(sessionId);
            }
@@ -634,13 +631,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        throw new IllegalStateException("Failed to allocate session ID");
    }

    private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
    private File getTmpSessionDir(String volumeUuid) {
        return Environment.getDataAppDirectory(volumeUuid);
    }

    private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
        final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
        return new File(stagingDir, "vmdl" + sessionId + ".tmp");
    private File buildTmpSessionDir(int sessionId, String volumeUuid) {
        final File sessionStagingDir = getTmpSessionDir(volumeUuid);
        return new File(sessionStagingDir, "vmdl" + sessionId + ".tmp");
    }

    private File buildSessionDir(int sessionId, SessionParams params) {
        if (params.isStaged) {
            final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
            return new File(sessionStagingDir, "session_" + sessionId);
        }
        return buildTmpSessionDir(sessionId, params.volumeUuid);
    }

    static void prepareStageDir(File stageDir) throws IOException {
+15 −10
Original line number Diff line number Diff line
@@ -977,18 +977,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        // Read transfers from the original owner stay open, but as the session's data
        // cannot be modified anymore, there is no leak of information.
        if (!params.isMultiPackage) {
        // For staged sessions, the validation is performed by StagingManager.
        if (!params.isMultiPackage && !params.isStaged) {
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES
                            | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);

            resolveStageDirLocked();

            // Verify that stage looks sane with respect to existing application.
            // This currently only ensures packageName, versionCode, and certificate
            // consistency.
            try {
                if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
                    // TODO(b/118865310): Remove this when APEX validation is done via
                    //                    StagingManager.
                    validateApexInstallLocked(pkgInfo);
                } else {
                    // Verify that stage looks sane with respect to existing application.
@@ -1061,16 +1061,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private void commitLocked()
            throws PackageManagerException {
        if (params.isStaged) {
            mStagingManager.commitSession(this);
            destroyInternal();
            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
            return;
        }
        final PackageManagerService.ActiveInstallSession committingSession =
                makeSessionActiveLocked();
        if (committingSession == null) {
            return;
        }
        if (isStaged()) {
            mStagingManager.commitSession(this);
            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
            return;
        }
        if (isMultiPackage()) {
            final int[] childSessionIds = getChildSessionIds();
            List<PackageManagerService.ActiveInstallSession> childSessions =
@@ -1973,7 +1974,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                bridge.forceClose();
            }
        }
        if (stageDir != null) {
        // For staged sessions, we don't delete the directory where the packages have been copied,
        // since these packages are supposed to be read on reboot. StagingManager is in charge of
        // deleting these dirs when the staged session has reached a final state.
        // TODO(b/118865310): Implement packageDir deletion in StagingManager.
        if (stageDir != null && !params.isStaged) {
            try {
                mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
            } catch (InstallerException ignored) {
+4 −1
Original line number Diff line number Diff line
@@ -2293,6 +2293,9 @@ class PackageManagerShellCommand extends ShellCommand {
                    break;
                case "--apex":
                    sessionParams.installFlags |= PackageManager.INSTALL_APEX;
                    // TODO(b/118865310): APEX packages should always imply
                    //                    sessionParams.isStaged(). Enforce this when the staged
                    //                    install workflow is complete.
                    break;
                case "--multi-package":
                    sessionParams.setMultiPackage();
@@ -2588,7 +2591,7 @@ class PackageManagerShellCommand extends ShellCommand {
        try {
            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));
            if (!session.isMultiPackage()) {
            if (!session.isMultiPackage() && !session.isStaged()) {
                // Sanity check that all .dm files match an apk.
                // (The installer does not support standalone .dm files and will not process them.)
                try {