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