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