1 /*
2  * Copyright (C) 2016 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.InstructionSets.getAppDexInstructionSets;
20 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
21 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
22 
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.content.pm.IOtaDexopt;
26 import android.content.pm.PackageManagerInternal;
27 import android.os.Environment;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.os.ServiceManager;
31 import android.os.ShellCallback;
32 import android.os.storage.StorageManager;
33 import android.util.ArrayMap;
34 import android.util.Log;
35 import android.util.Slog;
36 
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.server.LocalServices;
39 import com.android.server.pm.Installer.InstallerException;
40 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
41 import com.android.server.pm.dex.DexoptOptions;
42 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
43 import com.android.server.pm.pkg.AndroidPackage;
44 import com.android.server.pm.pkg.PackageStateInternal;
45 
46 import java.io.File;
47 import java.io.FileDescriptor;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.List;
53 import java.util.concurrent.TimeUnit;
54 import java.util.function.Predicate;
55 
56 /**
57  * A service for A/B OTA dexopting.
58  *
59  * {@hide}
60  */
61 public class OtaDexoptService extends IOtaDexopt.Stub {
62     private final static String TAG = "OTADexopt";
63     private final static boolean DEBUG_DEXOPT = true;
64 
65     // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
66     // not bulk-delete unused apps' odex files.
67     private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024;  // 1GB.
68 
69     private final Context mContext;
70     private final PackageManagerService mPackageManagerService;
71     private final MetricsLogger metricsLogger;
72 
73     // TODO: Evaluate the need for WeakReferences here.
74 
75     /**
76      * The list of dexopt invocations for all work.
77      */
78     private List<String> mDexoptCommands;
79 
80     private int completeSize;
81 
82     // MetricsLogger properties.
83 
84     // Space before and after.
85     private long availableSpaceBefore;
86     private long availableSpaceAfterBulkDelete;
87     private long availableSpaceAfterDexopt;
88 
89     // Packages.
90     private int importantPackageCount;
91     private int otherPackageCount;
92 
93     // Number of dexopt commands. This may be different from the count of packages.
94     private int dexoptCommandCountTotal;
95     private int dexoptCommandCountExecuted;
96 
97     // For spent time.
98     private long otaDexoptTimeStart;
99 
100 
OtaDexoptService(Context context, PackageManagerService packageManagerService)101     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
102         this.mContext = context;
103         this.mPackageManagerService = packageManagerService;
104         metricsLogger = new MetricsLogger();
105     }
106 
main(Context context, PackageManagerService packageManagerService)107     public static OtaDexoptService main(Context context,
108             PackageManagerService packageManagerService) {
109         OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
110         ServiceManager.addService("otadexopt", ota);
111 
112         // Now it's time to check whether we need to move any A/B artifacts.
113         ota.moveAbArtifacts(packageManagerService.mInstaller);
114 
115         return ota;
116     }
117 
118     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)119     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
120             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
121         (new OtaDexoptShellCommand(this)).exec(
122                 this, in, out, err, args, callback, resultReceiver);
123     }
124 
125     @Override
prepare()126     public synchronized void prepare() throws RemoteException {
127         if (mDexoptCommands != null) {
128             throw new IllegalStateException("already called prepare()");
129         }
130         final List<PackageStateInternal> important;
131         final List<PackageStateInternal> others;
132         Predicate<PackageStateInternal> isPlatformPackage = pkgSetting ->
133                 PLATFORM_PACKAGE_NAME.equals(pkgSetting.getPkg().getPackageName());
134         // Important: the packages we need to run with ab-ota compiler-reason.
135         final Computer snapshot = mPackageManagerService.snapshotComputer();
136         final Collection<? extends PackageStateInternal> allPackageStates =
137                 snapshot.getPackageStates().values();
138         important = DexOptHelper.getPackagesForDexopt(allPackageStates,mPackageManagerService,
139                 DEBUG_DEXOPT);
140         // Remove Platform Package from A/B OTA b/160735835.
141         important.removeIf(isPlatformPackage);
142         // Others: we should optimize this with the (first-)boot compiler-reason.
143         others = new ArrayList<>(allPackageStates);
144         others.removeAll(important);
145         others.removeIf(PackageManagerServiceUtils.REMOVE_IF_NULL_PKG);
146         others.removeIf(PackageManagerServiceUtils.REMOVE_IF_APEX_PKG);
147         others.removeIf(isPlatformPackage);
148 
149         // Pre-size the array list by over-allocating by a factor of 1.5.
150         mDexoptCommands = new ArrayList<>(3 * allPackageStates.size() / 2);
151 
152         for (PackageStateInternal pkgSetting : important) {
153             mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
154                     PackageManagerService.REASON_AB_OTA));
155         }
156         for (PackageStateInternal pkgSetting : others) {
157             // We assume here that there are no core apps left.
158             if (pkgSetting.getPkg().isCoreApp()) {
159                 throw new IllegalStateException("Found a core app that's not important");
160             }
161             // Use REASON_FIRST_BOOT to query "pm.dexopt.first-boot" for the compiler filter, but
162             // the reason itself won't make it into the actual compiler reason because it will be
163             // overridden in otapreopt.cpp.
164             mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
165                     PackageManagerService.REASON_FIRST_BOOT));
166         }
167         completeSize = mDexoptCommands.size();
168 
169         long spaceAvailable = getAvailableSpace();
170         if (spaceAvailable < BULK_DELETE_THRESHOLD) {
171             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
172                     + DexOptHelper.packagesToString(others));
173             for (PackageStateInternal pkg : others) {
174                 mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
175             }
176         }
177         long spaceAvailableNow = getAvailableSpace();
178 
179         prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
180 
181         if (DEBUG_DEXOPT) {
182             try {
183                 // Output some data about the packages.
184                 PackageStateInternal lastUsed = Collections.max(important,
185                         Comparator.comparingLong(pkgSetting -> pkgSetting.getTransientState()
186                                 .getLatestForegroundPackageUseTimeInMills()));
187                 Log.d(TAG, "A/B OTA: lastUsed time = "
188                         + lastUsed.getTransientState().getLatestForegroundPackageUseTimeInMills());
189                 Log.d(TAG, "A/B OTA: deprioritized packages:");
190                 for (PackageStateInternal pkgSetting : others) {
191                     Log.d(TAG, "  " + pkgSetting.getPackageName() + " - "
192                             + pkgSetting.getTransientState()
193                             .getLatestForegroundPackageUseTimeInMills());
194                 }
195             } catch (RuntimeException ignored) {
196             }
197         }
198     }
199 
200     @Override
cleanup()201     public synchronized void cleanup() throws RemoteException {
202         if (DEBUG_DEXOPT) {
203             Log.i(TAG, "Cleaning up OTA Dexopt state.");
204         }
205         mDexoptCommands = null;
206         availableSpaceAfterDexopt = getAvailableSpace();
207 
208         performMetricsLogging();
209     }
210 
211     @Override
isDone()212     public synchronized boolean isDone() throws RemoteException {
213         if (mDexoptCommands == null) {
214             throw new IllegalStateException("done() called before prepare()");
215         }
216 
217         return mDexoptCommands.isEmpty();
218     }
219 
220     @Override
getProgress()221     public synchronized float getProgress() throws RemoteException {
222         // Approximate the progress by the amount of already completed commands.
223         if (completeSize == 0) {
224             return 1f;
225         }
226         int commandsLeft = mDexoptCommands.size();
227         return (completeSize - commandsLeft) / ((float)completeSize);
228     }
229 
230     @Override
nextDexoptCommand()231     public synchronized String nextDexoptCommand() throws RemoteException {
232         if (mDexoptCommands == null) {
233             throw new IllegalStateException("dexoptNextPackage() called before prepare()");
234         }
235 
236         if (mDexoptCommands.isEmpty()) {
237             return "(all done)";
238         }
239 
240         String next = mDexoptCommands.remove(0);
241 
242         if (getAvailableSpace() > 0) {
243             dexoptCommandCountExecuted++;
244 
245             Log.d(TAG, "Next command: " + next);
246             return next;
247         } else {
248             if (DEBUG_DEXOPT) {
249                 Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
250                         + (mDexoptCommands.size() + 1) + " commands left.");
251             }
252             mDexoptCommands.clear();
253             return "(no free space)";
254         }
255     }
256 
getMainLowSpaceThreshold()257     private long getMainLowSpaceThreshold() {
258         File dataDir = Environment.getDataDirectory();
259         @SuppressWarnings("deprecation")
260         long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
261         if (lowThreshold == 0) {
262             throw new IllegalStateException("Invalid low memory threshold");
263         }
264         return lowThreshold;
265     }
266 
267     /**
268      * Returns the difference of free space to the low-storage-space threshold. Positive values
269      * indicate free bytes.
270      */
getAvailableSpace()271     private long getAvailableSpace() {
272         // TODO: If apps are not installed in the internal /data partition, we should compare
273         //       against that storage's free capacity.
274         long lowThreshold = getMainLowSpaceThreshold();
275 
276         File dataDir = Environment.getDataDirectory();
277         long usableSpace = dataDir.getUsableSpace();
278 
279         return usableSpace - lowThreshold;
280     }
281 
282     /**
283      * Generate all dexopt commands for the given package.
284      */
generatePackageDexopts(AndroidPackage pkg, PackageStateInternal pkgSetting, int compilationReason)285     private synchronized List<String> generatePackageDexopts(AndroidPackage pkg,
286             PackageStateInternal pkgSetting, int compilationReason) {
287         // Intercept and collect dexopt requests
288         final List<String> commands = new ArrayList<String>();
289         final Installer collectingInstaller = new Installer(mContext, true) {
290             /**
291              * Encode the dexopt command into a string.
292              *
293              * Note: If you have to change the signature of this function, increase the version
294              *       number, and update the counterpart in
295              *       frameworks/native/cmds/installd/otapreopt.cpp.
296              */
297             @Override
298             public boolean dexopt(String apkPath, int uid, @Nullable String pkgName,
299                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
300                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
301                     @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
302                     int targetSdkVersion, @Nullable String profileName,
303                     @Nullable String dexMetadataPath, @Nullable String dexoptCompilationReason)
304                     throws InstallerException {
305                 final StringBuilder builder = new StringBuilder();
306 
307                 if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
308                     // installd may change the reference profile in place for secondary dex
309                     // files, which isn't safe with the lock free approach in ART Service.
310                     throw new IllegalArgumentException("Invalid OTA dexopt call for secondary dex");
311                 }
312 
313                 // The current version. For v10, see b/115993344.
314                 builder.append("10 ");
315 
316                 builder.append("dexopt");
317 
318                 encodeParameter(builder, apkPath);
319                 encodeParameter(builder, uid);
320                 encodeParameter(builder, pkgName);
321                 encodeParameter(builder, instructionSet);
322                 encodeParameter(builder, dexoptNeeded);
323                 encodeParameter(builder, outputPath);
324                 encodeParameter(builder, dexFlags);
325                 encodeParameter(builder, compilerFilter);
326                 encodeParameter(builder, volumeUuid);
327                 encodeParameter(builder, sharedLibraries);
328                 encodeParameter(builder, seInfo);
329                 encodeParameter(builder, downgrade);
330                 encodeParameter(builder, targetSdkVersion);
331                 encodeParameter(builder, profileName);
332                 encodeParameter(builder, dexMetadataPath);
333                 encodeParameter(builder, dexoptCompilationReason);
334 
335                 commands.add(builder.toString());
336 
337                 // Cancellation cannot happen for OtaDexOpt. Returns true always.
338                 return true;
339             }
340 
341             /**
342              * Encode a parameter as necessary for the commands string.
343              */
344             private void encodeParameter(StringBuilder builder, Object arg) {
345                 builder.append(' ');
346 
347                 if (arg == null) {
348                     builder.append('!');
349                     return;
350                 }
351 
352                 String txt = String.valueOf(arg);
353                 if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
354                     throw new IllegalArgumentException(
355                             "Invalid argument while executing " + arg);
356                 }
357                 builder.append(txt);
358             }
359         };
360 
361         // Use the package manager install and install lock here for the OTA dex optimizer.
362         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
363                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
364 
365         try {
366             optimizer.performDexOpt(pkg, pkgSetting, null /* ISAs */,
367                     null /* CompilerStats.PackageStats */,
368                     mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(
369                             pkg.getPackageName()),
370                     new DexoptOptions(pkg.getPackageName(), compilationReason,
371                             DexoptOptions.DEXOPT_BOOT_COMPLETE));
372         } catch (LegacyDexoptDisabledException e) {
373             // OTA is still allowed to use the legacy dexopt code even when ART Service is enabled.
374             // The installer is isolated and won't call into installd, and the dexopt() method is
375             // overridden to only collect the command above. Hence we shouldn't go into any code
376             // path where this exception is thrown.
377             Slog.wtf(TAG, e);
378         }
379 
380         // ART Service compat note: These commands are consumed by the otapreopt binary, which uses
381         // the same legacy dexopt code as installd to invoke dex2oat. It provides output path
382         // implementations (see calculate_odex_file_path and create_cache_path in
383         // frameworks/native/cmds/installd/otapreopt.cpp) to write to different odex files than
384         // those used by ART Service in its ordinary operations, so it doesn't interfere with ART
385         // Service even when dalvik.vm.useartservice is true.
386         return commands;
387     }
388 
389     @Override
dexoptNextPackage()390     public synchronized void dexoptNextPackage() throws RemoteException {
391         throw new UnsupportedOperationException();
392     }
393 
moveAbArtifacts(Installer installer)394     private void moveAbArtifacts(Installer installer) {
395         if (mDexoptCommands != null) {
396             throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
397         }
398 
399         if (!mPackageManagerService.isDeviceUpgrading()) {
400             Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
401             return;
402         }
403 
404         // Make a copy of all packages and look into each package.
405         final ArrayMap<String, ? extends PackageStateInternal> packageStates =
406                 LocalServices.getService(PackageManagerInternal.class).getPackageStates();
407         int packagePaths = 0;
408         int pathsSuccessful = 0;
409         for (int index = 0; index < packageStates.size(); index++) {
410             final PackageStateInternal packageState = packageStates.valueAt(index);
411             final AndroidPackage pkg = packageState.getPkg();
412             if (pkg == null) {
413                 continue;
414             }
415 
416             // Does the package have code? If not, there won't be any artifacts.
417             if (!mPackageManagerService.mPackageDexOptimizer.canOptimizePackage(pkg)) {
418                 continue;
419             }
420             if (pkg.getPath() == null) {
421                 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
422                 continue;
423             }
424 
425             // If the path is in /system, /vendor, /product or /system_ext, ignore. It will
426             // have been ota-dexopted into /data/ota and moved into the dalvik-cache already.
427             if (pkg.getPath().startsWith("/system")
428                     || pkg.getPath().startsWith("/vendor")
429                     || pkg.getPath().startsWith("/product")
430                     || pkg.getPath().startsWith("/system_ext")) {
431                 continue;
432             }
433 
434             final String[] instructionSets = getAppDexInstructionSets(
435                     packageState.getPrimaryCpuAbi(),
436                     packageState.getSecondaryCpuAbi());
437             final List<String> paths =
438                     AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
439             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
440             final String packageName = pkg.getPackageName();
441             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
442                 for (String path : paths) {
443                     String oatDir = PackageDexOptimizer.getOatDir(
444                             new File(pkg.getPath())).getAbsolutePath();
445 
446                     // TODO: Check first whether there is an artifact, to save the roundtrip time.
447 
448                     packagePaths++;
449                     try {
450                         installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir);
451                         pathsSuccessful++;
452                     } catch (InstallerException e) {
453                     }
454                 }
455             }
456         }
457         Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
458     }
459 
460     /**
461      * Initialize logging fields.
462      */
prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk)463     private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
464         availableSpaceBefore = spaceBegin;
465         availableSpaceAfterBulkDelete = spaceBulk;
466         availableSpaceAfterDexopt = 0;
467 
468         importantPackageCount = important;
469         otherPackageCount = others;
470 
471         dexoptCommandCountTotal = mDexoptCommands.size();
472         dexoptCommandCountExecuted = 0;
473 
474         otaDexoptTimeStart = System.nanoTime();
475     }
476 
inMegabytes(long value)477     private static int inMegabytes(long value) {
478         long in_mega_bytes = value / (1024 * 1024);
479         if (in_mega_bytes > Integer.MAX_VALUE) {
480             Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
481             return Integer.MAX_VALUE;
482         }
483         return (int)in_mega_bytes;
484     }
485 
performMetricsLogging()486     private void performMetricsLogging() {
487         long finalTime = System.nanoTime();
488 
489         metricsLogger.histogram("ota_dexopt_available_space_before_mb",
490                 inMegabytes(availableSpaceBefore));
491         metricsLogger.histogram("ota_dexopt_available_space_after_bulk_delete_mb",
492                 inMegabytes(availableSpaceAfterBulkDelete));
493         metricsLogger.histogram("ota_dexopt_available_space_after_dexopt_mb",
494                 inMegabytes(availableSpaceAfterDexopt));
495 
496         metricsLogger.histogram("ota_dexopt_num_important_packages", importantPackageCount);
497         metricsLogger.histogram("ota_dexopt_num_other_packages", otherPackageCount);
498 
499         metricsLogger.histogram("ota_dexopt_num_commands", dexoptCommandCountTotal);
500         metricsLogger.histogram("ota_dexopt_num_commands_executed", dexoptCommandCountExecuted);
501 
502         final int elapsedTimeSeconds =
503                 (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
504         metricsLogger.histogram("ota_dexopt_time_s", elapsedTimeSeconds);
505     }
506 
507     private static class OTADexoptPackageDexOptimizer extends
508             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
OTADexoptPackageDexOptimizer(Installer installer, PackageManagerTracedLock installLock, Context context)509         OTADexoptPackageDexOptimizer(Installer installer,
510                 PackageManagerTracedLock installLock, Context context) {
511             super(installer, installLock, context, "*otadexopt*");
512         }
513     }
514 }
515