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