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