• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.art;
18 
19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
20 
21 import static com.android.server.art.ArtManagerLocal.SnapshotProfileException;
22 import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
23 import static com.android.server.art.ReasonMapping.BatchDexoptReason;
24 import static com.android.server.art.model.ArtFlags.BatchDexoptPass;
25 import static com.android.server.art.model.ArtFlags.DexoptFlags;
26 import static com.android.server.art.model.ArtFlags.PriorityClassApi;
27 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
28 import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
29 import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
30 import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
31 
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.os.Binder;
35 import android.os.Build;
36 import android.os.CancellationSignal;
37 import android.os.ParcelFileDescriptor;
38 import android.os.Process;
39 import android.system.ErrnoException;
40 import android.system.Os;
41 import android.system.StructStat;
42 
43 import androidx.annotation.RequiresApi;
44 
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.modules.utils.BasicShellCommandHandler;
48 import com.android.modules.utils.build.SdkLevel;
49 import com.android.server.art.model.ArtFlags;
50 import com.android.server.art.model.DeleteResult;
51 import com.android.server.art.model.DexoptParams;
52 import com.android.server.art.model.DexoptResult;
53 import com.android.server.art.model.DexoptStatus;
54 import com.android.server.art.model.OperationProgress;
55 import com.android.server.art.prereboot.PreRebootDriver;
56 import com.android.server.pm.PackageManagerLocal;
57 import com.android.server.pm.pkg.AndroidPackage;
58 import com.android.server.pm.pkg.PackageState;
59 
60 import libcore.io.Streams;
61 
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.FileOutputStream;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.io.OutputStream;
68 import java.io.PrintWriter;
69 import java.nio.ByteBuffer;
70 import java.nio.channels.ClosedByInterruptException;
71 import java.nio.channels.FileChannel;
72 import java.nio.file.Path;
73 import java.nio.file.Paths;
74 import java.util.ArrayList;
75 import java.util.HashMap;
76 import java.util.List;
77 import java.util.Locale;
78 import java.util.Map;
79 import java.util.UUID;
80 import java.util.concurrent.CompletableFuture;
81 import java.util.concurrent.ExecutorService;
82 import java.util.concurrent.Executors;
83 import java.util.function.Consumer;
84 import java.util.function.Function;
85 
86 /**
87  * This class handles ART shell commands.
88  *
89  * @hide
90  */
91 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
92 public final class ArtShellCommand extends BasicShellCommandHandler {
93     /** The default location for profile dumps. */
94     private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman";
95 
96     @NonNull private final Injector mInjector;
97 
98     @GuardedBy("sCancellationSignalMap")
99     @NonNull
100     private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
101 
ArtShellCommand(@onNull ArtManagerLocal artManagerLocal, @NonNull PackageManagerLocal packageManagerLocal)102     public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
103             @NonNull PackageManagerLocal packageManagerLocal) {
104         this(new Injector(artManagerLocal, packageManagerLocal));
105     }
106 
107     @VisibleForTesting
ArtShellCommand(@onNull Injector injector)108     public ArtShellCommand(@NonNull Injector injector) {
109         mInjector = injector;
110     }
111 
112     @Override
onCommand(String cmd)113     public int onCommand(String cmd) {
114         // Apps shouldn't call ART Service shell commands, not even for dexopting themselves.
115         enforceRootOrShell();
116         PrintWriter pw = getOutPrintWriter();
117         try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
118             switch (cmd) {
119                 case "compile":
120                     return handleCompile(pw, snapshot);
121                 case "reconcile-secondary-dex-files":
122                     pw.println("Warning: 'pm reconcile-secondary-dex-files' is deprecated. It is "
123                             + "now doing nothing");
124                     return 0;
125                 case "force-dex-opt":
126                     return handleForceDexopt(pw, snapshot);
127                 case "bg-dexopt-job":
128                     return handleBgDexoptJob(pw, snapshot);
129                 case "cancel-bg-dexopt-job":
130                     pw.println("Warning: 'pm cancel-bg-dexopt-job' is deprecated. It is now an "
131                             + "alias of 'pm bg-dexopt-job --cancel'");
132                     return handleCancelBgDexoptJob(pw);
133                 case "delete-dexopt":
134                     return handleDeleteDexopt(pw, snapshot);
135                 case "dump-profiles":
136                     return handleDumpProfile(pw, snapshot);
137                 case "snapshot-profile":
138                     return handleSnapshotProfile(pw, snapshot);
139                 case "art":
140                     return handleArtCommand(pw, snapshot);
141                 default:
142                     // Can't happen. Only supported commands are forwarded to ART Service.
143                     throw new IllegalArgumentException(
144                             String.format("Unexpected command '%s' forwarded to ART Service", cmd));
145             }
146         } catch (IllegalArgumentException | SnapshotProfileException e) {
147             pw.println("Error: " + e.getMessage());
148             return 1;
149         }
150     }
151 
handleArtCommand( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)152     private int handleArtCommand(
153             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
154         String subcmd = getNextArgRequired();
155         switch (subcmd) {
156             case "dexopt-packages": {
157                 return handleBatchDexopt(pw, snapshot);
158             }
159             case "cancel": {
160                 String jobId = getNextArgRequired();
161                 CancellationSignal signal;
162                 synchronized (sCancellationSignalMap) {
163                     signal = sCancellationSignalMap.getOrDefault(jobId, null);
164                 }
165                 if (signal == null) {
166                     pw.println("Job not found");
167                     return 1;
168                 }
169                 signal.cancel();
170                 pw.println("Job cancelled");
171                 return 0;
172             }
173             case "dump": {
174                 String packageName = getNextArg();
175                 if (packageName != null) {
176                     mInjector.getArtManagerLocal().dumpPackage(pw, snapshot, packageName);
177                 } else {
178                     mInjector.getArtManagerLocal().dump(pw, snapshot);
179                 }
180                 return 0;
181             }
182             case "cleanup": {
183                 return handleCleanup(pw, snapshot);
184             }
185             case "clear-app-profiles": {
186                 mInjector.getArtManagerLocal().clearAppProfiles(snapshot, getNextArgRequired());
187                 pw.println("Profiles cleared");
188                 return 0;
189             }
190             case "on-ota-staged": {
191                 return handleOnOtaStaged(pw);
192             }
193             case "pr-dexopt-job": {
194                 return handlePrDexoptJob(pw);
195             }
196             case "configure-batch-dexopt": {
197                 return handleConfigureBatchDexopt(pw);
198             }
199             default:
200                 pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
201                 pw.println("See 'pm help' for help");
202                 return 1;
203         }
204     }
205 
handleCompile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)206     private int handleCompile(
207             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
208         @DexoptFlags int scopeFlags = 0;
209         String reason = null;
210         String compilerFilter = null;
211         @PriorityClassApi int priorityClass = ArtFlags.PRIORITY_NONE;
212         String splitArg = null;
213         boolean force = false;
214         boolean reset = false;
215         boolean forAllPackages = false;
216         boolean legacyClearProfile = false;
217         boolean verbose = false;
218         boolean forceMergeProfile = false;
219         boolean forceCompilerFilter = false;
220 
221         String opt;
222         while ((opt = getNextOption()) != null) {
223             switch (opt) {
224                 case "-a":
225                     forAllPackages = true;
226                     break;
227                 case "-r":
228                     reason = getNextArgRequired();
229                     break;
230                 case "-m":
231                     compilerFilter = getNextArgRequired();
232                     forceCompilerFilter = true;
233                     break;
234                 case "-p":
235                     priorityClass = parsePriorityClass(getNextArgRequired());
236                     break;
237                 case "-f":
238                     force = true;
239                     break;
240                 case "--primary-dex":
241                     scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
242                     break;
243                 case "--secondary-dex":
244                     scopeFlags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
245                     break;
246                 case "--include-dependencies":
247                     scopeFlags |= ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
248                     break;
249                 case "--full":
250                     scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
251                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
252                     break;
253                 case "--split":
254                     splitArg = getNextArgRequired();
255                     break;
256                 case "--reset":
257                     reset = true;
258                     break;
259                 case "-c":
260                     pw.println("Warning: Flag '-c' is deprecated and usually produces undesired "
261                             + "results. Please use one of the following commands instead.");
262                     pw.println("- To clear the local profiles only, use "
263                             + "'pm art clear-app-profiles PACKAGE_NAME'. (The existing dexopt "
264                             + "artifacts will be kept, even if they are derived from the "
265                             + "profiles.)");
266                     pw.println("- To clear the local profiles and also clear the dexopt artifacts "
267                             + "that are derived from them, use 'pm compile --reset PACKAGE_NAME'. "
268                             + "(The package will be reset to the initial state as if it's newly "
269                             + "installed, which means the package will be re-dexopted if "
270                             + "necessary, and cloud profiles will be used if exist.)");
271                     pw.println("- To re-dexopt the package with no profile, use "
272                             + "'pm compile -m verify -f PACKAGE_NAME'. (The local profiles "
273                             + "will be kept but not used during the dexopt. The dexopt artifacts "
274                             + "are guaranteed to have no compiled code.)");
275                     legacyClearProfile = true;
276                     break;
277                 case "--check-prof":
278                     getNextArgRequired();
279                     pw.println("Warning: Ignoring obsolete flag '--check-prof'. It is "
280                             + "unconditionally enabled now");
281                     break;
282                 case "-v":
283                     verbose = true;
284                     break;
285                 case "--force-merge-profile":
286                     forceMergeProfile = true;
287                     break;
288                 default:
289                     pw.println("Error: Unknown option: " + opt);
290                     return 1;
291             }
292         }
293 
294         List<String> packageNames = forAllPackages
295                 ? List.copyOf(snapshot.getPackageStates().keySet())
296                 : List.of(getNextArgRequired());
297 
298         var paramsBuilder = new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE);
299         if (reason != null) {
300             if (reason.equals(ReasonMapping.REASON_INACTIVE)) {
301                 pw.println("Warning: '-r inactive' produces undesired results.");
302             }
303             if (compilerFilter == null) {
304                 paramsBuilder.setCompilerFilter(ReasonMapping.getCompilerFilterForReason(reason));
305             }
306             if (priorityClass == ArtFlags.PRIORITY_NONE) {
307                 paramsBuilder.setPriorityClass(ReasonMapping.getPriorityClassForReason(reason));
308             }
309         }
310         if (compilerFilter != null) {
311             paramsBuilder.setCompilerFilter(compilerFilter);
312         }
313         if (priorityClass != ArtFlags.PRIORITY_NONE) {
314             paramsBuilder.setPriorityClass(priorityClass);
315         }
316         if (force) {
317             paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
318         }
319         if (forceMergeProfile) {
320             paramsBuilder.setFlags(
321                     ArtFlags.FLAG_FORCE_MERGE_PROFILE, ArtFlags.FLAG_FORCE_MERGE_PROFILE);
322         }
323         if (forceCompilerFilter) {
324             paramsBuilder.setFlags(
325                     ArtFlags.FLAG_FORCE_COMPILER_FILTER, ArtFlags.FLAG_FORCE_COMPILER_FILTER);
326         }
327         if (splitArg != null) {
328             if (scopeFlags != 0) {
329                 pw.println("Error: '--primary-dex', '--secondary-dex', "
330                         + "'--include-dependencies', or '--full' must not be set when '--split' "
331                         + "is set.");
332                 return 1;
333             }
334             if (forAllPackages) {
335                 pw.println("Error:  '-a' cannot be specified together with '--split'");
336                 return 1;
337             }
338             scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX;
339             paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT, ArtFlags.FLAG_FOR_SINGLE_SPLIT)
340                     .setSplitName(getSplitName(pw, snapshot, packageNames.get(0), splitArg));
341         }
342         if (scopeFlags != 0) {
343             paramsBuilder.setFlags(scopeFlags,
344                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
345                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
346         } else {
347             paramsBuilder.setFlags(
348                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
349                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
350                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
351         }
352         if (forAllPackages) {
353             // We'll iterate over all packages anyway.
354             paramsBuilder.setFlags(0, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
355         }
356 
357         if (reset) {
358             return resetPackages(pw, snapshot, packageNames, verbose);
359         } else {
360             if (legacyClearProfile) {
361                 // For compat only. Combining this with dexopt usually produces in undesired
362                 // results.
363                 for (String packageName : packageNames) {
364                     mInjector.getArtManagerLocal().clearAppProfiles(snapshot, packageName);
365                 }
366             }
367             return dexoptPackages(pw, snapshot, packageNames, paramsBuilder.build(), verbose);
368         }
369     }
370 
handleForceDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)371     private int handleForceDexopt(
372             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
373         pw.println("Warning: 'pm force-dex-opt' is deprecated. Please use 'pm compile "
374                 + "-f PACKAGE_NAME' instead");
375         return dexoptPackages(pw, snapshot, List.of(getNextArgRequired()),
376                 new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE)
377                         .setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE)
378                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX
379                                         | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
380                                 ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
381                                         | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
382                         .build(),
383                 false /* verbose */);
384     }
385 
handleBgDexoptJob( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)386     private int handleBgDexoptJob(
387             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
388         String opt = getNextOption();
389         if (opt == null) {
390             List<String> packageNames = new ArrayList<>();
391             String arg;
392             while ((arg = getNextArg()) != null) {
393                 packageNames.add(arg);
394             }
395             if (!packageNames.isEmpty()) {
396                 pw.println("Warning: Running 'pm bg-dexopt-job' with package names is deprecated. "
397                         + "Please use 'pm compile -r bg-dexopt PACKAGE_NAME' instead");
398                 return dexoptPackages(pw, snapshot, packageNames,
399                         new DexoptParams.Builder(ReasonMapping.REASON_BG_DEXOPT).build(),
400                         false /* verbose */);
401             }
402 
403             CompletableFuture<BackgroundDexoptJob.Result> runningJob =
404                     mInjector.getArtManagerLocal().getRunningBackgroundDexoptJob();
405             if (runningJob != null) {
406                 pw.println("Another job already running. Waiting for it to finish... To cancel it, "
407                         + "run 'pm bg-dexopt-job --cancel'. in a separate shell.");
408                 pw.flush();
409                 Utils.getFuture(runningJob);
410             }
411             CompletableFuture<BackgroundDexoptJob.Result> future =
412                     mInjector.getArtManagerLocal().startBackgroundDexoptJobAndReturnFuture();
413             pw.println("Job running...  To cancel it, run 'pm bg-dexopt-job --cancel'. in a "
414                     + "separate shell.");
415             pw.flush();
416             BackgroundDexoptJob.Result result = Utils.getFuture(future);
417             if (result instanceof BackgroundDexoptJob.CompletedResult) {
418                 var completedResult = (BackgroundDexoptJob.CompletedResult) result;
419                 if (completedResult.isCancelled()) {
420                     pw.println("Job cancelled. See logs for details");
421                 } else {
422                     pw.println("Job finished. See logs for details");
423                 }
424             } else if (result instanceof BackgroundDexoptJob.FatalErrorResult) {
425                 // Never expected.
426                 pw.println("Job encountered a fatal error");
427             }
428             return 0;
429         }
430         switch (opt) {
431             case "--cancel": {
432                 return handleCancelBgDexoptJob(pw);
433             }
434             case "--enable": {
435                 // This operation requires the uid to be "system" (1000).
436                 long identityToken = Binder.clearCallingIdentity();
437                 try {
438                     mInjector.getArtManagerLocal().scheduleBackgroundDexoptJob();
439                 } finally {
440                     Binder.restoreCallingIdentity(identityToken);
441                 }
442                 pw.println("Background dexopt job enabled");
443                 return 0;
444             }
445             case "--disable": {
446                 // This operation requires the uid to be "system" (1000).
447                 long identityToken = Binder.clearCallingIdentity();
448                 try {
449                     mInjector.getArtManagerLocal().unscheduleBackgroundDexoptJob();
450                 } finally {
451                     Binder.restoreCallingIdentity(identityToken);
452                 }
453                 pw.println("Background dexopt job disabled");
454                 return 0;
455             }
456             default:
457                 pw.println("Error: Unknown option: " + opt);
458                 return 1;
459         }
460     }
461 
handleCancelBgDexoptJob(@onNull PrintWriter pw)462     private int handleCancelBgDexoptJob(@NonNull PrintWriter pw) {
463         mInjector.getArtManagerLocal().cancelBackgroundDexoptJob();
464         pw.println("Background dexopt job cancelled");
465         return 0;
466     }
467 
handleCleanup( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)468     private int handleCleanup(
469             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
470         long freedBytes = mInjector.getArtManagerLocal().cleanup(snapshot);
471         pw.printf("Freed %d bytes\n", freedBytes);
472         return 0;
473     }
474 
handleDeleteDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)475     private int handleDeleteDexopt(
476             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
477         DeleteResult result = mInjector.getArtManagerLocal().deleteDexoptArtifacts(
478                 snapshot, getNextArgRequired());
479         pw.printf("Freed %d bytes\n", result.getFreedBytes());
480         return 0;
481     }
482 
handleSnapshotProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)483     private int handleSnapshotProfile(
484             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
485             throws SnapshotProfileException {
486         String splitName = null;
487         String codePath = null;
488 
489         String opt;
490         while ((opt = getNextOption()) != null) {
491             switch (opt) {
492                 case "--split":
493                     splitName = getNextArgRequired();
494                     break;
495                 default:
496                     pw.println("Error: Unknown option: " + opt);
497                     return 1;
498             }
499         }
500 
501         String packageName = getNextArgRequired();
502 
503         if ("--code-path".equals(getNextOption())) {
504             pw.println("Warning: Specifying a split using '--code-path' is deprecated. Please use "
505                     + "'--split SPLIT_NAME' instead");
506             pw.println("Tip: '--split SPLIT_NAME' must be passed before the package name");
507             codePath = getNextArgRequired();
508         }
509 
510         if (splitName != null && codePath != null) {
511             pw.println("Error: '--split' and '--code-path' cannot be both specified");
512             return 1;
513         }
514 
515         if (packageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
516             if (splitName != null) {
517                 pw.println("Error: '--split' must not be specified for boot image profile");
518                 return 1;
519             }
520             if (codePath != null) {
521                 pw.println("Error: '--code-path' must not be specified for boot image profile");
522                 return 1;
523             }
524             return handleSnapshotBootProfile(pw, snapshot);
525         }
526 
527         if (splitName != null && splitName.isEmpty()) {
528             splitName = null;
529         }
530         if (codePath != null) {
531             splitName = getSplitNameByFullPath(snapshot, packageName, codePath);
532         }
533 
534         return handleSnapshotAppProfile(pw, snapshot, packageName, splitName);
535     }
536 
handleSnapshotBootProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)537     private int handleSnapshotBootProfile(
538             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
539             throws SnapshotProfileException {
540         String outputRelativePath = "android.prof";
541         ParcelFileDescriptor fd = mInjector.getArtManagerLocal().snapshotBootImageProfile(snapshot);
542         writeProfileFdContentsToFile(pw, fd, outputRelativePath);
543         return 0;
544     }
545 
handleSnapshotAppProfile(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName)546     private int handleSnapshotAppProfile(@NonNull PrintWriter pw,
547             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
548             @Nullable String splitName) throws SnapshotProfileException {
549         String outputRelativePath = String.format("%s%s.prof", packageName,
550                 splitName != null ? String.format("-split_%s.apk", splitName) : "");
551         ParcelFileDescriptor fd =
552                 mInjector.getArtManagerLocal().snapshotAppProfile(snapshot, packageName, splitName);
553         writeProfileFdContentsToFile(pw, fd, outputRelativePath);
554         return 0;
555     }
556 
handleDumpProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)557     private int handleDumpProfile(
558             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
559             throws SnapshotProfileException {
560         boolean dumpClassesAndMethods = false;
561 
562         String opt;
563         while ((opt = getNextOption()) != null) {
564             switch (opt) {
565                 case "--dump-classes-and-methods": {
566                     dumpClassesAndMethods = true;
567                     break;
568                 }
569                 default:
570                     pw.println("Error: Unknown option: " + opt);
571                     return 1;
572             }
573         }
574 
575         String packageName = getNextArgRequired();
576 
577         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
578         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
579         try (var tracing = new Utils.Tracing("dump profiles")) {
580             // `flushProfiles` may take time and may have unexpected side-effects (e.g., when the
581             // app has its own thread waiting for SIGUSR1). Therefore, We call it in the shell
582             // command handler instead of in `dumpAppProfile` to prevent existing Java API users
583             // from being impacted by this behavior.
584             pw.println("Waiting for app processes to flush profiles...");
585             pw.flush();
586             long startTimeMs = System.currentTimeMillis();
587             if (mInjector.getArtManagerLocal().flushProfiles(snapshot, packageName)) {
588                 pw.printf("App processes flushed profiles in %dms\n",
589                         System.currentTimeMillis() - startTimeMs);
590             } else {
591                 pw.println("Timed out while waiting for app processes to flush profiles");
592             }
593 
594             for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
595                 String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName());
596                 // The path is intentionally inconsistent with the one for "snapshot-profile". This
597                 // is to match the behavior of the legacy PM shell command.
598                 String outputRelativePath =
599                         String.format("%s-%s.prof.txt", packageName, profileName);
600                 ParcelFileDescriptor fd = mInjector.getArtManagerLocal().dumpAppProfile(
601                         snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods);
602                 writeProfileFdContentsToFile(pw, fd, outputRelativePath);
603             }
604         }
605         return 0;
606     }
607 
handleBatchDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)608     private int handleBatchDexopt(
609             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
610         String reason = null;
611         String opt;
612         while ((opt = getNextOption()) != null) {
613             switch (opt) {
614                 case "-r":
615                     reason = getNextArgRequired();
616                     break;
617                 default:
618                     pw.println("Error: Unknown option: " + opt);
619                     return 1;
620             }
621         }
622         if (reason == null) {
623             pw.println("Error: '-r REASON' is required");
624             return 1;
625         }
626         if (!ReasonMapping.BATCH_DEXOPT_REASONS.contains(reason)) {
627             pw.printf("Error: Invalid batch dexopt reason '%s'. Valid values are: %s\n", reason,
628                     ReasonMapping.BATCH_DEXOPT_REASONS);
629             return 1;
630         }
631 
632         final String finalReason = reason;
633 
634         // Create callbacks to print the progress.
635         Map<Integer, Consumer<OperationProgress>> progressCallbacks = new HashMap<>();
636         for (@BatchDexoptPass int pass : ArtFlags.BATCH_DEXOPT_PASSES) {
637             progressCallbacks.put(pass, progress -> {
638                 pw.println(String.format(Locale.US, "%s: %d%%",
639                         getProgressMessageForBatchDexoptPass(pass, finalReason),
640                         progress.getPercentage()));
641                 pw.flush();
642             });
643         }
644 
645         ExecutorService progressCallbackExecutor = Executors.newSingleThreadExecutor();
646         try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
647             Map<Integer, DexoptResult> results =
648                     mInjector.getArtManagerLocal().dexoptPackages(snapshot, finalReason,
649                             signal.get(), progressCallbackExecutor, progressCallbacks);
650 
651             Utils.executeAndWait(progressCallbackExecutor, () -> {
652                 for (@BatchDexoptPass int pass : ArtFlags.BATCH_DEXOPT_PASSES) {
653                     if (results.containsKey(pass)) {
654                         pw.println("Result of "
655                                 + getProgressMessageForBatchDexoptPass(pass, finalReason)
656                                           .toLowerCase(Locale.US)
657                                 + ":");
658                         printDexoptResult(
659                                 pw, results.get(pass), true /* verbose */, true /* multiPackage */);
660                     }
661                 }
662             });
663         } finally {
664             progressCallbackExecutor.shutdown();
665         }
666 
667         return 0;
668     }
669 
handleOnOtaStaged(@onNull PrintWriter pw)670     private int handleOnOtaStaged(@NonNull PrintWriter pw) {
671         if (!SdkLevel.isAtLeastV()) {
672             pw.println("Error: Unsupported command 'on-ota-staged'");
673             return 1;
674         }
675 
676         int uid = mInjector.getCallingUid();
677         if (uid != Process.ROOT_UID) {
678             throw new SecurityException("Only root can call 'on-ota-staged'");
679         }
680 
681         String mode = null;
682         String otaSlot = null;
683 
684         String opt;
685         while ((opt = getNextOption()) != null) {
686             switch (opt) {
687                 case "--slot":
688                     otaSlot = getNextArgRequired();
689                     break;
690                 case "--start":
691                     mode = opt;
692                     break;
693                 default:
694                     pw.println("Error: Unknown option: " + opt);
695                     return 1;
696             }
697         }
698 
699         if ("--start".equals(mode)) {
700             if (otaSlot != null) {
701                 pw.println("Error: '--slot' cannot be specified together with '--start'");
702                 return 1;
703             }
704             return handleOnOtaStagedStart(pw);
705         } else {
706             if (otaSlot == null) {
707                 pw.println("Error: '--slot' must be specified");
708                 return 1;
709             }
710 
711             if (mInjector.getArtManagerLocal().getPreRebootDexoptJob().isAsyncForOta()) {
712                 return handleSchedulePrDexoptJob(pw, otaSlot);
713             } else {
714                 // In the synchronous case, `update_engine` has already mapped snapshots for us.
715                 return handleRunPrDexoptJob(pw, otaSlot, true /* isUpdateEngineReady */);
716             }
717         }
718     }
719 
handlePrDexoptJob(@onNull PrintWriter pw)720     private int handlePrDexoptJob(@NonNull PrintWriter pw) {
721         if (!SdkLevel.isAtLeastV()) {
722             pw.println("Error: Unsupported command 'pr-dexopt-job'");
723             return 1;
724         }
725 
726         String mode = null;
727         String otaSlot = null;
728 
729         String opt;
730         while ((opt = getNextOption()) != null) {
731             switch (opt) {
732                 case "--slot":
733                     otaSlot = getNextArgRequired();
734                     break;
735                 case "--version":
736                 case "--test":
737                 case "--run":
738                 case "--schedule":
739                 case "--cancel":
740                     if (mode != null) {
741                         pw.println("Error: Only one mode can be specified");
742                         return 1;
743                     }
744                     mode = opt;
745                     break;
746                 default:
747                     pw.println("Error: Unknown option: " + opt);
748                     return 1;
749             }
750         }
751 
752         if (mode == null) {
753             pw.println("Error: No mode specified");
754             return 1;
755         }
756 
757         if (otaSlot != null && mInjector.getCallingUid() != Process.ROOT_UID) {
758             throw new SecurityException("Only root can specify '--slot'");
759         }
760 
761         switch (mode) {
762             case "--version":
763                 pw.println(3);
764                 return 0;
765             case "--test":
766                 return handleTestPrDexoptJob(pw);
767             case "--run":
768                 // Passing isUpdateEngineReady=false will make the job call update_engine's
769                 // triggerPostinstall to map the snapshot devices if the API is available.
770                 // It's always safe to do so because triggerPostinstall can be called at any time
771                 // any number of times to map the snapshots if any are available.
772                 return handleRunPrDexoptJob(pw, otaSlot, false /* isUpdateEngineReady */);
773             case "--schedule":
774                 return handleSchedulePrDexoptJob(pw, otaSlot);
775             case "--cancel":
776                 return handleCancelPrDexoptJob(pw);
777             default:
778                 // Can't happen.
779                 throw new IllegalStateException("Unknown mode: " + mode);
780         }
781     }
782 
783     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handleTestPrDexoptJob(@onNull PrintWriter pw)784     private int handleTestPrDexoptJob(@NonNull PrintWriter pw) {
785         try {
786             mInjector.getArtManagerLocal().getPreRebootDexoptJob().test();
787             pw.println("Success");
788             return 0;
789         } catch (Exception e) {
790             pw.println("Failure");
791             e.printStackTrace(pw);
792             return 2; // "1" is for general errors. Use "2" for the test failure.
793         }
794     }
795 
796     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handleRunPrDexoptJob( @onNull PrintWriter pw, @Nullable String otaSlot, boolean isUpdateEngineReady)797     private int handleRunPrDexoptJob(
798             @NonNull PrintWriter pw, @Nullable String otaSlot, boolean isUpdateEngineReady) {
799         PreRebootDexoptJob job = mInjector.getArtManagerLocal().getPreRebootDexoptJob();
800 
801         CompletableFuture<Void> future = job.onUpdateReadyStartNow(otaSlot, isUpdateEngineReady);
802         if (future == null) {
803             pw.println("Job disabled by system property");
804             return 1;
805         }
806 
807         return handlePrDexoptJobRunning(pw, future);
808     }
809 
810     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handleOnOtaStagedStart(@onNull PrintWriter pw)811     private int handleOnOtaStagedStart(@NonNull PrintWriter pw) {
812         PreRebootDexoptJob job = mInjector.getArtManagerLocal().getPreRebootDexoptJob();
813 
814         // We assume we're being invoked from within `UpdateEngine.triggerPostinstall` in
815         // `PreRebootDexoptJob.triggerUpdateEnginePostinstallAndWait`, so a Pre-reboot Dexopt job is
816         // waiting.
817         CompletableFuture<Void> future = job.notifyUpdateEngineReady();
818         if (future == null) {
819             pw.println("No waiting job found");
820             return 1;
821         }
822 
823         return handlePrDexoptJobRunning(pw, future);
824     }
825 
826     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handlePrDexoptJobRunning( @onNull PrintWriter pw, @NonNull CompletableFuture<Void> future)827     private int handlePrDexoptJobRunning(
828             @NonNull PrintWriter pw, @NonNull CompletableFuture<Void> future) {
829         PreRebootDexoptJob job = mInjector.getArtManagerLocal().getPreRebootDexoptJob();
830 
831         // Read stdin and cancel on broken pipe, to detect if the caller (e.g. update_engine) has
832         // killed the postinstall script.
833         // Put the read in a separate thread because there isn't an easy way in Java to wait for
834         // both the `Future` and the read.
835         var readThread = new Thread(() -> {
836             try (var in = new FileInputStream(getInFileDescriptor())) {
837                 ByteBuffer buffer = ByteBuffer.allocate(128 /* capacity */);
838                 FileChannel channel = in.getChannel();
839                 while (channel.read(buffer) >= 0) {
840                     buffer.clear();
841                 }
842                 // Broken pipe.
843                 job.cancelGiven(future, true /* expectInterrupt */);
844             } catch (ClosedByInterruptException e) {
845                 // Job finished normally.
846             } catch (IOException e) {
847                 AsLog.e("Unexpected exception", e);
848                 job.cancelGiven(future, true /* expectInterrupt */);
849             } catch (RuntimeException e) {
850                 AsLog.wtf("Unexpected exception", e);
851                 job.cancelGiven(future, true /* expectInterrupt */);
852             }
853         });
854         readThread.start();
855         pw.println("Job running...  To cancel it, press Ctrl+C or run "
856                 + "'pm art pr-dexopt-job --cancel' in a separate shell.");
857         pw.flush();
858 
859         try {
860             Utils.getFuture(future);
861             pw.println("Job finished. See logs for details");
862         } catch (RuntimeException e) {
863             pw.println("Job encountered a fatal error");
864             e.printStackTrace(pw);
865         } finally {
866             readThread.interrupt();
867             try {
868                 readThread.join();
869             } catch (InterruptedException e) {
870                 AsLog.wtf("Interrupted", e);
871             }
872         }
873 
874         return 0;
875     }
876 
877     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handleSchedulePrDexoptJob(@onNull PrintWriter pw, @Nullable String otaSlot)878     private int handleSchedulePrDexoptJob(@NonNull PrintWriter pw, @Nullable String otaSlot) {
879         int code =
880                 mInjector.getArtManagerLocal().getPreRebootDexoptJob().onUpdateReadyImpl(otaSlot);
881         switch (code) {
882             case ArtFlags.SCHEDULE_SUCCESS:
883                 pw.println("Pre-reboot Dexopt job scheduled");
884                 return 0;
885             case ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP:
886                 pw.println("Pre-reboot Dexopt job disabled by system property");
887                 return 1;
888             case ArtFlags.SCHEDULE_JOB_SCHEDULER_FAILURE:
889                 pw.println("Failed to schedule Pre-reboot Dexopt job");
890                 return 1;
891             default:
892                 // Can't happen.
893                 throw new IllegalStateException("Unknown result code: " + code);
894         }
895     }
896 
897     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
handleCancelPrDexoptJob(@onNull PrintWriter pw)898     private int handleCancelPrDexoptJob(@NonNull PrintWriter pw) {
899         mInjector.getArtManagerLocal().getPreRebootDexoptJob().cancelAny();
900         pw.println("Pre-reboot Dexopt job cancelled");
901         return 0;
902     }
903 
handleConfigureBatchDexopt(@onNull PrintWriter pw)904     private int handleConfigureBatchDexopt(@NonNull PrintWriter pw) {
905         String inputReason = null;
906         List<String> packages = new ArrayList<>();
907 
908         String opt;
909         while ((opt = getNextOption()) != null) {
910             switch (opt) {
911                 case "-r":
912                     inputReason = getNextArgRequired();
913                     break;
914                 case "--package":
915                     packages.add(getNextArgRequired());
916                     break;
917                 default:
918                     pw.println("Error: Unknown option: " + opt);
919                     return 1;
920             }
921         }
922 
923         // Variables used in lambda needs to be effectively final.
924         String finalInputReason = inputReason;
925         mInjector.getArtManagerLocal().setBatchDexoptStartCallback(
926                 Runnable::run, (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
927                     if (reason.equals(finalInputReason)) {
928                         if (!packages.isEmpty()) {
929                             builder.setPackages(packages);
930                         }
931                     }
932                 });
933 
934         return 0;
935     }
936 
937     @Override
onHelp()938     public void onHelp() {
939         // No one should call this. The help text should be printed by the `onHelp` handler of `cmd
940         // package`.
941         throw new UnsupportedOperationException("Unexpected call to 'onHelp'");
942     }
943 
printHelp(@onNull PrintWriter pw)944     public static void printHelp(@NonNull PrintWriter pw) {
945         pw.println("compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f]");
946         pw.println("    [--primary-dex] [--secondary-dex] [--include-dependencies] [--full]");
947         pw.println("    [--split SPLIT_NAME] [--reset] [-a | PACKAGE_NAME]");
948         pw.println("  Dexopt a package or all packages.");
949         pw.println("  Options:");
950         pw.println("    -a Dexopt all packages");
951         pw.println("    -r Set the compiler filter and the priority based on the given");
952         pw.println("       compilation reason.");
953         pw.println("       Available options: 'first-boot', 'boot-after-ota',");
954         pw.println("       'boot-after-mainline-update', 'install', 'bg-dexopt', 'cmdline'.");
955         pw.println("    -m Set the target compiler filter. The filter actually used may be");
956         pw.println("       different, e.g. 'speed-profile' without profiles present may result in");
957         pw.println("       'verify' being used instead. If not specified, this defaults to the");
958         pw.println("       value given by -r, or the system property 'pm.dexopt.cmdline'.");
959         pw.println("       Available options (in descending order): 'speed', 'speed-profile',");
960         pw.println("       'verify'.");
961         pw.println("    -p Set the priority of the operation, which determines the resource usage");
962         pw.println("       and the process priority. If not specified, this defaults to");
963         pw.println("       the value given by -r, or 'PRIORITY_INTERACTIVE'.");
964         pw.println("       Available options (in descending order): 'PRIORITY_BOOT',");
965         pw.println("       'PRIORITY_INTERACTIVE_FAST', 'PRIORITY_INTERACTIVE',");
966         pw.println("       'PRIORITY_BACKGROUND'.");
967         pw.println("    -f Force dexopt, also when the compiler filter being applied is not");
968         pw.println("       better than that of the current dexopt artifacts for a package.");
969         pw.println("    --reset Reset the dexopt state of the package as if the package is newly");
970         pw.println("       installed without cloud dexopt artifacts (SDM files).");
971         pw.println("       More specifically,");
972         pw.println("       - It clears current profiles, reference profiles, and all dexopt");
973         pw.println("         artifacts (including cloud dexopt artifacts).");
974         pw.println("       - If there is an external profile (e.g., a cloud profile), the");
975         pw.println("         reference profile will be re-created from that profile, and dexopt");
976         pw.println("         artifacts will be regenerated for that profile.");
977         pw.println("       For secondary dex files, it clears all profiles and dexopt artifacts");
978         pw.println("       without regeneration because secondary dex files are supposed to be");
979         pw.println("       unknown at install time.");
980         pw.println("       When this flag is set, all the other flags are ignored.");
981         pw.println("    -v Verbose mode. This mode prints detailed results.");
982         pw.println("    --force-merge-profile Force merge profiles even if the difference between");
983         pw.println("       before and after the merge is not significant.");
984         pw.println("  Scope options:");
985         pw.println("    --primary-dex Dexopt primary dex files only (all APKs that are installed");
986         pw.println("      as part of the package, including the base APK and all other split");
987         pw.println("      APKs).");
988         pw.println("    --secondary-dex Dexopt secondary dex files only (APKs/JARs that the app");
989         pw.println("      puts in its own data directory at runtime and loads with custom");
990         pw.println("      classloaders).");
991         pw.println("    --include-dependencies Include dependency packages (dependencies that are");
992         pw.println("      declared by the app with <uses-library> tags and transitive");
993         pw.println("      dependencies). This option can only be used together with");
994         pw.println("      '--primary-dex' or '--secondary-dex'.");
995         pw.println("    --full Dexopt all above. (Recommended)");
996         pw.println("    --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an empty");
997         pw.println("      string, only dexopt the base APK.");
998         pw.println("      Tip: To pass an empty string, use a pair of quotes (\"\").");
999         pw.println("      When this option is set, '--primary-dex', '--secondary-dex',");
1000         pw.println("      '--include-dependencies', '--full', and '-a' must not be set.");
1001         pw.println("    Note: If none of the scope options above are set, the scope defaults to");
1002         pw.println("    '--primary-dex --include-dependencies'.");
1003         pw.println();
1004         pw.println("delete-dexopt PACKAGE_NAME");
1005         pw.println("  Delete the dexopt artifacts of both primary dex files and secondary dex");
1006         pw.println("  files of a package, including cloud dexopt artifacts (SDM files).");
1007         pw.println();
1008         pw.println("bg-dexopt-job [--cancel | --disable | --enable]");
1009         pw.println("  Control the background dexopt job.");
1010         pw.println("  Without flags, it starts a background dexopt job immediately and waits for");
1011         pw.println("    it to finish. If a job is already started either automatically by the");
1012         pw.println("    system or through this command, it will wait for the running job to");
1013         pw.println("    finish and then start a new one.");
1014         pw.println("  Different from 'pm compile -r bg-dexopt -a', the behavior of this command");
1015         pw.println("  is the same as a real background dexopt job. Specifically,");
1016         pw.println("    - It only dexopts a subset of apps determined by either the system's");
1017         pw.println("      default logic based on app usage data or the custom logic specified by");
1018         pw.println("      the 'ArtManagerLocal.setBatchDexoptStartCallback' Java API.");
1019         pw.println("    - It runs dexopt in parallel, where the concurrency setting is specified");
1020         pw.println("      by the system property 'pm.dexopt.bg-dexopt.concurrency'.");
1021         pw.println("    - If the storage is low, it also downgrades unused apps.");
1022         pw.println("    - It also cleans up obsolete files.");
1023         pw.println("  Options:");
1024         pw.println("    --cancel Cancel any currently running background dexopt job immediately.");
1025         pw.println("      This cancels jobs started either automatically by the system or through");
1026         pw.println("      this command. This command is not blocking.");
1027         pw.println("    --disable: Disable the background dexopt job from being started by the");
1028         pw.println("      job scheduler. If a job is already started by the job scheduler and is");
1029         pw.println("      running, it will be cancelled immediately. Does not affect jobs started");
1030         pw.println("      through this command or by the system in other ways.");
1031         pw.println("      This state will be lost when the system_server process exits.");
1032         pw.println("    --enable: Enable the background dexopt job to be started by the job");
1033         pw.println("      scheduler again, if previously disabled by --disable.");
1034         pw.println("  When a list of package names is passed, this command does NOT start a real");
1035         pw.println("  background dexopt job. Instead, it dexopts the given packages sequentially.");
1036         pw.println("  This usage is deprecated. Please use 'pm compile -r bg-dexopt PACKAGE_NAME'");
1037         pw.println("  instead.");
1038         pw.println();
1039         pw.println("snapshot-profile [android | [--split SPLIT_NAME] PACKAGE_NAME]");
1040         pw.println("  Snapshot the boot image profile or the app profile and save it to");
1041         pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
1042         pw.println("  If 'android' is passed, the command snapshots the boot image profile, and");
1043         pw.println("  the output filename is 'android.prof'.");
1044         pw.println("  If a package name is passed, the command snapshots the app profile.");
1045         pw.println("  Options:");
1046         pw.println("    --split SPLIT_NAME If specified, the command snapshots the profile of the");
1047         pw.println("      given split, and the output filename is");
1048         pw.println("      'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'.");
1049         pw.println("      If not specified, the command snapshots the profile of the base APK,");
1050         pw.println("      and the output filename is 'PACKAGE_NAME.prof'");
1051         pw.println();
1052         pw.println("dump-profiles [--dump-classes-and-methods] PACKAGE_NAME");
1053         pw.println("  Dump the profiles of the given app in text format and save the outputs to");
1054         pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
1055         pw.println("  The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'");
1056         pw.println("  The profile of a split APK is dumped to");
1057         pw.println("  'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'");
1058         pw.println();
1059         pw.println("art SUB_COMMAND [ARGS]...");
1060         pw.println("  Run ART Service commands");
1061         pw.println();
1062         pw.println("  Supported sub-commands:");
1063         pw.println();
1064         pw.println("  cancel JOB_ID");
1065         pw.println("    Cancel a job started by a shell command. This doesn't apply to background");
1066         pw.println("    jobs.");
1067         pw.println();
1068         pw.println("  clear-app-profiles PACKAGE_NAME");
1069         pw.println("    Clear the profiles that are collected locally for the given package,");
1070         pw.println("    including the profiles for primary and secondary dex files. More");
1071         pw.println("    specifically, this command clears reference profiles and current");
1072         pw.println("    profiles. External profiles (e.g., cloud profiles) will be kept.");
1073         pw.println();
1074         pw.println("  cleanup");
1075         pw.println("    Cleanup obsolete files, such as dexopt artifacts that are outdated or");
1076         pw.println("    correspond to dex container files that no longer exist.");
1077         pw.println();
1078         pw.println("  dump [PACKAGE_NAME]");
1079         pw.println("    Dump the dexopt state in text format to stdout.");
1080         pw.println("    If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
1081         pw.println("    is for the given package.");
1082         pw.println();
1083         pw.println("  dexopt-packages -r REASON");
1084         pw.println("    Run batch dexopt for the given reason.");
1085         pw.println("    Valid values for REASON: 'first-boot', 'boot-after-ota',");
1086         pw.println("    'boot-after-mainline-update', 'bg-dexopt'");
1087         pw.println("    This command is different from 'pm compile -r REASON -a'. For example, it");
1088         pw.println("    only dexopts a subset of apps, and it runs dexopt in parallel. See the");
1089         pw.println("    API documentation for 'ArtManagerLocal.dexoptPackages' for details.");
1090         pw.println();
1091         pw.println("  on-ota-staged [--slot SLOT | --start]");
1092         pw.println("    Notify ART Service that an OTA update is staged. ART Service decides what");
1093         pw.println("    to do with this notification:");
1094         pw.println("    - If Pre-reboot Dexopt is disabled or unsupported, the command returns");
1095         pw.println("      non-zero.");
1096         pw.println("    - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks");
1097         pw.println("      until Pre-reboot Dexopt finishes, and returns zero no matter it");
1098         pw.println("      succeeds or not.");
1099         pw.println("    - If Pre-reboot Dexopt is enabled in asynchronous mode, the command");
1100         pw.println("      schedules an asynchronous job and returns 0 immediately. The job will");
1101         pw.println("      then run by the job scheduler when the device is idle and charging.");
1102         pw.println("    Options:");
1103         pw.println("      --slot SLOT The slot that contains the OTA update, '_a' or '_b'.");
1104         pw.println("      --start Notify the asynchronous job that the snapshot devices are");
1105         pw.println("        ready. The command blocks until the job finishes, and returns zero no");
1106         pw.println("        matter it succeeds or not.");
1107         pw.println("    Note: This command is only supposed to be used by the system. To manually");
1108         pw.println("    control the Pre-reboot Dexopt job, use 'pr-dexopt-job' instead.");
1109         pw.println();
1110         pw.println("  pr-dexopt-job [--version | --run | --schedule | --cancel | --test]");
1111         pw.println("      [--slot SLOT]");
1112         pw.println("    Control the Pre-reboot Dexopt job. One of the mode options must be");
1113         pw.println("    specified.");
1114         pw.println("    Mode Options:");
1115         pw.println("      --version Show the version of the Pre-reboot Dexopt job.");
1116         pw.println("      --run Start a Pre-reboot Dexopt job immediately and waits for it to");
1117         pw.println("        finish. This command preempts any pending or running job, previously");
1118         pw.println("        scheduled or started automatically by the system or through any");
1119         pw.println("        'pr-dexopt-job' command.");
1120         pw.println("      --schedule Schedule a Pre-reboot Dexopt job and return immediately. The");
1121         pw.println("        job will then be automatically started by the job scheduler when the");
1122         pw.println("        device is idle and charging. This command immediately preempts any");
1123         pw.println("        pending or running job, previously scheduled or started automatically");
1124         pw.println("        by the system or through any 'pr-dexopt-job' command.");
1125         pw.println("      --cancel Cancel any pending or running job, previously scheduled or");
1126         pw.println("        started automatically by the system or through any 'pr-dexopt-job'");
1127         pw.println("        command.");
1128         pw.println("      --test The behavior is undefined. Do not use it.");
1129         pw.println("    Options:");
1130         pw.println("      --slot SLOT The slot that contains the OTA update, '_a' or '_b'. If not");
1131         pw.println("        specified, the job is for a Mainline update");
1132         pw.println();
1133         pw.println("  configure-batch-dexopt -r REASON [--package PACKAGE_NAME]...");
1134         pw.println("    Configure batch dexopt parameters to be applied when the given reason is");
1135         pw.println("    used.");
1136         pw.println("    Once called, this command overwrites any configuration done through");
1137         pw.println("    'ArtManagerLocal.setBatchDexoptStartCallback' or through this command for");
1138         pw.println("    all reasons. In other words, configurations for other reasons are reset");
1139         pw.println("    to the default.");
1140         pw.println("    Valid values for REASON: 'first-boot', 'boot-after-ota',");
1141         pw.println("    'boot-after-mainline-update', 'bg-dexopt', 'ab-ota'");
1142         pw.println("    Options:");
1143         pw.println("      --package PACKAGE_NAME The package name to dexopt. This flag can be");
1144         pw.println("        passed multiple times, to specify multiple packages. If not");
1145         pw.println("        specified, the default package list will be used.");
1146     }
1147 
enforceRootOrShell()1148     private void enforceRootOrShell() {
1149         final int uid = mInjector.getCallingUid();
1150         if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
1151             throw new SecurityException("ART service shell commands need root or shell access");
1152         }
1153     }
1154 
1155     @PriorityClassApi
parsePriorityClass(@onNull String priorityClass)1156     int parsePriorityClass(@NonNull String priorityClass) {
1157         switch (priorityClass) {
1158             case "PRIORITY_BOOT":
1159                 return ArtFlags.PRIORITY_BOOT;
1160             case "PRIORITY_INTERACTIVE_FAST":
1161                 return ArtFlags.PRIORITY_INTERACTIVE_FAST;
1162             case "PRIORITY_INTERACTIVE":
1163                 return ArtFlags.PRIORITY_INTERACTIVE;
1164             case "PRIORITY_BACKGROUND":
1165                 return ArtFlags.PRIORITY_BACKGROUND;
1166             default:
1167                 throw new IllegalArgumentException("Unknown priority " + priorityClass);
1168         }
1169     }
1170 
1171     @Nullable
getSplitName(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull String splitArg)1172     private String getSplitName(@NonNull PrintWriter pw,
1173             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
1174             @NonNull String splitArg) {
1175         if (splitArg.isEmpty()) {
1176             return null; // Base APK.
1177         }
1178 
1179         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
1180         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
1181         PrimaryDexInfo dexInfo =
1182                 PrimaryDexUtils.findDexInfo(pkg, info -> splitArg.equals(info.splitName()));
1183         if (dexInfo != null) {
1184             return splitArg;
1185         }
1186         dexInfo = PrimaryDexUtils.findDexInfo(
1187                 pkg, info -> splitArg.equals(new File(info.dexPath()).getName()));
1188         if (dexInfo != null) {
1189             pw.println("Warning: Specifying a split using a filename is deprecated. Please "
1190                     + "use a split name (or an empty string for the base APK) instead");
1191             return dexInfo.splitName();
1192         }
1193 
1194         throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg));
1195     }
1196 
1197     @Nullable
getSplitNameByFullPath(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull String fullPath)1198     private String getSplitNameByFullPath(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1199             @NonNull String packageName, @NonNull String fullPath) {
1200         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
1201         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
1202         PrimaryDexInfo dexInfo =
1203                 PrimaryDexUtils.findDexInfo(pkg, info -> fullPath.equals(info.dexPath()));
1204         if (dexInfo != null) {
1205             return dexInfo.splitName();
1206         }
1207         throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath));
1208     }
1209 
resetPackages(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, boolean verbose)1210     private int resetPackages(@NonNull PrintWriter pw,
1211             @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1212             @NonNull List<String> packageNames, boolean verbose) {
1213         try (var signal = new WithCancellationSignal(pw, verbose)) {
1214             for (String packageName : packageNames) {
1215                 DexoptResult result = mInjector.getArtManagerLocal().resetDexoptStatus(
1216                         snapshot, packageName, signal.get());
1217                 printDexoptResult(pw, result, verbose, packageNames.size() > 1);
1218             }
1219         }
1220         return 0;
1221     }
1222 
dexoptPackages(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose)1223     private int dexoptPackages(@NonNull PrintWriter pw,
1224             @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1225             @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose) {
1226         try (var signal = new WithCancellationSignal(pw, verbose)) {
1227             for (String packageName : packageNames) {
1228                 DexoptResult result = mInjector.getArtManagerLocal().dexoptPackage(
1229                         snapshot, packageName, params, signal.get());
1230                 printDexoptResult(pw, result, verbose, packageNames.size() > 1);
1231             }
1232         }
1233         return 0;
1234     }
1235 
1236     @NonNull
dexoptResultStatusToSimpleString(@exoptResultStatus int status)1237     private String dexoptResultStatusToSimpleString(@DexoptResultStatus int status) {
1238         return (status == DexoptResult.DEXOPT_SKIPPED || status == DexoptResult.DEXOPT_PERFORMED)
1239                 ? "Success"
1240                 : "Failure";
1241     }
1242 
printDexoptResult(@onNull PrintWriter pw, @NonNull DexoptResult result, boolean verbose, boolean multiPackage)1243     private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result,
1244             boolean verbose, boolean multiPackage) {
1245         for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) {
1246             if (verbose) {
1247                 pw.printf("[%s]\n", packageResult.getPackageName());
1248                 for (DexContainerFileDexoptResult fileResult :
1249                         packageResult.getDexContainerFileDexoptResults()) {
1250                     pw.println(fileResult);
1251                 }
1252             } else if (multiPackage) {
1253                 pw.printf("[%s] %s\n", packageResult.getPackageName(),
1254                         dexoptResultStatusToSimpleString(packageResult.getStatus()));
1255             }
1256         }
1257 
1258         if (verbose) {
1259             pw.println("Final Status: "
1260                     + DexoptResult.dexoptResultStatusToString(result.getFinalStatus()));
1261         } else if (!multiPackage) {
1262             // Multi-package result is printed by the loop above.
1263             pw.println(dexoptResultStatusToSimpleString(result.getFinalStatus()));
1264         }
1265 
1266         pw.flush();
1267     }
1268 
writeProfileFdContentsToFile(@onNull PrintWriter pw, @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath)1269     private void writeProfileFdContentsToFile(@NonNull PrintWriter pw,
1270             @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) {
1271         try {
1272             StructStat st = Os.stat(PROFILE_DEBUG_LOCATION);
1273             if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID
1274                     || (st.st_mode & 0007) != 0) {
1275                 throw new RuntimeException(
1276                         String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o",
1277                                 PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode));
1278             }
1279         } catch (ErrnoException e) {
1280             throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e);
1281         }
1282         Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath);
1283         try (InputStream inputStream = new AutoCloseInputStream(fd);
1284                 FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) {
1285             // The system server doesn't have the permission to chown the file to "shell", so we
1286             // make it readable by everyone and put it in a directory that is only accessible by
1287             // "shell", which is created by system/core/rootdir/init.rc. The permissions are
1288             // verified by the code above.
1289             Os.fchmod(outputStream.getFD(), 0644);
1290             Streams.copy(inputStream, outputStream);
1291             pw.printf("Profile saved to '%s'\n", outputPath);
1292         } catch (IOException | ErrnoException e) {
1293             Utils.deleteIfExistsSafe(outputPath);
1294             throw new RuntimeException(e);
1295         }
1296     }
1297 
1298     @NonNull
getProgressMessageForBatchDexoptPass( @atchDexoptPass int pass, @NonNull @BatchDexoptReason String reason)1299     private String getProgressMessageForBatchDexoptPass(
1300             @BatchDexoptPass int pass, @NonNull @BatchDexoptReason String reason) {
1301         switch (pass) {
1302             case ArtFlags.PASS_DOWNGRADE:
1303                 return "Downgrading apps";
1304             case ArtFlags.PASS_MAIN:
1305                 return reason.equals(ReasonMapping.REASON_BG_DEXOPT) ? "Dexopting apps (main pass)"
1306                                                                      : "Dexopting apps";
1307             case ArtFlags.PASS_SUPPLEMENTARY:
1308                 return "Dexopting apps (supplementary pass)";
1309         }
1310         throw new IllegalArgumentException("Unknown batch dexopt pass " + pass);
1311     }
1312 
1313     private static class WithCancellationSignal implements AutoCloseable {
1314         @NonNull private final CancellationSignal mSignal = new CancellationSignal();
1315         @NonNull private final String mJobId;
1316 
WithCancellationSignal(@onNull PrintWriter pw, boolean verbose)1317         public WithCancellationSignal(@NonNull PrintWriter pw, boolean verbose) {
1318             mJobId = UUID.randomUUID().toString();
1319             if (verbose) {
1320                 pw.printf(
1321                         "Job running. To cancel it, run 'pm art cancel %s' in a separate shell.\n",
1322                         mJobId);
1323                 pw.flush();
1324             }
1325 
1326             synchronized (sCancellationSignalMap) {
1327                 sCancellationSignalMap.put(mJobId, mSignal);
1328             }
1329         }
1330 
1331         @NonNull
get()1332         public CancellationSignal get() {
1333             return mSignal;
1334         }
1335 
close()1336         public void close() {
1337             synchronized (sCancellationSignalMap) {
1338                 sCancellationSignalMap.remove(mJobId);
1339             }
1340         }
1341     }
1342 
1343     /** Injector pattern for testing purpose. */
1344     @VisibleForTesting
1345     public static class Injector {
1346         @NonNull private final ArtManagerLocal mArtManagerLocal;
1347         @NonNull private final PackageManagerLocal mPackageManagerLocal;
1348 
Injector(@onNull ArtManagerLocal artManagerLocal, @NonNull PackageManagerLocal packageManagerLocal)1349         public Injector(@NonNull ArtManagerLocal artManagerLocal,
1350                 @NonNull PackageManagerLocal packageManagerLocal) {
1351             mArtManagerLocal = artManagerLocal;
1352             mPackageManagerLocal = packageManagerLocal;
1353         }
1354 
1355         @NonNull
getArtManagerLocal()1356         public ArtManagerLocal getArtManagerLocal() {
1357             return mArtManagerLocal;
1358         }
1359 
1360         @NonNull
getPackageManagerLocal()1361         public PackageManagerLocal getPackageManagerLocal() {
1362             return mPackageManagerLocal;
1363         }
1364 
getCallingUid()1365         public int getCallingUid() {
1366             return Binder.getCallingUid();
1367         }
1368     }
1369 }
1370