1 /* 2 * Copyright (C) 2023 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 package com.android.tradefed.util.image; 17 18 import static org.junit.Assert.assertTrue; 19 20 import com.android.tradefed.build.IBuildInfo; 21 import com.android.tradefed.build.IDeviceBuildInfo; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.device.ITestDevice.RecoveryMode; 25 import com.android.tradefed.device.SnapuserdWaitPhase; 26 import com.android.tradefed.invoker.TestInformation; 27 import com.android.tradefed.invoker.logger.CurrentInvocation; 28 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 29 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationGroupMetricKey; 30 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 31 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 32 import com.android.tradefed.invoker.tracing.TracePropagatingExecutorService; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.result.error.InfraErrorIdentifier; 35 import com.android.tradefed.targetprep.TargetSetupError; 36 import com.android.tradefed.util.CommandResult; 37 import com.android.tradefed.util.CommandStatus; 38 import com.android.tradefed.util.FileUtil; 39 import com.android.tradefed.util.IRunUtil; 40 import com.android.tradefed.util.RunUtil; 41 import com.android.tradefed.util.ZipUtil; 42 import com.android.tradefed.util.ZipUtil2; 43 import com.android.tradefed.util.executor.ParallelDeviceExecutor; 44 import com.android.tradefed.util.image.DeviceImageTracker.FileCacheTracker; 45 46 import com.google.common.collect.ImmutableSet; 47 48 import java.io.File; 49 import java.io.IOException; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.HashMap; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.concurrent.Callable; 57 import java.util.concurrent.CompletableFuture; 58 import java.util.concurrent.ExecutionException; 59 import java.util.concurrent.Executors; 60 import java.util.concurrent.Future; 61 import java.util.concurrent.ThreadFactory; 62 import java.util.concurrent.TimeUnit; 63 import java.util.concurrent.atomic.AtomicInteger; 64 65 /** A utility to leverage the incremental image and device update. */ 66 public class IncrementalImageUtil { 67 68 private static final AtomicInteger poolNumber = new AtomicInteger(1); 69 70 public static final Set<String> DYNAMIC_PARTITIONS_TO_DIFF = 71 ImmutableSet.of( 72 "product.img", 73 "system.img", 74 "system_dlkm.img", 75 "system_ext.img", 76 "vendor.img", 77 "vendor_dlkm.img"); 78 79 private final File mSrcImage; 80 private final File mSrcBootloader; 81 private final File mSrcBaseband; 82 private final File mTargetImage; 83 private final ITestDevice mDevice; 84 private final File mCreateSnapshotBinary; 85 private final boolean mApplySnapshot; 86 private final SnapuserdWaitPhase mWaitPhase; 87 88 private boolean mAllowSameBuildFlashing = false; 89 private boolean mAllowUnzipBaseline = false; 90 private boolean mBootloaderNeedsFlashing = false; 91 private boolean mBasebandNeedsFlashing = false; 92 private boolean mUpdateWasCompleted = false; 93 private File mSourceDirectory; 94 private File mTargetDirectory; 95 96 private ParallelPreparation mParallelSetup; 97 private final IRunUtil mRunUtil; 98 initialize( ITestDevice device, IDeviceBuildInfo build, File createSnapshot, boolean isIsolatedSetup, boolean allowCrossRelease, boolean applySnapshot, SnapuserdWaitPhase waitPhase)99 public static IncrementalImageUtil initialize( 100 ITestDevice device, 101 IDeviceBuildInfo build, 102 File createSnapshot, 103 boolean isIsolatedSetup, 104 boolean allowCrossRelease, 105 boolean applySnapshot, 106 SnapuserdWaitPhase waitPhase) 107 throws DeviceNotAvailableException { 108 // With apply snapshot, device reset is supported 109 if (isIsolatedSetup && !applySnapshot) { 110 CLog.d("test is configured with isolation grade, doesn't support incremental yet."); 111 return null; 112 } 113 FileCacheTracker tracker = 114 DeviceImageTracker.getDefaultCache() 115 .getBaselineDeviceImage(device.getSerialNumber()); 116 if (tracker == null) { 117 CLog.d("Not tracking current baseline image."); 118 return null; 119 } 120 String deviceBuildId = device.getBuildId(); 121 if (!tracker.buildId.equals(deviceBuildId)) { 122 CLog.d( 123 "On-device build (id = %s) isn't matching the cache (id = %s).", 124 deviceBuildId, tracker.buildId); 125 InvocationMetricLogger.addInvocationMetrics( 126 InvocationMetricKey.DEVICE_IMAGE_CACHE_MISMATCH, 1); 127 return null; 128 } 129 if (!tracker.branch.equals(build.getBuildBranch())) { 130 CLog.d("Newer build is not on the same branch."); 131 return null; 132 } 133 boolean crossRelease = false; 134 if (!tracker.flavor.equals(build.getBuildFlavor())) { 135 if (allowCrossRelease) { 136 CLog.d( 137 "Allowing cross-flavor update from '%s' to '%s'", 138 tracker.flavor, build.getBuildFlavor()); 139 crossRelease = true; 140 } else { 141 CLog.d("Newer build is not on the build flavor."); 142 return null; 143 } 144 } 145 146 if (!isSnapshotSupported(device, applySnapshot)) { 147 CLog.d("Incremental flashing not supported."); 148 return null; 149 } 150 151 String splTarget = getSplVersion(build); 152 String splBaseline = device.getProperty("ro.build.version.security_patch"); 153 if (splTarget != null && !splBaseline.equals(splTarget)) { 154 CLog.d("Target SPL is '%s', while baseline is '%s", splTarget, splBaseline); 155 return null; 156 } 157 if (crossRelease) { 158 InvocationMetricLogger.addInvocationMetrics( 159 InvocationMetricKey.INCREMENTAL_ACROSS_RELEASE_COUNT, 1); 160 } 161 162 File deviceImage = null; 163 File bootloader = null; 164 File baseband = null; 165 try { 166 deviceImage = copyImage(tracker.zippedDeviceImage); 167 bootloader = copyImage(tracker.zippedBootloaderImage); 168 if (tracker.zippedBasebandImage != null) { 169 baseband = copyImage(tracker.zippedBasebandImage); 170 } 171 } catch (IOException e) { 172 InvocationMetricLogger.addInvocationMetrics( 173 InvocationMetricKey.DEVICE_IMAGE_CACHE_MISMATCH, 1); 174 CLog.e(e); 175 FileUtil.recursiveDelete(deviceImage); 176 FileUtil.deleteFile(bootloader); 177 FileUtil.deleteFile(baseband); 178 return null; 179 } 180 InvocationMetricLogger.addInvocationMetrics( 181 InvocationMetricKey.DEVICE_IMAGE_CACHE_ORIGIN, 182 String.format("%s:%s:%s", tracker.branch, tracker.buildId, tracker.flavor)); 183 return new IncrementalImageUtil( 184 device, 185 deviceImage, 186 bootloader, 187 baseband, 188 build.getDeviceImageFile(), 189 createSnapshot, 190 applySnapshot, 191 waitPhase); 192 } 193 IncrementalImageUtil( ITestDevice device, File deviceImage, File bootloader, File baseband, File targetImage, File createSnapshot, boolean applySnapshot, SnapuserdWaitPhase waitPhase)194 public IncrementalImageUtil( 195 ITestDevice device, 196 File deviceImage, 197 File bootloader, 198 File baseband, 199 File targetImage, 200 File createSnapshot, 201 boolean applySnapshot, 202 SnapuserdWaitPhase waitPhase) { 203 mDevice = device; 204 mSrcImage = deviceImage; 205 mSrcBootloader = bootloader; 206 mSrcBaseband = baseband; 207 mApplySnapshot = applySnapshot; 208 mWaitPhase = waitPhase; 209 210 mTargetImage = targetImage; 211 mRunUtil = new RunUtil(); 212 // TODO: clean up when docker image is updated 213 mRunUtil.setEnvVariable("LD_LIBRARY_PATH", "/tradefed/lib64"); 214 if (createSnapshot != null) { 215 File snapshot = createSnapshot; 216 try { 217 if (createSnapshot.getName().endsWith(".zip") 218 && ZipUtil.isZipFileValid(createSnapshot, false)) { 219 File destDir = ZipUtil2.extractZipToTemp(createSnapshot, "create_snapshot"); 220 snapshot = FileUtil.findFile(destDir, "create_snapshot"); 221 } 222 } catch (IOException e) { 223 CLog.e(e); 224 } 225 mCreateSnapshotBinary = snapshot; 226 FileUtil.chmodGroupRWX(snapshot); 227 } else { 228 mCreateSnapshotBinary = null; 229 } 230 mParallelSetup = 231 new ParallelPreparation( 232 Thread.currentThread().getThreadGroup(), mSrcImage, mTargetImage); 233 mParallelSetup.start(); 234 } 235 copyImage(File originalImage)236 private static File copyImage(File originalImage) throws IOException { 237 if (originalImage.isDirectory()) { 238 CLog.d("Baseline was already unzipped for %s", originalImage); 239 File copy = 240 FileUtil.createTempDir( 241 FileUtil.getBaseName(originalImage.getName()), 242 CurrentInvocation.getWorkFolder()); 243 FileUtil.recursiveHardlink(originalImage, copy); 244 return copy; 245 } else { 246 File copy = 247 FileUtil.createTempFile( 248 FileUtil.getBaseName(originalImage.getName()), 249 ".img", 250 CurrentInvocation.getWorkFolder()); 251 copy.delete(); 252 FileUtil.hardlinkFile(originalImage, copy); 253 return copy; 254 } 255 } 256 257 /** Returns whether or not we can use the snapshot logic to update the device */ isSnapshotSupported(ITestDevice device, boolean applySnapshot)258 public static boolean isSnapshotSupported(ITestDevice device, boolean applySnapshot) 259 throws DeviceNotAvailableException { 260 // Ensure snapshotctl exists 261 CommandResult whichOutput = device.executeShellV2Command("which snapshotctl"); 262 CLog.d("stdout: %s, stderr: %s", whichOutput.getStdout(), whichOutput.getStderr()); 263 if (!whichOutput.getStdout().contains("/system/bin/snapshotctl")) { 264 return false; 265 } 266 CommandResult helpOutput = device.executeShellV2Command("snapshotctl"); 267 CLog.d("stdout: %s, stderr: %s", helpOutput.getStdout(), helpOutput.getStderr()); 268 if (applySnapshot) { 269 if (helpOutput.getStdout().contains("apply-update") 270 || helpOutput.getStderr().contains("apply-update")) { 271 return true; 272 } 273 } else { 274 if (helpOutput.getStdout().contains("map-snapshots") 275 || helpOutput.getStderr().contains("map-snapshots")) { 276 return true; 277 } 278 } 279 return false; 280 } 281 notifyBootloaderNeedsRevert()282 public void notifyBootloaderNeedsRevert() { 283 mBootloaderNeedsFlashing = true; 284 } 285 notifyBasebadNeedsRevert()286 public void notifyBasebadNeedsRevert() { 287 mBasebandNeedsFlashing = true; 288 } 289 allowSameBuildFlashing()290 public void allowSameBuildFlashing() { 291 mAllowSameBuildFlashing = true; 292 } 293 isSameBuildFlashingAllowed()294 public boolean isSameBuildFlashingAllowed() { 295 return mAllowSameBuildFlashing; 296 } 297 allowUnzipBaseline()298 public void allowUnzipBaseline() { 299 mAllowUnzipBaseline = true; 300 } 301 302 /** Returns whether device is currently using snapshots or not. */ isSnapshotInUse(ITestDevice device)303 public static boolean isSnapshotInUse(ITestDevice device) throws DeviceNotAvailableException { 304 CommandResult dumpOutput = device.executeShellV2Command("snapshotctl dump"); 305 CLog.d("stdout: %s, stderr: %s", dumpOutput.getStdout(), dumpOutput.getStderr()); 306 if (dumpOutput.getStdout().contains("Using snapuserd: 0")) { 307 return false; 308 } 309 return true; 310 } 311 312 /** Updates the device using the snapshot logic. */ updateDevice(File currentBootloader, File currentRadio)313 public void updateDevice(File currentBootloader, File currentRadio) 314 throws DeviceNotAvailableException, TargetSetupError { 315 if (mDevice.isStateBootloaderOrFastbootd()) { 316 mDevice.rebootUntilOnline(); 317 } 318 if (!mDevice.enableAdbRoot()) { 319 throw new TargetSetupError( 320 "Failed to obtain root, this is required for incremental update.", 321 InfraErrorIdentifier.INCREMENTAL_FLASHING_ERROR); 322 } 323 try { 324 internalUpdateDevice(currentBootloader, currentRadio); 325 } catch (DeviceNotAvailableException | TargetSetupError | RuntimeException e) { 326 InvocationMetricLogger.addInvocationMetrics( 327 InvocationMetricKey.INCREMENTAL_FLASHING_UPDATE_FAILURE, 1); 328 throw e; 329 } 330 } 331 internalUpdateDevice(File currentBootloader, File currentRadio)332 private void internalUpdateDevice(File currentBootloader, File currentRadio) 333 throws DeviceNotAvailableException, TargetSetupError { 334 InvocationMetricLogger.addInvocationMetrics( 335 InvocationMetricKey.INCREMENTAL_FLASHING_ATTEMPT_COUNT, 1); 336 // Join the unzip thread 337 long startWait = System.currentTimeMillis(); 338 try { 339 mParallelSetup.join(); 340 } catch (InterruptedException e) { 341 mParallelSetup.cleanUpFiles(); 342 throw new RuntimeException(e); 343 } finally { 344 InvocationMetricLogger.addInvocationMetrics( 345 InvocationMetricKey.INCREMENTAL_FLASHING_WAIT_PARALLEL_SETUP, 346 System.currentTimeMillis() - startWait); 347 } 348 if (mParallelSetup.getError() != null) { 349 mParallelSetup.cleanUpFiles(); 350 InvocationMetricLogger.addInvocationMetrics( 351 InvocationMetricKey.INCREMENTAL_FALLBACK_REASON, 352 mParallelSetup.getError().getMessage()); 353 throw mParallelSetup.getError(); 354 } 355 boolean bootComplete = 356 mDevice.waitForBootComplete(mDevice.getOptions().getAvailableTimeout()); 357 if (!bootComplete) { 358 mParallelSetup.cleanUpFiles(); 359 throw new TargetSetupError( 360 "Failed to boot within timeout.", 361 InfraErrorIdentifier.INCREMENTAL_FLASHING_ERROR); 362 } 363 // We need a few seconds after boot complete for update_engine to finish 364 // TODO: we could improve by listening to some update_engine messages. 365 RunUtil.getDefault().sleep(5000L); 366 File srcDirectory = mParallelSetup.getSrcDirectory(); 367 File targetDirectory = mParallelSetup.getTargetDirectory(); 368 File workDir = mParallelSetup.getWorkDir(); 369 try (CloseableTraceScope ignored = new CloseableTraceScope("update_device")) { 370 // Once block comparison is successful, log the information 371 logTargetInformation(targetDirectory); 372 logPatchesInformation(workDir); 373 374 mDevice.executeShellV2Command("mkdir -p /data/ndb"); 375 mDevice.executeShellV2Command("rm -rf /data/ndb/*.patch"); 376 377 mDevice.executeShellV2Command("snapshotctl unmap-snapshots"); 378 mDevice.executeShellV2Command("snapshotctl delete-snapshots"); 379 380 RecoveryMode mode = mDevice.getRecoveryMode(); 381 mDevice.setRecoveryMode(RecoveryMode.NONE); 382 try { 383 List<Callable<Boolean>> pushTasks = new ArrayList<>(); 384 for (File f : workDir.listFiles()) { 385 try (CloseableTraceScope push = 386 new CloseableTraceScope("push:" + f.getName())) { 387 pushTasks.add( 388 () -> { 389 boolean success; 390 if (f.isDirectory()) { 391 success = mDevice.pushDir(f, "/data/ndb/"); 392 } else { 393 success = mDevice.pushFile(f, "/data/ndb/" + f.getName()); 394 } 395 CLog.d( 396 "Push status: %s. %s->%s", 397 success, f, "/data/ndb/" + f.getName()); 398 assertTrue(success); 399 return true; 400 }); 401 } 402 } 403 ParallelDeviceExecutor<Boolean> pushExec = 404 new ParallelDeviceExecutor<Boolean>(pushTasks.size()); 405 pushExec.invokeAll(pushTasks, 0, TimeUnit.MINUTES); 406 if (pushExec.hasErrors()) { 407 for (Throwable err : pushExec.getErrors()) { 408 InvocationMetricLogger.addInvocationMetrics( 409 InvocationMetricKey.INCREMENTAL_FALLBACK_REASON, err.getMessage()); 410 if (err instanceof DeviceNotAvailableException) { 411 throw (DeviceNotAvailableException) err; 412 } 413 } 414 throw new TargetSetupError( 415 String.format("Failed to push patches."), 416 pushExec.getErrors().get(0), 417 InfraErrorIdentifier.INCREMENTAL_FLASHING_ERROR); 418 } 419 } finally { 420 mDevice.setRecoveryMode(mode); 421 } 422 423 CommandResult listSnapshots = mDevice.executeShellV2Command("ls -l /data/ndb/"); 424 CLog.d("stdout: %s, stderr: %s", listSnapshots.getStdout(), listSnapshots.getStderr()); 425 426 if (mApplySnapshot) { 427 CommandResult mapOutput = 428 mDevice.executeShellV2Command("snapshotctl apply-update /data/ndb/"); 429 CLog.d("stdout: %s, stderr: %s", mapOutput.getStdout(), mapOutput.getStderr()); 430 if (!CommandStatus.SUCCESS.equals(mapOutput.getStatus())) { 431 InvocationMetricLogger.addInvocationMetrics( 432 InvocationMetricKey.INCREMENTAL_FALLBACK_REASON, "Failed apply-update"); 433 throw new TargetSetupError( 434 String.format( 435 "Failed to apply-update.\nstdout:%s\nstderr:%s", 436 mapOutput.getStdout(), mapOutput.getStderr()), 437 InfraErrorIdentifier.INCREMENTAL_FLASHING_ERROR); 438 } 439 } else { 440 CommandResult mapOutput = 441 mDevice.executeShellV2Command("snapshotctl map-snapshots /data/ndb/"); 442 CLog.d("stdout: %s, stderr: %s", mapOutput.getStdout(), mapOutput.getStderr()); 443 if (!CommandStatus.SUCCESS.equals(mapOutput.getStatus())) { 444 InvocationMetricLogger.addInvocationMetrics( 445 InvocationMetricKey.INCREMENTAL_FALLBACK_REASON, 446 "Failed map-snapshots"); 447 throw new TargetSetupError( 448 String.format( 449 "Failed to map the snapshots.\nstdout:%s\nstderr:%s", 450 mapOutput.getStdout(), mapOutput.getStderr()), 451 InfraErrorIdentifier.INCREMENTAL_FLASHING_ERROR); 452 } 453 } 454 if (mApplySnapshot) { 455 attemptBootloaderAndRadioFlashing(true, currentBootloader, currentRadio); 456 } 457 flashStaticPartition(targetDirectory); 458 mSourceDirectory = srcDirectory; 459 460 mDevice.enableAdbRoot(); 461 462 if (mApplySnapshot) { 463 mDevice.notifySnapuserd(mWaitPhase); 464 mDevice.waitForSnapuserd(SnapuserdWaitPhase.BLOCK_AFTER_UPDATE); 465 } else { 466 // If patches are mounted, just print snapuserd once 467 CommandResult psOutput = mDevice.executeShellV2Command("ps -ef | grep snapuserd"); 468 CLog.d("stdout: %s, stderr: %s", psOutput.getStdout(), psOutput.getStderr()); 469 } 470 mTargetDirectory = targetDirectory; 471 mUpdateWasCompleted = true; 472 } catch (DeviceNotAvailableException | RuntimeException e) { 473 if (mSourceDirectory == null) { 474 FileUtil.recursiveDelete(srcDirectory); 475 } 476 throw e; 477 } finally { 478 FileUtil.recursiveDelete(workDir); 479 } 480 } 481 482 /** Returns whether update was completed or not. */ updateCompleted()483 public boolean updateCompleted() { 484 return mUpdateWasCompleted; 485 } 486 getExtractedTargetDirectory()487 public File getExtractedTargetDirectory() { 488 return mTargetDirectory; 489 } 490 491 /** When doing some of the apply logic, we can clean up files right after setup. */ cleanAfterSetup()492 public void cleanAfterSetup() { 493 if (!mApplySnapshot) { 494 return; 495 } 496 // Delete the copy we made to use the incremental update 497 FileUtil.recursiveDelete(mSourceDirectory); 498 FileUtil.recursiveDelete(mTargetDirectory); 499 FileUtil.recursiveDelete(mSrcImage); 500 FileUtil.deleteFile(mSrcBootloader); 501 FileUtil.deleteFile(mSrcBaseband); 502 // In case of same build flashing, we should clean the setup operation 503 if (mParallelSetup != null) { 504 try { 505 mParallelSetup.join(); 506 } catch (InterruptedException e) { 507 CLog.e(e); 508 } 509 mParallelSetup.cleanUpFiles(); 510 } 511 } 512 513 /* 514 * Returns the device to its original state. 515 */ teardownDevice(TestInformation testInfo)516 public void teardownDevice(TestInformation testInfo) throws DeviceNotAvailableException { 517 try { 518 if (mApplySnapshot) { 519 return; 520 } 521 try (CloseableTraceScope ignored = new CloseableTraceScope("teardownDevice")) { 522 attemptBootloaderAndRadioFlashing(false, mSrcBootloader, mSrcBaseband); 523 if (mDevice.isStateBootloaderOrFastbootd()) { 524 mDevice.reboot(); 525 } 526 mDevice.enableAdbRoot(); 527 CommandResult revertOutput = 528 mDevice.executeShellV2Command( 529 "snapshotctl revert-snapshots", 60L, TimeUnit.SECONDS, 0); 530 if (!CommandStatus.SUCCESS.equals(revertOutput.getStatus())) { 531 CLog.d( 532 "Failed revert-snapshots. stdout: %s, stderr: %s", 533 revertOutput.getStdout(), revertOutput.getStderr()); 534 InvocationMetricLogger.addInvocationMetrics( 535 InvocationMetricKey.INCREMENTAL_FLASHING_TEARDOWN_FAILURE, 1); 536 } 537 if (mSourceDirectory != null) { 538 flashStaticPartition(mSourceDirectory); 539 } 540 if (mSourceDirectory != null && mAllowUnzipBaseline) { 541 DeviceImageTracker.getDefaultCache() 542 .trackUpdatedDeviceImage( 543 mDevice.getSerialNumber(), 544 mSourceDirectory, 545 mSrcBootloader, 546 mSrcBaseband, 547 testInfo.getBuildInfo().getBuildId(), 548 testInfo.getBuildInfo().getBuildBranch(), 549 testInfo.getBuildInfo().getBuildFlavor()); 550 } 551 } catch (DeviceNotAvailableException e) { 552 InvocationMetricLogger.addInvocationMetrics( 553 InvocationMetricKey.INCREMENTAL_FLASHING_TEARDOWN_FAILURE, 1); 554 throw e; 555 } 556 } finally { 557 // Delete the copy we made to use the incremental update 558 FileUtil.recursiveDelete(mSourceDirectory); 559 FileUtil.recursiveDelete(mTargetDirectory); 560 FileUtil.recursiveDelete(mSrcImage); 561 FileUtil.deleteFile(mSrcBootloader); 562 FileUtil.deleteFile(mSrcBaseband); 563 // In case of same build flashing, we should clean the setup operation 564 if (mParallelSetup != null) { 565 try { 566 mParallelSetup.join(); 567 } catch (InterruptedException e) { 568 CLog.e(e); 569 } 570 mParallelSetup.cleanUpFiles(); 571 } 572 } 573 } 574 attemptBootloaderAndRadioFlashing( boolean forceFlashing, File bootloader, File baseband)575 private void attemptBootloaderAndRadioFlashing( 576 boolean forceFlashing, File bootloader, File baseband) 577 throws DeviceNotAvailableException { 578 if (mBootloaderNeedsFlashing || forceFlashing) { 579 if (bootloader == null) { 580 CLog.w("No bootloader file to flash."); 581 } else { 582 mDevice.rebootIntoBootloader(); 583 584 CommandResult bootloaderFlashTarget = 585 mDevice.executeFastbootCommand( 586 "flash", "bootloader", bootloader.getAbsolutePath()); 587 CLog.d("Status: %s", bootloaderFlashTarget.getStatus()); 588 CLog.d("stdout: %s", bootloaderFlashTarget.getStdout()); 589 CLog.d("stderr: %s", bootloaderFlashTarget.getStderr()); 590 } 591 } 592 if (mBasebandNeedsFlashing || forceFlashing) { 593 if (baseband == null) { 594 CLog.w("No baseband file to flash"); 595 } else { 596 mDevice.rebootIntoBootloader(); 597 598 CommandResult radioFlashTarget = 599 mDevice.executeFastbootCommand( 600 "flash", "radio", baseband.getAbsolutePath()); 601 CLog.d("Status: %s", radioFlashTarget.getStatus()); 602 CLog.d("stdout: %s", radioFlashTarget.getStdout()); 603 CLog.d("stderr: %s", radioFlashTarget.getStderr()); 604 } 605 } 606 } 607 blockCompare(File srcImage, File targetImage, File workDir)608 private void blockCompare(File srcImage, File targetImage, File workDir) { 609 try (CloseableTraceScope ignored = 610 new CloseableTraceScope("block_compare:" + srcImage.getName())) { 611 mRunUtil.setWorkingDir(workDir); 612 613 String createSnapshot = "create_snapshot"; // Expected to be on PATH 614 if (mCreateSnapshotBinary != null && mCreateSnapshotBinary.exists()) { 615 createSnapshot = mCreateSnapshotBinary.getAbsolutePath(); 616 } 617 CommandResult result = 618 mRunUtil.runTimedCmd( 619 0L, 620 createSnapshot, 621 "--source=" + srcImage.getAbsolutePath(), 622 "--target=" + targetImage.getAbsolutePath()); 623 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 624 throw new RuntimeException( 625 String.format("%s\n%s", result.getStdout(), result.getStderr())); 626 } 627 File[] listFiles = workDir.listFiles(); 628 CLog.d("%s", Arrays.asList(listFiles)); 629 } 630 } 631 flashStaticPartition(File imageDirectory)632 private boolean flashStaticPartition(File imageDirectory) throws DeviceNotAvailableException { 633 // flash all static partition in bootloader 634 mDevice.rebootIntoBootloader(); 635 Map<String, String> envMap = new HashMap<>(); 636 envMap.put("ANDROID_PRODUCT_OUT", imageDirectory.getAbsolutePath()); 637 CommandResult fastbootResult = 638 mDevice.executeLongFastbootCommand( 639 envMap, 640 "flashall", 641 "--exclude-dynamic-partitions", 642 "--disable-super-optimization"); 643 CLog.d("Status: %s", fastbootResult.getStatus()); 644 CLog.d("stdout: %s", fastbootResult.getStdout()); 645 CLog.d("stderr: %s", fastbootResult.getStderr()); 646 if (!CommandStatus.SUCCESS.equals(fastbootResult.getStatus())) { 647 return false; 648 } 649 mDevice.waitForDeviceAvailable(5 * 60 * 1000L); 650 return true; 651 } 652 logPatchesInformation(File patchesDirectory)653 private void logPatchesInformation(File patchesDirectory) { 654 for (File patch : patchesDirectory.listFiles()) { 655 InvocationMetricLogger.addInvocationMetrics( 656 InvocationGroupMetricKey.INCREMENTAL_FLASHING_PATCHES_SIZE, 657 patch.getName(), 658 patch.length()); 659 } 660 } 661 logTargetInformation(File targetDirectory)662 private void logTargetInformation(File targetDirectory) { 663 for (File patch : targetDirectory.listFiles()) { 664 if (DYNAMIC_PARTITIONS_TO_DIFF.contains(patch.getName())) { 665 InvocationMetricLogger.addInvocationMetrics( 666 InvocationGroupMetricKey.INCREMENTAL_FLASHING_TARGET_SIZE, 667 patch.getName(), 668 patch.length()); 669 } 670 } 671 } 672 getSplVersion(IBuildInfo build)673 private static String getSplVersion(IBuildInfo build) { 674 File buildProp = build.getFile("build.prop"); 675 if (buildProp == null) { 676 CLog.d("No target build.prop found for comparison."); 677 return null; 678 } 679 try { 680 String props = FileUtil.readStringFromFile(buildProp); 681 for (String line : props.split("\n")) { 682 if (line.startsWith("ro.build.version.security_patch=")) { 683 return line.split("=")[1]; 684 } 685 } 686 } catch (IOException e) { 687 CLog.e(e); 688 } 689 return null; 690 } 691 692 private class ParallelPreparation extends Thread { 693 694 private final File mSetupSrcImage; 695 private final File mSetupTargetImage; 696 697 private File mSrcDirectory; 698 private File mTargetDirectory; 699 private File mWorkDir; 700 private TargetSetupError mError; 701 ParallelPreparation(ThreadGroup currentGroup, File srcImage, File targetImage)702 public ParallelPreparation(ThreadGroup currentGroup, File srcImage, File targetImage) { 703 super(currentGroup, "incremental-flashing-preparation"); 704 setDaemon(true); 705 this.mSetupSrcImage = srcImage; 706 this.mSetupTargetImage = targetImage; 707 } 708 709 @Override run()710 public void run() { 711 ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); 712 ThreadFactory factory = 713 new ThreadFactory() { 714 @Override 715 public Thread newThread(Runnable r) { 716 Thread t = 717 new Thread( 718 currentGroup, 719 r, 720 "unzip-pool-task-" + poolNumber.getAndIncrement()); 721 t.setDaemon(true); 722 return t; 723 } 724 }; 725 try (CloseableTraceScope ignored = new CloseableTraceScope("unzip_device_images")) { 726 mSrcDirectory = FileUtil.createTempDir("incremental_src"); 727 mTargetDirectory = FileUtil.createTempDir("incremental_target"); 728 Future<Boolean> futureSrcDir = 729 CompletableFuture.supplyAsync( 730 () -> { 731 try (CloseableTraceScope unzipBaseline = 732 new CloseableTraceScope("unzip_baseline")) { 733 if (mSetupSrcImage.isDirectory()) { 734 FileUtil.recursiveHardlink( 735 mSetupSrcImage, mSrcDirectory); 736 return true; 737 } 738 739 ZipUtil2.extractZip(mSetupSrcImage, mSrcDirectory); 740 return true; 741 } catch (IOException ioe) { 742 throw new RuntimeException(ioe); 743 } 744 }, 745 TracePropagatingExecutorService.create( 746 Executors.newFixedThreadPool(1, factory))); 747 Future<Boolean> futureTargetDir = 748 CompletableFuture.supplyAsync( 749 () -> { 750 try (CloseableTraceScope unzipTarget = 751 new CloseableTraceScope("unzip_target")) { 752 if (mSetupTargetImage.isDirectory()) { 753 FileUtil.recursiveHardlink( 754 mSetupTargetImage, mTargetDirectory); 755 return true; 756 } 757 ZipUtil2.extractZip(mSetupTargetImage, mTargetDirectory); 758 return true; 759 } catch (IOException ioe) { 760 throw new RuntimeException(ioe); 761 } 762 }, 763 TracePropagatingExecutorService.create( 764 Executors.newFixedThreadPool(1, factory))); 765 // Join the unzipping 766 futureSrcDir.get(); 767 futureTargetDir.get(); 768 } catch (InterruptedException | IOException | ExecutionException e) { 769 FileUtil.recursiveDelete(mSrcDirectory); 770 FileUtil.recursiveDelete(mTargetDirectory); 771 mSrcDirectory = null; 772 mTargetDirectory = null; 773 mError = 774 new TargetSetupError( 775 e.getMessage(), e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 776 return; 777 } 778 779 try { 780 mWorkDir = FileUtil.createTempDir("block_compare_workdir"); 781 } catch (IOException e) { 782 FileUtil.recursiveDelete(mWorkDir); 783 FileUtil.recursiveDelete(mSrcDirectory); 784 FileUtil.recursiveDelete(mTargetDirectory); 785 mSrcDirectory = null; 786 mTargetDirectory = null; 787 mError = 788 new TargetSetupError( 789 e.getMessage(), e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 790 return; 791 } 792 793 List<Callable<Boolean>> callableTasks = new ArrayList<>(); 794 for (String partition : mSrcDirectory.list()) { 795 File possibleSrc = new File(mSrcDirectory, partition); 796 File possibleTarget = new File(mTargetDirectory, partition); 797 File workDirectory = mWorkDir; 798 if (possibleSrc.exists() && possibleTarget.exists()) { 799 if (DYNAMIC_PARTITIONS_TO_DIFF.contains(partition)) { 800 callableTasks.add( 801 () -> { 802 blockCompare(possibleSrc, possibleTarget, workDirectory); 803 return true; 804 }); 805 } 806 } else { 807 CLog.e("Skipping %s no src or target", partition); 808 } 809 } 810 ParallelDeviceExecutor<Boolean> executor = 811 new ParallelDeviceExecutor<Boolean>(callableTasks.size()); 812 executor.invokeAll(callableTasks, 0, TimeUnit.MINUTES); 813 if (executor.hasErrors()) { 814 mError = 815 new TargetSetupError( 816 executor.getErrors().get(0).getMessage(), 817 executor.getErrors().get(0), 818 InfraErrorIdentifier.BLOCK_COMPARE_ERROR); 819 } 820 } 821 getSrcDirectory()822 public File getSrcDirectory() { 823 return mSrcDirectory; 824 } 825 getTargetDirectory()826 public File getTargetDirectory() { 827 return mTargetDirectory; 828 } 829 getWorkDir()830 public File getWorkDir() { 831 return mWorkDir; 832 } 833 getError()834 public TargetSetupError getError() { 835 return mError; 836 } 837 cleanUpFiles()838 public void cleanUpFiles() { 839 FileUtil.recursiveDelete(mSrcDirectory); 840 FileUtil.recursiveDelete(mTargetDirectory); 841 FileUtil.recursiveDelete(mWorkDir); 842 } 843 } 844 } 845