diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 000627cc884e8..5df53551b84fb 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -125,6 +125,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final long MAX_ACTIVE_SESSIONS = 1024; /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; + /** Destroy sessions older than this on storage free request */ + private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS; private final Context mContext; private final PackageManagerService mPm; @@ -228,23 +230,58 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @GuardedBy("mSessions") private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) { - final File stagingDir = buildStagingDir(volumeUuid, isEphemeral); - final ArraySet unclaimedStages = newArraySet( - stagingDir.listFiles(sStageFilter)); + final ArraySet unclaimedStages = getStagingDirsOnVolume(volumeUuid, isEphemeral); // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); unclaimedStages.remove(session.stageDir); } + removeStagingDirs(unclaimedStages); + } + private ArraySet getStagingDirsOnVolume(String volumeUuid, boolean isEphemeral) { + final File stagingDir = buildStagingDir(volumeUuid, isEphemeral); + final ArraySet stagingDirs = newArraySet( + stagingDir.listFiles(sStageFilter)); + return stagingDirs; + } + + private void removeStagingDirs(ArraySet stagingDirsToRemove) { // Clean up orphaned staging directories - for (File stage : unclaimedStages) { + for (File stage : stagingDirsToRemove) { Slog.w(TAG, "Deleting orphan stage " + stage); synchronized (mPm.mInstallLock) { mPm.removeCodePathLI(stage); + } + } + } + + /** + * Called to free up some storage space from obsolete installation files + */ + public void freeStageDirs(String volumeUuid, boolean internalVolume) { + final ArraySet unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid, internalVolume); + final long currentTimeMillis = System.currentTimeMillis(); + synchronized (mSessions) { + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) { + // Only handles sessions stored on the target volume + continue; + } + final long age = currentTimeMillis - session.createdMillis; + if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { + // Aggressively close old sessions because we are running low on storage + // Their staging dirs will be removed too + session.abandon(); + } else { + // Session is new enough, so it deserves to be kept even on low storage + unclaimedStagingDirsOnVolume.remove(session.stageDir); + } } } + removeStagingDirs(unclaimedStagingDirsOnVolume); } public void onPrivateVolumeMounted(String volumeUuid) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4c44b7b8027fc..edada326ee129 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -793,6 +793,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Check if the caller is the owner of this session. Otherwise throw a + * {@link SecurityException}. + */ + @GuardedBy("mLock") + private void assertCallerIsOwnerOrRootOrSystemLocked() { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid + && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Session does not belong to uid " + callingUid); + } + } + /** * If anybody is reading or writing data of the session, throw an {@link SecurityException}. */ @@ -1564,7 +1577,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void abandon() { synchronized (mLock) { - assertCallerIsOwnerOrRootLocked(); + assertCallerIsOwnerOrRootOrSystemLocked(); if (mRelinquished) { Slog.d(TAG, "Ignoring abandon after commit relinquished control"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 04aeb2fcd8429..77dceb6c22e05 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4696,6 +4696,9 @@ public class PackageManagerService extends IPackageManager.Stub InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { return; } + + // 11. Clear temp install session files + mInstallerService.freeStageDirs(volumeUuid, internalVolume); } else { try { mInstaller.freeCache(volumeUuid, bytes, 0, 0);