• 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.model.ArtFlags.DexoptFlags;
24 import static com.android.server.art.model.ArtFlags.PriorityClassApi;
25 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
26 import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
27 import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
28 import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.os.Binder;
33 import android.os.Build;
34 import android.os.CancellationSignal;
35 import android.os.ParcelFileDescriptor;
36 import android.os.Process;
37 import android.system.ErrnoException;
38 import android.system.Os;
39 import android.system.StructStat;
40 import android.util.Log;
41 
42 import androidx.annotation.RequiresApi;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.modules.utils.BasicShellCommandHandler;
46 import com.android.server.art.model.ArtFlags;
47 import com.android.server.art.model.DeleteResult;
48 import com.android.server.art.model.DexoptParams;
49 import com.android.server.art.model.DexoptResult;
50 import com.android.server.art.model.DexoptStatus;
51 import com.android.server.art.model.OperationProgress;
52 import com.android.server.pm.PackageManagerLocal;
53 import com.android.server.pm.pkg.AndroidPackage;
54 import com.android.server.pm.pkg.PackageState;
55 
56 import libcore.io.Streams;
57 
58 import java.io.File;
59 import java.io.FileOutputStream;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.io.OutputStream;
63 import java.io.PrintWriter;
64 import java.nio.file.Path;
65 import java.nio.file.Paths;
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.UUID;
71 import java.util.concurrent.CompletableFuture;
72 import java.util.concurrent.ExecutorService;
73 import java.util.concurrent.Executors;
74 import java.util.stream.Collectors;
75 
76 /**
77  * This class handles ART shell commands.
78  *
79  * @hide
80  */
81 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
82 public final class ArtShellCommand extends BasicShellCommandHandler {
83     private static final String TAG = ArtManagerLocal.TAG;
84 
85     /** The default location for profile dumps. */
86     private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman";
87 
88     private final ArtManagerLocal mArtManagerLocal;
89     private final PackageManagerLocal mPackageManagerLocal;
90 
91     @GuardedBy("sCancellationSignalMap")
92     @NonNull
93     private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
94 
ArtShellCommand(@onNull ArtManagerLocal artManagerLocal, @NonNull PackageManagerLocal packageManagerLocal)95     public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
96             @NonNull PackageManagerLocal packageManagerLocal) {
97         mArtManagerLocal = artManagerLocal;
98         mPackageManagerLocal = packageManagerLocal;
99     }
100 
101     @Override
onCommand(String cmd)102     public int onCommand(String cmd) {
103         // Apps shouldn't call ART Service shell commands, not even for dexopting themselves.
104         enforceRootOrShell();
105         PrintWriter pw = getOutPrintWriter();
106         try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) {
107             switch (cmd) {
108                 case "compile":
109                     return handleCompile(pw, snapshot);
110                 case "reconcile-secondary-dex-files":
111                     pw.println("Warning: 'pm reconcile-secondary-dex-files' is deprecated. It is "
112                             + "now doing nothing");
113                     return 0;
114                 case "force-dex-opt":
115                     return handleForceDexopt(pw, snapshot);
116                 case "bg-dexopt-job":
117                     return handleBgDexoptJob(pw, snapshot);
118                 case "cancel-bg-dexopt-job":
119                     pw.println("Warning: 'pm cancel-bg-dexopt-job' is deprecated. It is now an "
120                             + "alias of 'pm bg-dexopt-job --cancel'");
121                     return handleCancelBgDexoptJob(pw);
122                 case "delete-dexopt":
123                     return handleDeleteDexopt(pw, snapshot);
124                 case "dump-profiles":
125                     return handleDumpProfile(pw, snapshot);
126                 case "snapshot-profile":
127                     return handleSnapshotProfile(pw, snapshot);
128                 case "art":
129                     return handleArtCommand(pw, snapshot);
130                 default:
131                     // Can't happen. Only supported commands are forwarded to ART Service.
132                     throw new IllegalArgumentException(
133                             String.format("Unexpected command '%s' forwarded to ART Service", cmd));
134             }
135         } catch (IllegalArgumentException | SnapshotProfileException e) {
136             pw.println("Error: " + e.getMessage());
137             return 1;
138         }
139     }
140 
handleArtCommand( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)141     private int handleArtCommand(
142             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
143         String subcmd = getNextArgRequired();
144         switch (subcmd) {
145             case "dexopt-packages": {
146                 return handleBatchDexopt(pw, snapshot);
147             }
148             case "cancel": {
149                 String jobId = getNextArgRequired();
150                 CancellationSignal signal;
151                 synchronized (sCancellationSignalMap) {
152                     signal = sCancellationSignalMap.getOrDefault(jobId, null);
153                 }
154                 if (signal == null) {
155                     pw.println("Job not found");
156                     return 1;
157                 }
158                 signal.cancel();
159                 pw.println("Job cancelled");
160                 return 0;
161             }
162             case "dump": {
163                 String packageName = getNextArg();
164                 if (packageName != null) {
165                     mArtManagerLocal.dumpPackage(pw, snapshot, packageName);
166                 } else {
167                     mArtManagerLocal.dump(pw, snapshot);
168                 }
169                 return 0;
170             }
171             case "cleanup": {
172                 return handleCleanup(pw, snapshot);
173             }
174             case "clear-app-profiles": {
175                 mArtManagerLocal.clearAppProfiles(snapshot, getNextArgRequired());
176                 pw.println("Profiles cleared");
177                 return 0;
178             }
179             default:
180                 pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
181                 pw.println("See 'pm help' for help");
182                 return 1;
183         }
184     }
185 
handleCompile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)186     private int handleCompile(
187             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
188         @DexoptFlags int scopeFlags = 0;
189         String reason = null;
190         String compilerFilter = null;
191         @PriorityClassApi int priorityClass = ArtFlags.PRIORITY_NONE;
192         String splitArg = null;
193         boolean force = false;
194         boolean reset = false;
195         boolean forAllPackages = false;
196         boolean legacyClearProfile = false;
197         boolean verbose = false;
198 
199         String opt;
200         while ((opt = getNextOption()) != null) {
201             switch (opt) {
202                 case "-a":
203                     forAllPackages = true;
204                     break;
205                 case "-r":
206                     reason = getNextArgRequired();
207                     break;
208                 case "-m":
209                     compilerFilter = getNextArgRequired();
210                     break;
211                 case "-p":
212                     priorityClass = parsePriorityClass(getNextArgRequired());
213                     break;
214                 case "-f":
215                     force = true;
216                     break;
217                 case "--primary-dex":
218                     scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
219                     break;
220                 case "--secondary-dex":
221                     scopeFlags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
222                     break;
223                 case "--include-dependencies":
224                     scopeFlags |= ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
225                     break;
226                 case "--full":
227                     scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
228                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
229                     break;
230                 case "--split":
231                     splitArg = getNextArgRequired();
232                     break;
233                 case "--reset":
234                     reset = true;
235                     break;
236                 case "-c":
237                     pw.println("Warning: Flag '-c' is deprecated and usually produces undesired "
238                             + "results. Please use one of the following commands instead.");
239                     pw.println("- To clear the local profiles only, use "
240                             + "'pm art clear-app-profiles PACKAGE_NAME'. (The existing dexopt "
241                             + "artifacts will be kept, even if they are derived from the "
242                             + "profiles.)");
243                     pw.println("- To clear the local profiles and also clear the dexopt artifacts "
244                             + "that are derived from them, use 'pm compile --reset PACKAGE_NAME'. "
245                             + "(The package will be reset to the initial state as if it's newly "
246                             + "installed, which means the package will be re-dexopted if "
247                             + "necessary, and cloud profiles will be used if exist.)");
248                     pw.println("- To re-dexopt the package with no profile, use "
249                             + "'pm compile -m verify -f PACKAGE_NAME'. (The local profiles "
250                             + "will be kept but not used during the dexopt. The dexopt artifacts "
251                             + "are guaranteed to have no compiled code.)");
252                     legacyClearProfile = true;
253                     break;
254                 case "--check-prof":
255                     getNextArgRequired();
256                     pw.println("Warning: Ignoring obsolete flag '--check-prof'. It is "
257                             + "unconditionally enabled now");
258                     break;
259                 case "-v":
260                     verbose = true;
261                     break;
262                 default:
263                     pw.println("Error: Unknown option: " + opt);
264                     return 1;
265             }
266         }
267 
268         List<String> packageNames = forAllPackages
269                 ? List.copyOf(snapshot.getPackageStates().keySet())
270                 : List.of(getNextArgRequired());
271 
272         var paramsBuilder = new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE);
273         if (reason != null) {
274             if (reason.equals(ReasonMapping.REASON_INACTIVE)) {
275                 pw.println("Warning: '-r inactive' produces undesired results.");
276             }
277             if (compilerFilter == null) {
278                 paramsBuilder.setCompilerFilter(ReasonMapping.getCompilerFilterForReason(reason));
279             }
280             if (priorityClass == ArtFlags.PRIORITY_NONE) {
281                 paramsBuilder.setPriorityClass(ReasonMapping.getPriorityClassForReason(reason));
282             }
283         }
284         if (compilerFilter != null) {
285             paramsBuilder.setCompilerFilter(compilerFilter);
286         }
287         if (priorityClass != ArtFlags.PRIORITY_NONE) {
288             paramsBuilder.setPriorityClass(priorityClass);
289         }
290         if (force) {
291             paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
292         }
293         if (splitArg != null) {
294             if (scopeFlags != 0) {
295                 pw.println("Error: '--primary-dex', '--secondary-dex', "
296                         + "'--include-dependencies', or '--full' must not be set when '--split' "
297                         + "is set.");
298                 return 1;
299             }
300             if (forAllPackages) {
301                 pw.println("Error:  '-a' cannot be specified together with '--split'");
302                 return 1;
303             }
304             scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX;
305             paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT, ArtFlags.FLAG_FOR_SINGLE_SPLIT)
306                     .setSplitName(getSplitName(pw, snapshot, packageNames.get(0), splitArg));
307         }
308         if (scopeFlags != 0) {
309             paramsBuilder.setFlags(scopeFlags,
310                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
311                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
312         } else {
313             paramsBuilder.setFlags(
314                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
315                     ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
316                             | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
317         }
318         if (forAllPackages) {
319             // We'll iterate over all packages anyway.
320             paramsBuilder.setFlags(0, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
321         }
322 
323         if (reset) {
324             return resetPackages(pw, snapshot, packageNames, verbose);
325         } else {
326             if (legacyClearProfile) {
327                 // For compat only. Combining this with dexopt usually produces in undesired
328                 // results.
329                 for (String packageName : packageNames) {
330                     mArtManagerLocal.clearAppProfiles(snapshot, packageName);
331                 }
332             }
333             return dexoptPackages(pw, snapshot, packageNames, paramsBuilder.build(), verbose);
334         }
335     }
336 
handleForceDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)337     private int handleForceDexopt(
338             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
339         pw.println("Warning: 'pm force-dex-opt' is deprecated. Please use 'pm compile "
340                 + "-f PACKAGE_NAME' instead");
341         return dexoptPackages(pw, snapshot, List.of(getNextArgRequired()),
342                 new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE)
343                         .setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE)
344                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX
345                                         | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
346                                 ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
347                                         | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
348                         .build(),
349                 false /* verbose */);
350     }
351 
handleBgDexoptJob( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)352     private int handleBgDexoptJob(
353             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
354         String opt = getNextOption();
355         if (opt == null) {
356             List<String> packageNames = new ArrayList<>();
357             String arg;
358             while ((arg = getNextArg()) != null) {
359                 packageNames.add(arg);
360             }
361             if (!packageNames.isEmpty()) {
362                 pw.println("Warning: Running 'pm bg-dexopt-job' with package names is deprecated. "
363                         + "Please use 'pm compile -r bg-dexopt PACKAGE_NAME' instead");
364                 return dexoptPackages(pw, snapshot, packageNames,
365                         new DexoptParams.Builder(ReasonMapping.REASON_BG_DEXOPT).build(),
366                         false /* verbose */);
367             }
368 
369             CompletableFuture<BackgroundDexoptJob.Result> runningJob =
370                     mArtManagerLocal.getRunningBackgroundDexoptJob();
371             if (runningJob != null) {
372                 pw.println("Another job already running. Waiting for it to finish... To cancel it, "
373                         + "run 'pm bg-dexopt-job --cancel'. in a separate shell.");
374                 pw.flush();
375                 Utils.getFuture(runningJob);
376             }
377             CompletableFuture<BackgroundDexoptJob.Result> future =
378                     mArtManagerLocal.startBackgroundDexoptJobAndReturnFuture();
379             pw.println("Job running...  To cancel it, run 'pm bg-dexopt-job --cancel'. in a "
380                     + "separate shell.");
381             pw.flush();
382             BackgroundDexoptJob.Result result = Utils.getFuture(future);
383             if (result instanceof BackgroundDexoptJob.CompletedResult) {
384                 var completedResult = (BackgroundDexoptJob.CompletedResult) result;
385                 if (completedResult.dexoptResult().getFinalStatus()
386                         == DexoptResult.DEXOPT_CANCELLED) {
387                     pw.println("Job cancelled. See logs for details");
388                 } else {
389                     pw.println("Job finished. See logs for details");
390                 }
391             } else if (result instanceof BackgroundDexoptJob.FatalErrorResult) {
392                 // Never expected.
393                 pw.println("Job encountered a fatal error");
394             }
395             return 0;
396         }
397         switch (opt) {
398             case "--cancel": {
399                 return handleCancelBgDexoptJob(pw);
400             }
401             case "--enable": {
402                 // This operation requires the uid to be "system" (1000).
403                 long identityToken = Binder.clearCallingIdentity();
404                 try {
405                     mArtManagerLocal.scheduleBackgroundDexoptJob();
406                 } finally {
407                     Binder.restoreCallingIdentity(identityToken);
408                 }
409                 pw.println("Background dexopt job enabled");
410                 return 0;
411             }
412             case "--disable": {
413                 // This operation requires the uid to be "system" (1000).
414                 long identityToken = Binder.clearCallingIdentity();
415                 try {
416                     mArtManagerLocal.unscheduleBackgroundDexoptJob();
417                 } finally {
418                     Binder.restoreCallingIdentity(identityToken);
419                 }
420                 pw.println("Background dexopt job disabled");
421                 return 0;
422             }
423             default:
424                 pw.println("Error: Unknown option: " + opt);
425                 return 1;
426         }
427     }
428 
handleCancelBgDexoptJob(@onNull PrintWriter pw)429     private int handleCancelBgDexoptJob(@NonNull PrintWriter pw) {
430         mArtManagerLocal.cancelBackgroundDexoptJob();
431         pw.println("Background dexopt job cancelled");
432         return 0;
433     }
434 
handleCleanup( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)435     private int handleCleanup(
436             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
437         long freedBytes = mArtManagerLocal.cleanup(snapshot);
438         pw.printf("Freed %d bytes\n", freedBytes);
439         return 0;
440     }
441 
handleDeleteDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)442     private int handleDeleteDexopt(
443             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
444         DeleteResult result =
445                 mArtManagerLocal.deleteDexoptArtifacts(snapshot, getNextArgRequired());
446         pw.printf("Freed %d bytes\n", result.getFreedBytes());
447         return 0;
448     }
449 
handleSnapshotProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)450     private int handleSnapshotProfile(
451             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
452             throws SnapshotProfileException {
453         String splitName = null;
454         String codePath = null;
455 
456         String opt;
457         while ((opt = getNextOption()) != null) {
458             switch (opt) {
459                 case "--split":
460                     splitName = getNextArgRequired();
461                     break;
462                 default:
463                     pw.println("Error: Unknown option: " + opt);
464                     return 1;
465             }
466         }
467 
468         String packageName = getNextArgRequired();
469 
470         if ("--code-path".equals(getNextOption())) {
471             pw.println("Warning: Specifying a split using '--code-path' is deprecated. Please use "
472                     + "'--split SPLIT_NAME' instead");
473             pw.println("Tip: '--split SPLIT_NAME' must be passed before the package name");
474             codePath = getNextArgRequired();
475         }
476 
477         if (splitName != null && codePath != null) {
478             pw.println("Error: '--split' and '--code-path' cannot be both specified");
479             return 1;
480         }
481 
482         if (packageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
483             if (splitName != null) {
484                 pw.println("Error: '--split' must not be specified for boot image profile");
485                 return 1;
486             }
487             if (codePath != null) {
488                 pw.println("Error: '--code-path' must not be specified for boot image profile");
489                 return 1;
490             }
491             return handleSnapshotBootProfile(pw, snapshot);
492         }
493 
494         if (splitName != null && splitName.isEmpty()) {
495             splitName = null;
496         }
497         if (codePath != null) {
498             splitName = getSplitNameByFullPath(snapshot, packageName, codePath);
499         }
500 
501         return handleSnapshotAppProfile(pw, snapshot, packageName, splitName);
502     }
503 
handleSnapshotBootProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)504     private int handleSnapshotBootProfile(
505             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
506             throws SnapshotProfileException {
507         String outputRelativePath = "android.prof";
508         ParcelFileDescriptor fd = mArtManagerLocal.snapshotBootImageProfile(snapshot);
509         writeProfileFdContentsToFile(pw, fd, outputRelativePath);
510         return 0;
511     }
512 
handleSnapshotAppProfile(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName)513     private int handleSnapshotAppProfile(@NonNull PrintWriter pw,
514             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
515             @Nullable String splitName) throws SnapshotProfileException {
516         String outputRelativePath = String.format("%s%s.prof", packageName,
517                 splitName != null ? String.format("-split_%s.apk", splitName) : "");
518         ParcelFileDescriptor fd =
519                 mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName);
520         writeProfileFdContentsToFile(pw, fd, outputRelativePath);
521         return 0;
522     }
523 
handleDumpProfile( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)524     private int handleDumpProfile(
525             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
526             throws SnapshotProfileException {
527         boolean dumpClassesAndMethods = false;
528 
529         String opt;
530         while ((opt = getNextOption()) != null) {
531             switch (opt) {
532                 case "--dump-classes-and-methods": {
533                     dumpClassesAndMethods = true;
534                     break;
535                 }
536                 default:
537                     pw.println("Error: Unknown option: " + opt);
538                     return 1;
539             }
540         }
541 
542         String packageName = getNextArgRequired();
543 
544         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
545         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
546         try (var tracing = new Utils.Tracing("dump profiles")) {
547             for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
548                 if (!dexInfo.hasCode()) {
549                     continue;
550                 }
551                 String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName());
552                 // The path is intentionally inconsistent with the one for "snapshot-profile". This
553                 // is to match the behavior of the legacy PM shell command.
554                 String outputRelativePath =
555                         String.format("%s-%s.prof.txt", packageName, profileName);
556                 ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
557                         snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods);
558                 writeProfileFdContentsToFile(pw, fd, outputRelativePath);
559             }
560         }
561         return 0;
562     }
563 
handleBatchDexopt( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)564     private int handleBatchDexopt(
565             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
566         String reason = null;
567         String opt;
568         while ((opt = getNextOption()) != null) {
569             switch (opt) {
570                 case "-r":
571                     reason = getNextArgRequired();
572                     break;
573                 default:
574                     pw.println("Error: Unknown option: " + opt);
575                     return 1;
576             }
577         }
578         if (reason == null) {
579             pw.println("Error: '-r REASON' is required");
580             return 1;
581         }
582         if (!ReasonMapping.BATCH_DEXOPT_REASONS.contains(reason)) {
583             pw.printf("Error: Invalid batch dexopt reason '%s'. Valid values are: %s\n", reason,
584                     ReasonMapping.BATCH_DEXOPT_REASONS);
585             return 1;
586         }
587         DexoptResult result;
588         ExecutorService progressCallbackExecutor = Executors.newSingleThreadExecutor();
589         try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
590             result = mArtManagerLocal.dexoptPackages(
591                     snapshot, reason, signal.get(), progressCallbackExecutor, progress -> {
592                         pw.println(String.format("Dexopting apps: %d%%", progress.getPercentage()));
593                         pw.flush();
594                     });
595             Utils.executeAndWait(progressCallbackExecutor, () -> {
596                 printDexoptResult(pw, result, true /* verbose */, true /* multiPackage */);
597             });
598         } finally {
599             progressCallbackExecutor.shutdown();
600         }
601         return 0;
602     }
603 
604     @Override
onHelp()605     public void onHelp() {
606         // No one should call this. The help text should be printed by the `onHelp` handler of `cmd
607         // package`.
608         throw new UnsupportedOperationException("Unexpected call to 'onHelp'");
609     }
610 
printHelp(@onNull PrintWriter pw)611     public static void printHelp(@NonNull PrintWriter pw) {
612         pw.println("compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f]");
613         pw.println("    [--primary-dex] [--secondary-dex] [--include-dependencies] [--full]");
614         pw.println("    [--split SPLIT_NAME] [--reset] [-a | PACKAGE_NAME]");
615         pw.println("  Dexopt a package or all packages.");
616         pw.println("  Options:");
617         pw.println("    -a Dexopt all packages");
618         pw.println("    -r Set the compiler filter and the priority based on the given");
619         pw.println("       compilation reason.");
620         pw.println("       Available options: 'first-boot', 'boot-after-ota',");
621         pw.println("       'boot-after-mainline-update', 'install', 'bg-dexopt', 'cmdline'.");
622         pw.println("    -m Set the target compiler filter. The filter actually used may be");
623         pw.println("       different, e.g. 'speed-profile' without profiles present may result in");
624         pw.println("       'verify' being used instead. If not specified, this defaults to the");
625         pw.println("       value given by -r, or the system property 'pm.dexopt.cmdline'.");
626         pw.println("       Available options (in descending order): 'speed', 'speed-profile',");
627         pw.println("       'verify'.");
628         pw.println("    -p Set the priority of the operation, which determines the resource usage");
629         pw.println("       and the process priority. If not specified, this defaults to");
630         pw.println("       the value given by -r, or 'PRIORITY_INTERACTIVE'.");
631         pw.println("       Available options (in descending order): 'PRIORITY_BOOT',");
632         pw.println("       'PRIORITY_INTERACTIVE_FAST', 'PRIORITY_INTERACTIVE',");
633         pw.println("       'PRIORITY_BACKGROUND'.");
634         pw.println("    -f Force dexopt, also when the compiler filter being applied is not");
635         pw.println("       better than that of the current dexopt artifacts for a package.");
636         pw.println("    --reset Reset the dexopt state of the package as if the package is newly");
637         pw.println("       installed.");
638         pw.println("       More specifically, it clears reference profiles, current profiles, and");
639         pw.println("       any code compiled from those local profiles. If there is an external");
640         pw.println("       profile (e.g., a cloud profile), the code compiled from that profile");
641         pw.println("       will be kept.");
642         pw.println("       For secondary dex files, it also clears all dexopt artifacts.");
643         pw.println("       When this flag is set, all the other flags are ignored.");
644         pw.println("    -v Verbose mode. This mode prints detailed results.");
645         pw.println("  Scope options:");
646         pw.println("    --primary-dex Dexopt primary dex files only (all APKs that are installed");
647         pw.println("      as part of the package, including the base APK and all other split");
648         pw.println("      APKs).");
649         pw.println("    --secondary-dex Dexopt secondary dex files only (APKs/JARs that the app");
650         pw.println("      puts in its own data directory at runtime and loads with custom");
651         pw.println("      classloaders).");
652         pw.println("    --include-dependencies Include dependency packages (dependencies that are");
653         pw.println("      declared by the app with <uses-library> tags and transitive");
654         pw.println("      dependencies). This option can only be used together with");
655         pw.println("      '--primary-dex' or '--secondary-dex'.");
656         pw.println("    --full Dexopt all above. (Recommended)");
657         pw.println("    --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an empty");
658         pw.println("      string, only dexopt the base APK.");
659         pw.println("      Tip: To pass an empty string, use a pair of quotes (\"\").");
660         pw.println("      When this option is set, '--primary-dex', '--secondary-dex',");
661         pw.println("      '--include-dependencies', '--full', and '-a' must not be set.");
662         pw.println("    Note: If none of the scope options above are set, the scope defaults to");
663         pw.println("    '--primary-dex --include-dependencies'.");
664         pw.println();
665         pw.println("delete-dexopt PACKAGE_NAME");
666         pw.println("  Delete the dexopt artifacts of both primary dex files and secondary dex");
667         pw.println("  files of a package.");
668         pw.println();
669         pw.println("bg-dexopt-job [--cancel | --disable | --enable]");
670         pw.println("  Control the background dexopt job.");
671         pw.println("  Without flags, it starts a background dexopt job immediately and waits for");
672         pw.println("    it to finish. If a job is already started either automatically by the");
673         pw.println("    system or through this command, it will wait for the running job to");
674         pw.println("    finish and then start a new one.");
675         pw.println("  Different from 'pm compile -r bg-dexopt -a', the behavior of this command");
676         pw.println("  is the same as a real background dexopt job. Specifically,");
677         pw.println("    - It only dexopts a subset of apps determined by either the system's");
678         pw.println("      default logic based on app usage data or the custom logic specified by");
679         pw.println("      the 'ArtManagerLocal.setBatchDexoptStartCallback' Java API.");
680         pw.println("    - It runs dexopt in parallel, where the concurrency setting is specified");
681         pw.println("      by the system property 'pm.dexopt.bg-dexopt.concurrency'.");
682         pw.println("    - If the storage is low, it also downgrades unused apps.");
683         pw.println("    - It also cleans up obsolete files.");
684         pw.println("  Options:");
685         pw.println("    --cancel Cancel any currently running background dexopt job immediately.");
686         pw.println("      This cancels jobs started either automatically by the system or through");
687         pw.println("      this command. This command is not blocking.");
688         pw.println("    --disable: Disable the background dexopt job from being started by the");
689         pw.println("      job scheduler. If a job is already started by the job scheduler and is");
690         pw.println("      running, it will be cancelled immediately. Does not affect jobs started");
691         pw.println("      through this command or by the system in other ways.");
692         pw.println("      This state will be lost when the system_server process exits.");
693         pw.println("    --enable: Enable the background dexopt job to be started by the job");
694         pw.println("      scheduler again, if previously disabled by --disable.");
695         pw.println("  When a list of package names is passed, this command does NOT start a real");
696         pw.println("  background dexopt job. Instead, it dexopts the given packages sequentially.");
697         pw.println("  This usage is deprecated. Please use 'pm compile -r bg-dexopt PACKAGE_NAME'");
698         pw.println("  instead.");
699         pw.println();
700         pw.println("snapshot-profile [android | [--split SPLIT_NAME] PACKAGE_NAME]");
701         pw.println("  Snapshot the boot image profile or the app profile and save it to");
702         pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
703         pw.println("  If 'android' is passed, the command snapshots the boot image profile, and");
704         pw.println("  the output filename is 'android.prof'.");
705         pw.println("  If a package name is passed, the command snapshots the app profile.");
706         pw.println("  Options:");
707         pw.println("    --split SPLIT_NAME If specified, the command snapshots the profile of the");
708         pw.println("      given split, and the output filename is");
709         pw.println("      'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'.");
710         pw.println("      If not specified, the command snapshots the profile of the base APK,");
711         pw.println("      and the output filename is 'PACKAGE_NAME.prof'");
712         pw.println();
713         pw.println("dump-profiles [--dump-classes-and-methods] PACKAGE_NAME");
714         pw.println("  Dump the profiles of the given app in text format and save the outputs to");
715         pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
716         pw.println("  The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'");
717         pw.println("  The profile of a split APK is dumped to");
718         pw.println("  'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'");
719         pw.println();
720         pw.println("art SUB_COMMAND [ARGS]...");
721         pw.println("  Run ART Service commands");
722         pw.println();
723         pw.println("  Supported sub-commands:");
724         pw.println();
725         pw.println("  cancel JOB_ID");
726         pw.println("    Cancel a job started by a shell command. This doesn't apply to background");
727         pw.println("    jobs.");
728         pw.println();
729         pw.println("  clear-app-profiles PACKAGE_NAME");
730         pw.println("    Clear the profiles that are collected locally for the given package,");
731         pw.println("    including the profiles for primary and secondary dex files. More");
732         pw.println("    specifically, this command clears reference profiles and current");
733         pw.println("    profiles. External profiles (e.g., cloud profiles) will be kept.");
734         pw.println();
735         pw.println("  cleanup");
736         pw.println("    Cleanup obsolete files, such as dexopt artifacts that are outdated or");
737         pw.println("    correspond to dex container files that no longer exist.");
738         pw.println();
739         pw.println("  dump [PACKAGE_NAME]");
740         pw.println("    Dumps the dexopt state in text format to stdout.");
741         pw.println("    If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
742         pw.println("    is for the given package.");
743         pw.println();
744         pw.println("  dexopt-packages -r REASON");
745         pw.println("    Run batch dexopt for the given reason.");
746         pw.println("    Valid values for REASON: 'first-boot', 'boot-after-ota',");
747         pw.println("    'boot-after-mainline-update', 'bg-dexopt'");
748         pw.println("    This command is different from 'pm compile -r REASON -a'. For example, it");
749         pw.println("    only dexopts a subset of apps, and it runs dexopt in parallel. See the");
750         pw.println("    API documentation for 'ArtManagerLocal.dexoptPackages' for details.");
751     }
752 
enforceRootOrShell()753     private void enforceRootOrShell() {
754         final int uid = Binder.getCallingUid();
755         if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
756             throw new SecurityException("ART service shell commands need root or shell access");
757         }
758     }
759 
760     @PriorityClassApi
parsePriorityClass(@onNull String priorityClass)761     int parsePriorityClass(@NonNull String priorityClass) {
762         switch (priorityClass) {
763             case "PRIORITY_BOOT":
764                 return ArtFlags.PRIORITY_BOOT;
765             case "PRIORITY_INTERACTIVE_FAST":
766                 return ArtFlags.PRIORITY_INTERACTIVE_FAST;
767             case "PRIORITY_INTERACTIVE":
768                 return ArtFlags.PRIORITY_INTERACTIVE;
769             case "PRIORITY_BACKGROUND":
770                 return ArtFlags.PRIORITY_BACKGROUND;
771             default:
772                 throw new IllegalArgumentException("Unknown priority " + priorityClass);
773         }
774     }
775 
776     @Nullable
getSplitName(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull String splitArg)777     private String getSplitName(@NonNull PrintWriter pw,
778             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
779             @NonNull String splitArg) {
780         if (splitArg.isEmpty()) {
781             return null; // Base APK.
782         }
783 
784         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
785         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
786         List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
787 
788         for (PrimaryDexInfo dexInfo : dexInfoList) {
789             if (splitArg.equals(dexInfo.splitName())) {
790                 return splitArg;
791             }
792         }
793 
794         for (PrimaryDexInfo dexInfo : dexInfoList) {
795             if (splitArg.equals(new File(dexInfo.dexPath()).getName())) {
796                 pw.println("Warning: Specifying a split using a filename is deprecated. Please "
797                         + "use a split name (or an empty string for the base APK) instead");
798                 return dexInfo.splitName();
799             }
800         }
801 
802         throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg));
803     }
804 
805     @Nullable
getSplitNameByFullPath(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull String fullPath)806     private String getSplitNameByFullPath(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
807             @NonNull String packageName, @NonNull String fullPath) {
808         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
809         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
810         List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
811 
812         for (PrimaryDexInfo dexInfo : dexInfoList) {
813             if (fullPath.equals(dexInfo.dexPath())) {
814                 return dexInfo.splitName();
815             }
816         }
817 
818         throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath));
819     }
820 
resetPackages(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, boolean verbose)821     private int resetPackages(@NonNull PrintWriter pw,
822             @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
823             @NonNull List<String> packageNames, boolean verbose) {
824         try (var signal = new WithCancellationSignal(pw, verbose)) {
825             for (String packageName : packageNames) {
826                 DexoptResult result =
827                         mArtManagerLocal.resetDexoptStatus(snapshot, packageName, signal.get());
828                 printDexoptResult(pw, result, verbose, packageNames.size() > 1);
829             }
830         }
831         return 0;
832     }
833 
dexoptPackages(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose)834     private int dexoptPackages(@NonNull PrintWriter pw,
835             @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
836             @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose) {
837         try (var signal = new WithCancellationSignal(pw, verbose)) {
838             for (String packageName : packageNames) {
839                 DexoptResult result =
840                         mArtManagerLocal.dexoptPackage(snapshot, packageName, params, signal.get());
841                 printDexoptResult(pw, result, verbose, packageNames.size() > 1);
842             }
843         }
844         return 0;
845     }
846 
847     @NonNull
dexoptResultStatusToSimpleString(@exoptResultStatus int status)848     private String dexoptResultStatusToSimpleString(@DexoptResultStatus int status) {
849         return (status == DexoptResult.DEXOPT_SKIPPED || status == DexoptResult.DEXOPT_PERFORMED)
850                 ? "Success"
851                 : "Failure";
852     }
853 
printDexoptResult(@onNull PrintWriter pw, @NonNull DexoptResult result, boolean verbose, boolean multiPackage)854     private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result,
855             boolean verbose, boolean multiPackage) {
856         for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) {
857             if (verbose) {
858                 pw.printf("[%s]\n", packageResult.getPackageName());
859                 for (DexContainerFileDexoptResult fileResult :
860                         packageResult.getDexContainerFileDexoptResults()) {
861                     pw.println(fileResult);
862                 }
863             } else if (multiPackage) {
864                 pw.printf("[%s] %s\n", packageResult.getPackageName(),
865                         dexoptResultStatusToSimpleString(packageResult.getStatus()));
866             }
867         }
868 
869         if (verbose) {
870             pw.println("Final Status: "
871                     + DexoptResult.dexoptResultStatusToString(result.getFinalStatus()));
872         } else if (!multiPackage) {
873             // Multi-package result is printed by the loop above.
874             pw.println(dexoptResultStatusToSimpleString(result.getFinalStatus()));
875         }
876 
877         pw.flush();
878     }
879 
writeProfileFdContentsToFile(@onNull PrintWriter pw, @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath)880     private void writeProfileFdContentsToFile(@NonNull PrintWriter pw,
881             @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) {
882         try {
883             StructStat st = Os.stat(PROFILE_DEBUG_LOCATION);
884             if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID
885                     || (st.st_mode & 0007) != 0) {
886                 throw new RuntimeException(
887                         String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o",
888                                 PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode));
889             }
890         } catch (ErrnoException e) {
891             throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e);
892         }
893         Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath);
894         try (InputStream inputStream = new AutoCloseInputStream(fd);
895                 FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) {
896             // The system server doesn't have the permission to chown the file to "shell", so we
897             // make it readable by everyone and put it in a directory that is only accessible by
898             // "shell", which is created by system/core/rootdir/init.rc. The permissions are
899             // verified by the code above.
900             Os.fchmod(outputStream.getFD(), 0644);
901             Streams.copy(inputStream, outputStream);
902             pw.printf("Profile saved to '%s'\n", outputPath);
903         } catch (IOException | ErrnoException e) {
904             Utils.deleteIfExistsSafe(outputPath);
905             throw new RuntimeException(e);
906         }
907     }
908 
909     private static class WithCancellationSignal implements AutoCloseable {
910         @NonNull private final CancellationSignal mSignal = new CancellationSignal();
911         @NonNull private final String mJobId;
912 
WithCancellationSignal(@onNull PrintWriter pw, boolean verbose)913         public WithCancellationSignal(@NonNull PrintWriter pw, boolean verbose) {
914             mJobId = UUID.randomUUID().toString();
915             if (verbose) {
916                 pw.printf(
917                         "Job running. To cancel it, run 'pm art cancel %s' in a separate shell.\n",
918                         mJobId);
919                 pw.flush();
920             }
921 
922             synchronized (sCancellationSignalMap) {
923                 sCancellationSignalMap.put(mJobId, mSignal);
924             }
925         }
926 
927         @NonNull
get()928         public CancellationSignal get() {
929             return mSignal;
930         }
931 
close()932         public void close() {
933             synchronized (sCancellationSignalMap) {
934                 sCancellationSignalMap.remove(mJobId);
935             }
936         }
937     }
938 }
939