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