1 /* 2 * Copyright (C) 2020 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.targetprep; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.config.GlobalConfiguration; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.config.OptionClass; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.DeviceUnresponsiveException; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.device.ITestDevice.RecoveryMode; 26 import com.android.tradefed.device.SnapuserdWaitPhase; 27 import com.android.tradefed.device.TestDeviceState; 28 import com.android.tradefed.host.IHostOptions; 29 import com.android.tradefed.host.IHostOptions.PermitLimitType; 30 import com.android.tradefed.invoker.TestInformation; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.result.error.DeviceErrorIdentifier; 33 import com.android.tradefed.util.CommandResult; 34 import com.android.tradefed.util.CommandStatus; 35 import com.android.tradefed.util.FileUtil; 36 import com.android.tradefed.util.IRunUtil; 37 import com.android.tradefed.util.RunUtil; 38 import com.android.tradefed.util.TarUtil; 39 import com.android.tradefed.util.ZipUtil2; 40 import com.android.tradefed.util.image.DeviceImageTracker; 41 42 import com.google.common.annotations.VisibleForTesting; 43 import com.google.common.base.Strings; 44 import com.google.common.io.PatternFilenameFilter; 45 46 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; 47 import org.apache.commons.compress.archivers.zip.ZipFile; 48 49 import java.io.File; 50 import java.io.FileWriter; 51 import java.io.IOException; 52 import java.io.PrintWriter; 53 import java.nio.file.Files; 54 import java.nio.file.NoSuchFileException; 55 import java.nio.file.Path; 56 import java.util.ArrayList; 57 import java.util.Arrays; 58 import java.util.Collection; 59 import java.util.Enumeration; 60 import java.util.List; 61 import java.util.concurrent.TimeUnit; 62 import java.util.regex.Pattern; 63 import java.util.stream.Collectors; 64 import java.util.stream.Stream; 65 66 /** 67 * A target preparer that flash the device with android common kernel generic image. Please see 68 * https://source.android.com/devices/architecture/kernel/android-common for details. 69 */ 70 @OptionClass(alias = "gki-device-flash-preparer") 71 public class GkiDeviceFlashPreparer extends BaseTargetPreparer implements ILabPreparer { 72 73 private static final String AVBTOOL = "bin/avbtool"; 74 private static final String MKBOOTIMG = "bin/mkbootimg"; 75 private static final String BUILD_IMAGE = "bin/build_image"; 76 private static final String MKE2FS = "bin/mke2fs"; 77 private static final String MKUSERIMG_MKE2FS = "bin/mkuserimg_mke2fs"; 78 private static final String E2FSDROID = "bin/e2fsdroid"; 79 private static final String OTATOOLS_ZIP = "otatools.zip"; 80 private static final String KERNEL_IMAGE = "Image.gz"; 81 // Wait time for device state to stablize in millisecond 82 private static final int STATE_STABLIZATION_WAIT_TIME = 10000; 83 84 @Option( 85 name = "device-boot-time", 86 description = "max time to wait for device to boot. Set as 5 minutes by default", 87 isTimeVal = true) 88 private long mDeviceBootTime = 5 * 60 * 1000; 89 90 @Option( 91 name = "gki-boot-image-name", 92 description = "The file name in BuildInfo that provides GKI boot image.") 93 private String mGkiBootImageName = "gki_boot.img"; 94 95 @Option( 96 name = "ramdisk-image-name", 97 description = "The file name in BuildInfo that provides ramdisk image.") 98 private String mRamdiskImageName = "ramdisk.img"; 99 100 @Option( 101 name = "initramfs-image-name", 102 description = "The file name in BuildInfo that provides initramfs image.") 103 private String mInitramfsImageName = "initramfs.img"; 104 105 @Option( 106 name = "vendor-boot-image-name", 107 description = "The file name in BuildInfo that provides vendor boot image.") 108 private String mVendorBootImageName = "vendor_boot.img"; 109 110 @Option( 111 name = "vendor-kernel-boot-image-name", 112 description = "The file name in BuildInfo that provides vendor kernel boot image.") 113 private String mVendorKernelBootImageName = "vendor_kernel_boot.img"; 114 115 @Option( 116 name = "dtbo-image-name", 117 description = "The file name in BuildInfo that provides dtbo image.") 118 private String mDtboImageName = "dtbo.img"; 119 120 @Option( 121 name = "vendor-dlkm-image-name", 122 description = "The file name in BuildInfo that provides vendor_dlkm image.") 123 private String mVendorDlkmImageName = "vendor_dlkm.img"; 124 125 @Option( 126 name = "system-dlkm-image-name", 127 description = "The file name in BuildInfo that provides system_dlkm image.") 128 private String mSystemDlkmImageName = "system_dlkm.img"; 129 130 @Option( 131 name = "system-dlkm-archive-name", 132 description = 133 "The file name in BuildInfo that provides system_dlkm_staging_archive.tar.gz.") 134 private String mSystemDlkmArchiveName = "system_dlkm_staging_archive.tar.gz"; 135 136 @Option( 137 name = "vbmeta-image-name", 138 description = "The file name in BuildInfo that provides vbmeta image.") 139 private String mVbmetaImageName = "vbmeta.img"; 140 141 @Option( 142 name = "boot-image-file-name", 143 description = 144 "The boot image file name to search for if gki-boot-image-name in " 145 + "BuildInfo is a zip file or directory, for example boot-5.4-gz.img.") 146 private String mBootImageFileName = "boot(.*).img"; 147 148 @Option( 149 name = "vendor-boot-image-file-name", 150 description = 151 "The vendor boot image file name to search for if vendor-boot-image-name in " 152 + "BuildInfo is a zip file or directory, for example vendor_boot.img.") 153 private String mVendorBootImageFileName = "vendor_boot.img"; 154 155 @Option( 156 name = "vendor-kernel-boot-image-file-name", 157 description = 158 "The vendor kernel boot image file name to search for if " 159 + "vendor-kernel-boot-image-name in BuildInfo is a zip file or " 160 + "directory, for example vendor_kernel_boot.img.") 161 private String mVendorKernelBootImageFileName = "vendor_kernel_boot.img"; 162 163 @Option( 164 name = "dtbo-image-file-name", 165 description = 166 "The dtbo image file name to search for if dtbo-image-name in " 167 + "BuildInfo is a zip file or directory, for example dtbo.img.") 168 private String mDtboImageFileName = "dtbo.img"; 169 170 @Option( 171 name = "vendor-dlkm-image-file-name", 172 description = 173 "The vendor_dlkm image file name to search for if vendor-dlkm-image-name in " 174 + "BuildInfo is a zip file or directory, for example vendor_dlkm.img.") 175 private String mVendorDlkmImageFileName = "vendor_dlkm.img"; 176 177 @Option( 178 name = "system-dlkm-image-file-name", 179 description = 180 "The system_dlkm image file name to search for if system-dlkm-image-name in " 181 + "BuildInfo is a zip file or directory, for example system_dlkm.img.") 182 private String mSystemDlkmImageFileName = "system_dlkm.img"; 183 184 @Option( 185 name = "vbmeta-image-file-name", 186 description = 187 "The vbmeta image file name to search for if vbmeta-image-name in " 188 + "BuildInfo is a zip file or directory, for example vbmeta.img.") 189 private String mVbmetaImageFileName = "vbmeta.img"; 190 191 @Option( 192 name = "post-reboot-device-into-user-space", 193 description = "whether to boot the device in user space after flash.") 194 private boolean mPostRebootDeviceIntoUserSpace = true; 195 196 @Option( 197 name = "wipe-device-before-gki-flash", 198 description = "Whether to wipe device before GKI boot image flash.") 199 private boolean mShouldWipeDeviceBeforeFlash = false; 200 201 @Deprecated 202 @Option( 203 name = "wipe-device-after-gki-flash", 204 description = "deprecated, use option wipe-device-before-gki-flash instead.") 205 private boolean mShouldWipeDevice = false; 206 207 @Option(name = "disable-verity", description = "Whether to disable-verity.") 208 private boolean mShouldDisableVerity = false; 209 210 @Option(name = "oem-disable-verity", description = "Whether to run oem disable-verity.") 211 private boolean mShouldDisableOemVerity = false; 212 213 @Option( 214 name = "fastboot-flash-option", 215 description = "additional options to pass with fastboot flash command.") 216 private Collection<String> mFastbootFlashOptions = new ArrayList<>(); 217 218 @Option( 219 name = "additional-fastboot-command", 220 description = "additional fastboot command to run.") 221 private Collection<String> mFastbootCommands = new ArrayList<>(); 222 223 @Option( 224 name = "boot-header-version", 225 description = "The version of the boot.img header. Set to 3 by default.") 226 private int mBootHeaderVersion = 3; 227 228 @Option( 229 name = "add-hash-footer", 230 description = 231 "Add hash footer to GKI boot image. More info at " 232 + "https://android.googlesource.com/platform/external/avb/+/master/README.md") 233 private boolean mAddHashFooter = false; 234 235 @Option( 236 name = "security-patch-level", 237 description = 238 "The security patch level to sign the boot image when add-hash-footer is" 239 + " enabled.") 240 private String mSecurityPatchLevel = null; 241 242 @Option( 243 name = "boot-image-key-path", 244 description = 245 "The key path in otatools to sign the boot image when add-hash-footer is" 246 + " enabled.") 247 private String mBootImgKeyPath = "external/avb/test/data/testkey_rsa4096.pem"; 248 249 @Option( 250 name = "boot-image-key-algorithm", 251 description = 252 "The key algorithm to sign the boot image when add-hash-footer is enabled.") 253 private String mBootImgKeyAlgorithm = "SHA256_RSA4096"; 254 255 @Option(name = "support-fastbootd", description = "Whether the device supports fastbootd mode") 256 private boolean mSupportFastbootd = true; 257 258 private File mBootImg = null; 259 private File mSystemDlkmImg = null; 260 private Collection<String> mFlashOptions = new ArrayList<>(); 261 262 /** {@inheritDoc} */ 263 @Override setUp(TestInformation testInfo)264 public void setUp(TestInformation testInfo) 265 throws TargetSetupError, BuildError, DeviceNotAvailableException { 266 // If we use the GKI preparer invalidate baseline 267 DeviceImageTracker.getDefaultCache() 268 .invalidateTracking(testInfo.getDevice().getSerialNumber()); 269 ITestDevice device = testInfo.getDevice(); 270 IBuildInfo buildInfo = testInfo.getBuildInfo(); 271 272 mFlashOptions = 273 mFastbootFlashOptions.stream().map(String::trim).collect(Collectors.toList()); 274 File tmpDir = null; 275 try { 276 tmpDir = FileUtil.createTempDir("gki_preparer"); 277 validateGkiBootImg(device, buildInfo, tmpDir); 278 if (mAddHashFooter) { 279 addHashFooter(device, buildInfo, tmpDir); 280 } 281 buildGkiSystemDlkmImg(device, buildInfo, tmpDir); 282 flashGki(device, buildInfo, tmpDir); 283 } catch (IOException ioe) { 284 throw new TargetSetupError(ioe.getMessage(), ioe, device.getDeviceDescriptor()); 285 } finally { 286 FileUtil.recursiveDelete(tmpDir); 287 } 288 289 if (!mPostRebootDeviceIntoUserSpace) { 290 return; 291 } 292 // Wait some time after flashing the image. 293 getRunUtil().sleep(STATE_STABLIZATION_WAIT_TIME); 294 device.rebootUntilOnline(); 295 if (device.enableAdbRoot()) { 296 device.setDate(null); 297 } 298 try { 299 device.setRecoveryMode(RecoveryMode.AVAILABLE); 300 device.waitForDeviceAvailable(mDeviceBootTime); 301 } catch (DeviceUnresponsiveException e) { 302 // assume this is a build problem 303 throw new DeviceFailedToBootError( 304 String.format( 305 "Device %s did not become available after flashing GKI. Exception: %s", 306 device.getSerialNumber(), e), 307 device.getDeviceDescriptor(), 308 DeviceErrorIdentifier.ERROR_AFTER_FLASHING); 309 } 310 device.postBootSetup(); 311 CLog.i("Device update completed on %s", device.getDeviceDescriptor()); 312 } 313 314 /** 315 * Get a reference to the {@link IHostOptions} 316 * 317 * @return the {@link IHostOptions} to use 318 */ 319 @VisibleForTesting getHostOptions()320 protected IHostOptions getHostOptions() { 321 return GlobalConfiguration.getInstance().getHostOptions(); 322 } 323 324 /** 325 * Get the {@link IRunUtil} instance to use. 326 * 327 * @return the {@link IRunUtil} to use 328 */ 329 @VisibleForTesting getRunUtil()330 protected IRunUtil getRunUtil() { 331 return RunUtil.getDefault(); 332 } 333 334 /** 335 * Flash GKI images. 336 * 337 * @param device the {@link ITestDevice} 338 * @param buildInfo the {@link IBuildInfo} the build info 339 * @param tmpDir the temporary directory {@link File} 340 * @throws TargetSetupError, DeviceNotAvailableException, IOException 341 */ flashGki(ITestDevice device, IBuildInfo buildInfo, File tmpDir)342 private void flashGki(ITestDevice device, IBuildInfo buildInfo, File tmpDir) 343 throws TargetSetupError, DeviceNotAvailableException { 344 if (mShouldDisableVerity) { 345 device.enableAdbRoot(); 346 device.executeAdbCommand("disable-verity"); 347 device.reboot(); 348 } 349 device.rebootIntoBootloader(); 350 if (mShouldWipeDeviceBeforeFlash) { 351 executeFastbootCmd(device, "-w"); 352 } 353 if (mShouldDisableOemVerity) { 354 executeFastbootCmd(device, "oem disable-verity"); 355 } 356 long start = System.currentTimeMillis(); 357 getHostOptions().takePermit(PermitLimitType.CONCURRENT_FLASHER); 358 // Ensure snapuserd isn't running 359 device.waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING); 360 CLog.v( 361 "Flashing permit obtained after %ds", 362 TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start))); 363 // Don't allow interruptions during flashing operations. 364 getRunUtil().allowInterrupt(false); 365 try { 366 if (buildInfo.getFile(mVendorBootImageName) != null) { 367 File vendorBootImg = 368 getRequestedFile( 369 device, 370 mVendorBootImageFileName, 371 buildInfo.getFile(mVendorBootImageName), 372 tmpDir); 373 executeFastbootCmd(device, "flash", "vendor_boot", vendorBootImg.getAbsolutePath()); 374 } 375 if (buildInfo.getFile(mVendorKernelBootImageName) != null) { 376 File vendorKernelBootImg = 377 getRequestedFile( 378 device, 379 mVendorKernelBootImageFileName, 380 buildInfo.getFile(mVendorKernelBootImageName), 381 tmpDir); 382 executeFastbootCmd( 383 device, 384 "flash", 385 "vendor_kernel_boot", 386 vendorKernelBootImg.getAbsolutePath()); 387 } 388 if (buildInfo.getFile(mInitramfsImageName) != null) { 389 File initramfsImg = 390 getRequestedFile( 391 device, 392 mInitramfsImageName, 393 buildInfo.getFile(mInitramfsImageName), 394 tmpDir); 395 executeFastbootCmd( 396 device, "flash", "vendor_boot:dlkm", initramfsImg.getAbsolutePath()); 397 } 398 if (buildInfo.getFile(mDtboImageName) != null) { 399 File dtboImg = 400 getRequestedFile( 401 device, 402 mDtboImageFileName, 403 buildInfo.getFile(mDtboImageName), 404 tmpDir); 405 executeFastbootCmd(device, "flash", "dtbo", dtboImg.getAbsolutePath()); 406 } 407 408 executeFastbootCmd(device, "flash", "boot", mBootImg.getAbsolutePath()); 409 410 if (buildInfo.getFile(mVendorDlkmImageName) != null) { 411 File vendorDlkmImg = 412 getRequestedFile( 413 device, 414 mVendorDlkmImageFileName, 415 buildInfo.getFile(mVendorDlkmImageName), 416 tmpDir); 417 if (mSupportFastbootd 418 && !TestDeviceState.FASTBOOTD.equals(device.getDeviceState())) { 419 device.rebootIntoFastbootd(); 420 } 421 executeFastbootCmd(device, "flash", "vendor_dlkm", vendorDlkmImg.getAbsolutePath()); 422 } 423 424 if (buildInfo.getFile(mSystemDlkmImageName) != null) { 425 File systemDlkmImg = 426 getRequestedFile( 427 device, 428 mSystemDlkmImageFileName, 429 buildInfo.getFile(mSystemDlkmImageName), 430 tmpDir); 431 if (mSupportFastbootd 432 && !TestDeviceState.FASTBOOTD.equals(device.getDeviceState())) { 433 device.rebootIntoFastbootd(); 434 } 435 executeFastbootCmd(device, "flash", "system_dlkm", systemDlkmImg.getAbsolutePath()); 436 } 437 438 if (buildInfo.getFile(mVbmetaImageName) != null) { 439 File vbmetaImg = 440 getRequestedFile( 441 device, 442 mVbmetaImageFileName, 443 buildInfo.getFile(mVbmetaImageName), 444 tmpDir); 445 if (mSupportFastbootd 446 && !TestDeviceState.FASTBOOTD.equals(device.getDeviceState())) { 447 device.rebootIntoFastbootd(); 448 } 449 executeFastbootCmd(device, "flash", "vbmeta", vbmetaImg.getAbsolutePath()); 450 } 451 452 // Run additional fastboot command 453 for (String cmd : mFastbootCommands) { 454 executeFastbootCmd(device, cmd); 455 } 456 } finally { 457 getHostOptions().returnPermit(PermitLimitType.CONCURRENT_FLASHER); 458 // Allow interruption at the end no matter what. 459 getRunUtil().allowInterrupt(true); 460 CLog.v( 461 "Flashing permit returned after %ds", 462 TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start))); 463 } 464 } 465 466 /** 467 * Validate GKI boot image is expected. (Obsoleted. Please call with tmpDir provided) 468 * 469 * @param device the {@link ITestDevice} 470 * @param buildInfo the {@link IBuildInfo} the build info 471 * @throws TargetSetupError if there is no valid gki boot.img 472 */ validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo)473 public void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo) 474 throws TargetSetupError { 475 throw new TargetSetupError( 476 "Obsoleted. Please use validateGkiBootImg(ITestDevice, IBuildInfo, File)", 477 device.getDeviceDescriptor()); 478 } 479 480 /** 481 * Validate GKI boot image is expected. Throw exception if there is no valid boot.img. 482 * 483 * @param device the {@link ITestDevice} 484 * @param buildInfo the {@link IBuildInfo} the build info 485 * @param tmpDir the temporary directory {@link File} 486 * @throws TargetSetupError if there is no valid gki boot.img 487 */ 488 @VisibleForTesting validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)489 protected void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir) 490 throws TargetSetupError { 491 if (buildInfo.getFile(mGkiBootImageName) != null && mBootImageFileName != null) { 492 mBootImg = 493 getRequestedFile( 494 device, 495 mBootImageFileName, 496 buildInfo.getFile(mGkiBootImageName), 497 tmpDir); 498 return; 499 } 500 if (buildInfo.getFile(KERNEL_IMAGE) == null) { 501 throw new TargetSetupError( 502 KERNEL_IMAGE + " is not provided. Can not generate GKI boot.img.", 503 device.getDeviceDescriptor()); 504 } 505 if (buildInfo.getFile(mRamdiskImageName) == null) { 506 throw new TargetSetupError( 507 mRamdiskImageName + " is not provided. Can not generate GKI boot.img.", 508 device.getDeviceDescriptor()); 509 } 510 if (buildInfo.getFile(OTATOOLS_ZIP) == null) { 511 throw new TargetSetupError( 512 OTATOOLS_ZIP + " is not provided. Can not generate GKI boot.img.", 513 device.getDeviceDescriptor()); 514 } 515 try { 516 File mkbootimg = 517 getRequestedFile(device, MKBOOTIMG, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 518 mkbootimg.setExecutable(true, false); 519 mBootImg = FileUtil.createTempFile("boot", ".img", tmpDir); 520 String cmd = 521 String.format( 522 "%s --kernel %s --header_version %d --base 0x00000000 " 523 + "--pagesize 4096 --ramdisk %s -o %s", 524 mkbootimg.getAbsolutePath(), 525 buildInfo.getFile(KERNEL_IMAGE), 526 mBootHeaderVersion, 527 buildInfo.getFile(mRamdiskImageName), 528 mBootImg.getAbsolutePath()); 529 executeHostCommand(device, cmd); 530 CLog.i("The GKI boot.img is of size %d", mBootImg.length()); 531 if (mBootImg.length() == 0) { 532 throw new TargetSetupError( 533 "The mkbootimg tool didn't generate a valid boot.img.", 534 device.getDeviceDescriptor()); 535 } 536 buildInfo.setFile(mGkiBootImageName, mBootImg, "0"); 537 } catch (IOException e) { 538 throw new TargetSetupError( 539 "Fail to generate GKI boot.img.", e, device.getDeviceDescriptor()); 540 } 541 } 542 543 /** 544 * Extracts the system_dlkm tar gzip file into the system_dlkm_staging folder. This function is 545 * a wrapper around {@link TarUtil.extractTarGzipToTemp} in order to stub out the untarring for 546 * unit testing. 547 * 548 * @param systemDlkmArchive the system_dlkm tar gzip file containing GKI modules. 549 * @return File containing the system_dlkm tar gzip contents. 550 * @throws IOException 551 */ 552 @VisibleForTesting extractSystemDlkmTarGzip(File systemDlkmArchive)553 protected File extractSystemDlkmTarGzip(File systemDlkmArchive) throws IOException { 554 return TarUtil.extractTarGzipToTemp(systemDlkmArchive, "system_dlkm_staging"); 555 } 556 557 /** 558 * Flatten the system_dlkm staging directory so that all the kernel modules are directly under 559 * /lib/modules. This is necessary to match the expected system_dlkm file layout for platform 560 * builds. 561 * 562 * @param device the {@link ITestDevice} 563 * @param systemDlkmStagingDir the system_dlkm staging directory {@link File} 564 * @throws IOException or TargetSetupError if there is an error flattening the system_dlkm. 565 */ 566 @VisibleForTesting flattenSystemDlkm(ITestDevice device, File systemDlkmStagingDir)567 protected void flattenSystemDlkm(ITestDevice device, File systemDlkmStagingDir) 568 throws IOException, TargetSetupError { 569 File systemStagingLibModulesDir = new File(systemDlkmStagingDir, "lib/modules"); 570 571 // Move all modules from the kernel directory to /lib/modules 572 Path libModulesPath = systemStagingLibModulesDir.toPath(); 573 File[] libModulesVersionFiles = systemStagingLibModulesDir.listFiles(); 574 File libModulesVersionDir = null; 575 if (libModulesVersionFiles.length == 1) { 576 // Move all the files under the kernel version folder to be 577 // under lib/modules. 578 libModulesVersionDir = libModulesVersionFiles[0]; 579 for (File file : libModulesVersionDir.listFiles()) { 580 if (file.isFile()) { 581 File hardLink = new File(systemStagingLibModulesDir, file.getName()); 582 try { 583 FileUtil.hardlinkFile(file, hardLink, true); 584 } catch (IOException e) { 585 throw new TargetSetupError( 586 String.format( 587 "Failed to create hardlink of %s to %s", 588 file.toString(), hardLink.toString()), 589 device.getDeviceDescriptor()); 590 } 591 } 592 } 593 } 594 595 Path libModulesKernel = 596 new File( 597 libModulesVersionDir != null 598 ? libModulesVersionDir 599 : systemStagingLibModulesDir, 600 "kernel") 601 .toPath(); 602 try (Stream<Path> allPaths = Files.walk(libModulesKernel)) { 603 Path[] modulePaths = 604 allPaths.filter(path -> path.toString().endsWith(".ko")).toArray(Path[]::new); 605 for (Path path : modulePaths) { 606 File hardLink = new File(systemStagingLibModulesDir, path.toFile().getName()); 607 try { 608 FileUtil.hardlinkFile(path.toFile(), hardLink, true); 609 } catch (IOException e) { 610 throw new TargetSetupError( 611 String.format( 612 "Failed to create a hardlink of %s to %s", 613 path.toString(), hardLink.toString()), 614 device.getDeviceDescriptor()); 615 } 616 } 617 } catch (NoSuchFileException e) { 618 // Not a problem. Just means there's either no modules or the 619 // tarball is already flat. 620 CLog.i("Didn't find a kernel directory under lib/modules"); 621 } 622 if (libModulesVersionDir != null) { 623 FileUtil.recursiveDelete(libModulesVersionDir); 624 } else if (libModulesKernel != null) { 625 FileUtil.recursiveDelete(libModulesKernel.toFile()); 626 } 627 628 // Remove modules.*.bin and modules.order. These aren't used or 629 // included in the platform system_dlkm image. 630 File[] files = 631 libModulesPath.toFile().listFiles(new PatternFilenameFilter("modules\\..*\\.bin")); 632 for (File f : files) { 633 Files.deleteIfExists(f.toPath()); 634 } 635 Files.deleteIfExists(libModulesPath.resolve("modules.order")); 636 637 File[] depmodFiles = 638 libModulesPath.toFile().listFiles(new PatternFilenameFilter("modules\\..*")); 639 640 // Update the depmod files that reference the kernel modules to use the 641 // new path. 642 for (File f : depmodFiles) { 643 String contents = FileUtil.readStringFromFile(f); 644 contents = 645 Pattern.compile("kernel[^: \n\t]*/([^: \n\t]+\\.ko)") 646 .matcher(contents) 647 .replaceAll("$1"); 648 FileUtil.writeToFile(contents, f); 649 } 650 } 651 652 /** 653 * Build GKI system_dlkm image if the system_dlkm archive is provided. 654 * 655 * @param device the {@link ITestDevice} 656 * @param buildInfo the {@link IBuildInfo} the build info 657 * @param tmpDir the temporary directory {@link File} 658 * @throws TargetSetupError if there is an error building the image file. 659 */ 660 @VisibleForTesting buildGkiSystemDlkmImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)661 protected void buildGkiSystemDlkmImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir) 662 throws TargetSetupError { 663 File systemDlkmStagingDir = null; 664 665 if (buildInfo.getFile(mSystemDlkmArchiveName) == null) { 666 /* Nothing to do here */ 667 return; 668 } 669 670 File systemDlkmArchive = 671 getRequestedFile( 672 device, 673 mSystemDlkmArchiveName, 674 buildInfo.getFile(mSystemDlkmArchiveName), 675 tmpDir); 676 if (systemDlkmArchive == null) { 677 throw new TargetSetupError( 678 mSystemDlkmArchiveName 679 + " is not provided. Can not generate GKI system_dlkm.img.", 680 device.getDeviceDescriptor()); 681 } 682 683 if (buildInfo.getFile(OTATOOLS_ZIP) == null) { 684 throw new TargetSetupError( 685 OTATOOLS_ZIP + " is not provided. Can not generate GKI system_dlkm.img.", 686 device.getDeviceDescriptor()); 687 } 688 689 File build_image = 690 getRequestedFile(device, BUILD_IMAGE, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 691 // Get build_image dependencies 692 File mkuserimg_mke2fs = 693 getRequestedFile(device, MKUSERIMG_MKE2FS, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 694 File mke2fs = getRequestedFile(device, MKE2FS, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 695 File e2fsdroid = 696 getRequestedFile(device, E2FSDROID, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 697 build_image.setExecutable(true, false); 698 mkuserimg_mke2fs.setExecutable(true, false); 699 mke2fs.setExecutable(true, false); 700 e2fsdroid.setExecutable(true, false); 701 702 try { 703 systemDlkmStagingDir = extractSystemDlkmTarGzip(systemDlkmArchive); 704 flattenSystemDlkm(device, systemDlkmStagingDir); 705 706 // Create temporary files for the system_dlkm properties and file contexts 707 File systemDlkmPropsFile = new File(tmpDir, "system_dlkm.props"); 708 File systemDlkmFileContexts = new File(tmpDir, "system_dlkm_file_contexts"); 709 710 // These are defaults GKI uses. We might want to pull this file from 711 // a device build if devices require different properties. 712 PrintWriter systemDlkmFileContextsWriter = 713 new PrintWriter(new FileWriter(systemDlkmFileContexts)); 714 systemDlkmFileContextsWriter.println( 715 "/system_dlkm(/.*)? u:object_r:system_dlkm_file:s0"); 716 systemDlkmFileContextsWriter.close(); 717 718 PrintWriter systemDlkmPropsPrintWriter = 719 new PrintWriter(new FileWriter(systemDlkmPropsFile)); 720 systemDlkmPropsPrintWriter.println("fs_type=ext4"); 721 systemDlkmPropsPrintWriter.println("use_dynamic_partition_size=true"); 722 systemDlkmPropsPrintWriter.println("ext_mkuserimg=mkuserimg_mke2fs"); 723 systemDlkmPropsPrintWriter.println("ext4_share_dup_blocks=true"); 724 systemDlkmPropsPrintWriter.println("extfs_rsv_pct=0"); 725 systemDlkmPropsPrintWriter.println("journal_size=0"); 726 systemDlkmPropsPrintWriter.println("mount_point=system_dlkm"); 727 systemDlkmPropsPrintWriter.println( 728 String.format("selinux_fc=%s", systemDlkmFileContexts.getAbsolutePath())); 729 systemDlkmPropsPrintWriter.close(); 730 731 mSystemDlkmImg = new File(tmpDir, "system_dlkm.img"); 732 String buildImageCmd = 733 String.format( 734 "%s %s %s %s /dev/null", 735 build_image.getAbsolutePath(), 736 systemDlkmStagingDir.getAbsolutePath(), 737 systemDlkmPropsFile.getAbsolutePath(), 738 mSystemDlkmImg.getAbsolutePath()); 739 executeHostCommand(device, buildImageCmd); 740 CLog.i("The GKI system_dlkm.img is of size %d", mSystemDlkmImg.length()); 741 if (mSystemDlkmImg.length() == 0) { 742 throw new TargetSetupError( 743 "The build_image tool didn't generate a valid system_dlkm.img. (size=0)", 744 device.getDeviceDescriptor()); 745 } 746 buildInfo.setFile(mSystemDlkmImageName, mSystemDlkmImg, "0"); 747 } catch (IOException e) { 748 throw new TargetSetupError( 749 "Failed to generate GKI system_dlkm.img.", e, device.getDeviceDescriptor()); 750 } finally { 751 // Clean up the system dlkm staging dir 752 FileUtil.recursiveDelete(systemDlkmStagingDir); 753 } 754 755 File avbtool = getRequestedFile(device, AVBTOOL, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 756 avbtool.setExecutable(true, false); 757 String cmd = 758 String.format( 759 "%s add_hashtree_footer --do_not_generate_fec " 760 + "--hash_algorithm sha256 " 761 + "--image %s " 762 + "--partition_name system_dlkm", 763 avbtool.getAbsolutePath(), mSystemDlkmImg.getAbsolutePath()); 764 executeHostCommand(device, cmd); 765 } 766 767 /** 768 * Validate GKI boot image is expected. Throw exception if there is no valid boot.img. 769 * 770 * @param device the {@link ITestDevice} 771 * @param buildInfo the {@link IBuildInfo} the build info 772 * @param tmpDir the temporary directory {@link File} 773 * @throws TargetSetupError if there is no valid gki boot.img 774 */ 775 @VisibleForTesting addHashFooter(ITestDevice device, IBuildInfo buildInfo, File tmpDir)776 protected void addHashFooter(ITestDevice device, IBuildInfo buildInfo, File tmpDir) 777 throws TargetSetupError, DeviceNotAvailableException { 778 if (mBootImg == null) { 779 throw new TargetSetupError( 780 mGkiBootImageName + " is not provided. Can not add hash footer to it.", 781 device.getDeviceDescriptor()); 782 } 783 if (buildInfo.getFile(OTATOOLS_ZIP) == null) { 784 throw new TargetSetupError( 785 OTATOOLS_ZIP + " is not provided. Can not add hash footer to GKI boot.img.", 786 device.getDeviceDescriptor()); 787 } 788 File avbtool = getRequestedFile(device, AVBTOOL, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 789 avbtool.setExecutable(true, false); 790 File boot_img_key = 791 getRequestedFile(device, mBootImgKeyPath, buildInfo.getFile(OTATOOLS_ZIP), tmpDir); 792 793 String android_version = device.getProperty("ro.build.version.release"); 794 if (Strings.isNullOrEmpty(android_version)) { 795 throw new TargetSetupError( 796 "Can not get android version from property ro.build.version.release.", 797 device.getDeviceDescriptor()); 798 } 799 if (Strings.isNullOrEmpty(mSecurityPatchLevel)) { 800 mSecurityPatchLevel = device.getProperty("ro.build.version.security_patch"); 801 if (Strings.isNullOrEmpty(mSecurityPatchLevel)) { 802 throw new TargetSetupError( 803 "--security-patch-level is not provided. Can not get security patch version" 804 + " from property ro.build.version.security_patch.", 805 device.getDeviceDescriptor()); 806 } 807 } 808 809 String command = String.format("du -b %s", mBootImg.getAbsolutePath()); 810 CommandResult cmdResult = executeHostCommand(device, command); 811 String partition_size = cmdResult.getStdout().split("\\s+")[0]; 812 CLog.i("Boot image partition size: %s", partition_size); 813 String cmd = 814 String.format( 815 "%s add_hash_footer --image %s --partition_size %s " 816 + "--algorithm %s " 817 + "--key %s " 818 + "--partition_name boot " 819 + "--prop com.android.build.boot.os_version:%s " 820 + "--prop com.android.build.boot.security_patch:%s", 821 avbtool.getAbsolutePath(), 822 mBootImg.getAbsolutePath(), 823 partition_size, 824 mBootImgKeyAlgorithm, 825 boot_img_key, 826 android_version, 827 mSecurityPatchLevel); 828 executeHostCommand(device, cmd); 829 } 830 831 /** 832 * Helper method to execute host command. 833 * 834 * @param device the {@link ITestDevice} 835 * @param command the command string 836 * @return the CommandResult 837 * @throws TargetSetupError, DeviceNotAvailableException 838 */ executeHostCommand(ITestDevice device, final String command)839 private CommandResult executeHostCommand(ITestDevice device, final String command) 840 throws TargetSetupError { 841 final CommandResult result = getRunUtil().runTimedCmd(300000L, command.split("\\s+")); 842 switch (result.getStatus()) { 843 case SUCCESS: 844 CLog.i( 845 "Command %s finished successfully, stdout = [%s].", 846 command, result.getStdout().trim()); 847 break; 848 case FAILED: 849 throw new TargetSetupError( 850 String.format( 851 "Command %s failed, stdout = [%s], stderr = [%s].", 852 command, result.getStdout().trim(), result.getStderr().trim()), 853 device.getDeviceDescriptor()); 854 case TIMED_OUT: 855 throw new TargetSetupError( 856 String.format("Command %s timed out.", command), 857 device.getDeviceDescriptor()); 858 case EXCEPTION: 859 throw new TargetSetupError( 860 String.format("Exception occurred when running command %s.", command), 861 device.getDeviceDescriptor()); 862 } 863 return result; 864 } 865 866 /** 867 * Get the requested file from the source file (zip or folder) by requested file name. 868 * 869 * <p>The provided source file can be a zip file. The method will unzip it to tempary directory 870 * and find the requested file by the provided file name. 871 * 872 * <p>The provided source file can be a file folder. The method will find the requestd file by 873 * the provided file name. 874 * 875 * @param device the {@link ITestDevice} 876 * @param requestedFileName the requeste file name String 877 * @param sourceFile the source file 878 * @return the file that is specified by the requested file name 879 * @throws TargetSetupError 880 */ 881 @VisibleForTesting getRequestedFile( ITestDevice device, String requestedFileName, File sourceFile, File tmpDir)882 protected File getRequestedFile( 883 ITestDevice device, String requestedFileName, File sourceFile, File tmpDir) 884 throws TargetSetupError { 885 File requestedFile = null; 886 String baseFileName = new File(requestedFileName).getName(); 887 String subdirPathName = new File(requestedFileName).getParent(); 888 889 if (sourceFile.getName().endsWith(".zip")) { 890 try (ZipFile sourceZipFile = new ZipFile(sourceFile)) { 891 File destDir = 892 FileUtil.createNamedTempDir( 893 tmpDir, FileUtil.getBaseName(sourceFile.getName()) + "_zip"); 894 File subdir = null; 895 if (subdirPathName != null && !subdirPathName.isEmpty()) { 896 subdir = FileUtil.createNamedTempDir(destDir, subdirPathName); 897 } 898 requestedFile = new File(subdir != null ? subdir : destDir, baseFileName); 899 ZipUtil2.extractFileFromZip(sourceZipFile, requestedFileName, requestedFile); 900 if (!requestedFile.exists()) { 901 /* Let's search for the file within the zip archive in case of a regex 902 * filename before giving up. */ 903 final Enumeration<ZipArchiveEntry> entries = sourceZipFile.getEntries(); 904 while (entries.hasMoreElements()) { 905 final ZipArchiveEntry entry = entries.nextElement(); 906 if (entry.isDirectory() || !entry.getName().matches(requestedFileName)) { 907 continue; 908 } 909 requestedFile = 910 new File(subdir != null ? subdir : destDir, entry.getName()); 911 FileUtil.writeToFile(sourceZipFile.getInputStream(entry), requestedFile); 912 break; 913 } 914 } 915 } catch (IOException e) { 916 throw new TargetSetupError( 917 String.format("Fail to get %s from %s", requestedFileName, sourceFile), 918 e, 919 device.getDeviceDescriptor()); 920 } 921 } else if (sourceFile.isDirectory()) { 922 requestedFile = FileUtil.findFile(sourceFile, requestedFileName); 923 } else { 924 requestedFile = sourceFile; 925 } 926 if (requestedFile == null || !requestedFile.exists()) { 927 throw new TargetSetupError( 928 String.format( 929 "Requested file with file_name %s does not exist in provided %s.", 930 requestedFileName, sourceFile), 931 device.getDeviceDescriptor()); 932 } 933 return requestedFile; 934 } 935 936 /** 937 * Helper method to execute a fastboot command. 938 * 939 * @param device the {@link ITestDevice} to execute command on 940 * @param cmdArgs the arguments to provide to fastboot 941 * @return String the stderr output from command if non-empty. Otherwise returns the stdout Some 942 * fastboot commands are weird in that they dump output to stderr on success case 943 * @throws DeviceNotAvailableException if device is not available 944 * @throws TargetSetupError if fastboot command fails 945 */ executeFastbootCmd(ITestDevice device, String... cmdArgs)946 private String executeFastbootCmd(ITestDevice device, String... cmdArgs) 947 throws DeviceNotAvailableException, TargetSetupError { 948 List<String> fastbootCmdArgs = new ArrayList<>(); 949 if ("flash".equals(cmdArgs[0])) { 950 fastbootCmdArgs.addAll(mFlashOptions); 951 } 952 fastbootCmdArgs.addAll(Arrays.asList(cmdArgs)); 953 CLog.i( 954 "Execute fastboot command '%s' on %s", 955 String.join(" ", fastbootCmdArgs), device.getSerialNumber()); 956 CommandResult result = 957 device.executeLongFastbootCommand( 958 fastbootCmdArgs.toArray(new String[fastbootCmdArgs.size()])); 959 if (result == null) { 960 throw new TargetSetupError( 961 String.format( 962 "CommandResult with fastboot command '%s' is null", 963 String.join(" ", fastbootCmdArgs)), 964 device.getDeviceDescriptor(), 965 DeviceErrorIdentifier.ERROR_AFTER_FLASHING); 966 } 967 CLog.v("fastboot stdout: " + result.getStdout()); 968 CLog.v("fastboot stderr: " + result.getStderr()); 969 CommandStatus cmdStatus = result.getStatus(); 970 // fastboot command line output is in stderr even for successful run 971 if (result.getStderr().contains("FAILED")) { 972 // if output contains "FAILED", just override to failure 973 cmdStatus = CommandStatus.FAILED; 974 } 975 if (cmdStatus != CommandStatus.SUCCESS) { 976 throw new TargetSetupError( 977 String.format( 978 "fastboot command '%s' failed in device %s. stdout: %s, stderr: %s", 979 String.join(" ", fastbootCmdArgs), 980 device.getSerialNumber(), 981 result.getStdout(), 982 result.getStderr()), 983 device.getDeviceDescriptor(), 984 DeviceErrorIdentifier.ERROR_AFTER_FLASHING); 985 } 986 if (result.getStderr().length() > 0) { 987 return result.getStderr(); 988 } else { 989 return result.getStdout(); 990 } 991 } 992 } 993