1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static com.android.server.pm.PackageManagerService.TAG; 20 21 import android.content.Context; 22 import android.content.pm.PackageInfoLite; 23 import android.content.pm.parsing.PackageLite; 24 import android.os.Environment; 25 import android.os.FileUtils; 26 import android.os.RemoteException; 27 import android.os.SystemProperties; 28 import android.os.storage.IStorageManager; 29 import android.os.storage.StorageManager; 30 import android.os.storage.StorageManagerInternal; 31 import android.provider.Settings; 32 import android.text.format.DateUtils; 33 import android.util.Slog; 34 35 import com.android.internal.content.InstallLocationUtils; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.util.Objects; 40 import java.util.concurrent.TimeUnit; 41 42 /** 43 * A helper to clear various types of cached data across the system. 44 */ 45 final class FreeStorageHelper { 46 private static final long FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD = 47 TimeUnit.HOURS.toMillis(2); /* two hours */ 48 49 /** 50 * Wall-clock timeout (in milliseconds) after which we *require* that an fstrim 51 * be run on this device. We use the value in the Settings.Global.MANDATORY_FSTRIM_INTERVAL 52 * settings entry if available, otherwise we use the hardcoded default. If it's been 53 * more than this long since the last fstrim, we force one during the boot sequence. 54 * 55 * This backstops other fstrim scheduling: if the device is alive at midnight+idle, 56 * one gets run at the next available charging+idle time. This final mandatory 57 * no-fstrim check kicks in only of the other scheduling criteria is never met. 58 */ 59 private static final long DEFAULT_MANDATORY_FSTRIM_INTERVAL = 3 * DateUtils.DAY_IN_MILLIS; 60 61 private final PackageManagerService mPm; 62 private final Context mContext; 63 private final PackageManagerServiceInjector mInjector; 64 private final boolean mEnableFreeCacheV2; 65 66 // TODO(b/198166813): remove PMS dependency FreeStorageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, Context context, boolean enableFreeCacheV2)67 FreeStorageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, 68 Context context, boolean enableFreeCacheV2) { 69 mPm = pm; 70 mInjector = injector; 71 mContext = context; 72 mEnableFreeCacheV2 = enableFreeCacheV2; 73 } 74 FreeStorageHelper(PackageManagerService pm)75 FreeStorageHelper(PackageManagerService pm) { 76 this(pm, pm.mInjector, pm.mContext, 77 SystemProperties.getBoolean("fw.free_cache_v2", true)); 78 } 79 80 /** 81 * Blocking call to clear various types of cached data across the system 82 * until the requested bytes are available. 83 */ freeStorage(String volumeUuid, long bytes, @StorageManager.AllocateFlags int flags)84 void freeStorage(String volumeUuid, long bytes, 85 @StorageManager.AllocateFlags int flags) throws IOException { 86 final StorageManager storage = mInjector.getSystemService(StorageManager.class); 87 final File file = storage.findPathForUuid(volumeUuid); 88 if (file.getUsableSpace() >= bytes) return; 89 90 if (mEnableFreeCacheV2) { 91 final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, 92 volumeUuid); 93 final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; 94 95 // 1. Pre-flight to determine if we have any chance to succeed 96 // 2. Consider preloaded data (after 1w honeymoon, unless aggressive) 97 if (internalVolume && (aggressive || SystemProperties 98 .getBoolean("persist.sys.preloads.file_cache_expired", false))) { 99 mPm.deletePreloadsFileCache(); 100 if (file.getUsableSpace() >= bytes) return; 101 } 102 103 // 3. Consider parsed APK data (aggressive only) 104 if (internalVolume && aggressive) { 105 FileUtils.deleteContents(mPm.getCacheDir()); 106 if (file.getUsableSpace() >= bytes) return; 107 } 108 109 // 4. Consider cached app data (above quotas) 110 try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { 111 mPm.mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2); 112 } catch (Installer.InstallerException ignored) { 113 } 114 if (file.getUsableSpace() >= bytes) return; 115 116 final Computer computer = mPm.snapshotComputer(); 117 final SharedLibrariesImpl sharedLibraries = mPm.mInjector.getSharedLibrariesImpl(); 118 // 5. Consider shared libraries with refcount=0 and age>min cache period 119 if (internalVolume && sharedLibraries.pruneUnusedStaticSharedLibraries(computer, bytes, 120 android.provider.Settings.Global.getLong(mContext.getContentResolver(), 121 Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, 122 FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) { 123 return; 124 } 125 126 // 6. Consider dexopt output (aggressive only) 127 // TODO: Implement 128 129 // 7. Consider installed instant apps unused longer than min cache period 130 if (internalVolume) { 131 if (mPm.mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes, 132 android.provider.Settings.Global.getLong( 133 mContext.getContentResolver(), 134 Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, 135 InstantAppRegistry 136 .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { 137 return; 138 } 139 } 140 141 // 8. Consider cached app data (below quotas) 142 try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { 143 mPm.mInstaller.freeCache(volumeUuid, bytes, 144 Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA); 145 } catch (Installer.InstallerException ignored) { 146 } 147 if (file.getUsableSpace() >= bytes) return; 148 149 // 9. Consider DropBox entries 150 // TODO: Implement 151 152 // 10. Consider instant meta-data (uninstalled apps) older that min cache period 153 if (internalVolume) { 154 if (mPm.mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes, 155 android.provider.Settings.Global.getLong( 156 mContext.getContentResolver(), 157 Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, 158 InstantAppRegistry 159 .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { 160 return; 161 } 162 } 163 164 // 11. Free storage service cache 165 StorageManagerInternal smInternal = 166 mInjector.getLocalService(StorageManagerInternal.class); 167 long freeBytesRequired = bytes - file.getUsableSpace(); 168 if (freeBytesRequired > 0) { 169 smInternal.freeCache(volumeUuid, freeBytesRequired); 170 } 171 172 // 12. Clear temp install session files 173 mPm.mInstallerService.freeStageDirs(volumeUuid); 174 } else { 175 try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { 176 mPm.mInstaller.freeCache(volumeUuid, bytes, 0); 177 } catch (Installer.InstallerException ignored) { 178 } 179 } 180 if (file.getUsableSpace() >= bytes) return; 181 182 throw new IOException("Failed to free " + bytes + " on storage device at " + file); 183 } 184 freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite, String resolvedPath, String mPackageAbiOverride, int installFlags)185 int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite, 186 String resolvedPath, String mPackageAbiOverride, int installFlags) { 187 // TODO: focus freeing disk space on the target device 188 final StorageManager storage = StorageManager.from(mContext); 189 final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory()); 190 191 final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath, 192 mPackageAbiOverride); 193 if (sizeBytes >= 0) { 194 try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { 195 mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0); 196 PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo( 197 mContext, pkgLite, resolvedPath, installFlags, 198 mPackageAbiOverride); 199 // The cache free must have deleted the file we downloaded to install. 200 if (pkgInfoLite.recommendedInstallLocation 201 == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) { 202 pkgInfoLite.recommendedInstallLocation = 203 InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 204 } 205 return pkgInfoLite.recommendedInstallLocation; 206 } catch (Installer.InstallerException e) { 207 Slog.w(TAG, "Failed to free cache", e); 208 } 209 } 210 return recommendedInstallLocation; 211 } 212 performFstrimIfNeeded()213 void performFstrimIfNeeded() { 214 PackageManagerServiceUtils.enforceSystemOrRoot("Only the system can request fstrim"); 215 216 // Before everything else, see whether we need to fstrim. 217 try { 218 IStorageManager sm = InstallLocationUtils.getStorageManager(); 219 if (sm != null) { 220 boolean doTrim = false; 221 final long interval = android.provider.Settings.Global.getLong( 222 mContext.getContentResolver(), 223 android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL, 224 DEFAULT_MANDATORY_FSTRIM_INTERVAL); 225 if (interval > 0) { 226 final long timeSinceLast = System.currentTimeMillis() - sm.lastMaintenance(); 227 if (timeSinceLast > interval) { 228 doTrim = true; 229 Slog.w(TAG, "No disk maintenance in " + timeSinceLast 230 + "; running immediately"); 231 } 232 } 233 if (doTrim) { 234 sm.runMaintenance(); 235 } 236 } else { 237 Slog.e(TAG, "storageManager service unavailable!"); 238 } 239 } catch (RemoteException e) { 240 // Can't happen; StorageManagerService is local 241 } 242 } 243 } 244