blob: 924fbaf6dc012c21d5589874638ef06dcb64fa72 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.SnapuserdWaitPhase;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.device.TestDeviceState;
import com.android.tradefed.host.IHostOptions;
import com.android.tradefed.host.IHostOptions.PermitLimitType;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.TarUtil;
import com.android.tradefed.util.ZipUtil2;
import com.android.tradefed.util.image.DeviceImageTracker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.io.PatternFilenameFilter;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/**
* A target preparer that flash the device with android common kernel generic image. Please see
* https://source.android.com/devices/architecture/kernel/android-common for details.
*/
@OptionClass(alias = "gki-device-flash-preparer")
public class GkiDeviceFlashPreparer extends BaseTargetPreparer implements ILabPreparer {
private static final String AVBTOOL = "bin/avbtool";
private static final String MKBOOTIMG = "bin/mkbootimg";
private static final String BUILD_IMAGE = "bin/build_image";
private static final String MKE2FS = "bin/mke2fs";
private static final String MKUSERIMG_MKE2FS = "bin/mkuserimg_mke2fs";
private static final String E2FSDROID = "bin/e2fsdroid";
private static final String OTATOOLS_ZIP = "otatools.zip";
private static final String KERNEL_IMAGE = "Image.gz";
// Wait time for device state to stablize in millisecond
private static final int STATE_STABLIZATION_WAIT_TIME = 60000;
@Option(
name = "device-boot-time",
description = "max time to wait for device to boot. Set as 5 minutes by default",
isTimeVal = true)
private long mDeviceBootTime = 5 * 60 * 1000;
@Option(
name = "gki-boot-image-name",
description = "The file name in BuildInfo that provides GKI boot image.")
private String mGkiBootImageName = "gki_boot.img";
@Option(
name = "ramdisk-image-name",
description = "The file name in BuildInfo that provides ramdisk image.")
private String mRamdiskImageName = "ramdisk.img";
@Option(
name = "vendor-boot-image-name",
description = "The file name in BuildInfo that provides vendor boot image.")
private String mVendorBootImageName = "vendor_boot.img";
@Option(
name = "vendor-kernel-boot-image-name",
description = "The file name in BuildInfo that provides vendor kernel boot image.")
private String mVendorKernelBootImageName = "vendor_kernel_boot.img";
@Option(
name = "dtbo-image-name",
description = "The file name in BuildInfo that provides dtbo image.")
private String mDtboImageName = "dtbo.img";
@Option(
name = "vendor-dlkm-image-name",
description = "The file name in BuildInfo that provides vendor_dlkm image.")
private String mVendorDlkmImageName = "vendor_dlkm.img";
@Option(
name = "system-dlkm-image-name",
description = "The file name in BuildInfo that provides system_dlkm image.")
private String mSystemDlkmImageName = "system_dlkm.img";
@Option(
name = "system-dlkm-archive-name",
description =
"The file name in BuildInfo that provides system_dlkm_staging_archive.tar.gz.")
private String mSystemDlkmArchiveName = "system_dlkm_staging_archive.tar.gz";
@Option(
name = "boot-image-file-name",
description =
"The boot image file name to search for if gki-boot-image-name in "
+ "BuildInfo is a zip file or directory, for example boot-5.4-gz.img.")
private String mBootImageFileName = "boot(.*).img";
@Option(
name = "vendor-boot-image-file-name",
description =
"The vendor boot image file name to search for if vendor-boot-image-name in "
+ "BuildInfo is a zip file or directory, for example vendor_boot.img.")
private String mVendorBootImageFileName = "vendor_boot.img";
@Option(
name = "vendor-kernel-boot-image-file-name",
description =
"The vendor kernel boot image file name to search for if "
+ "vendor-kernel-boot-image-name in BuildInfo is a zip file or "
+ "directory, for example vendor_kernel_boot.img.")
private String mVendorKernelBootImageFileName = "vendor_kernel_boot.img";
@Option(
name = "dtbo-image-file-name",
description =
"The dtbo image file name to search for if dtbo-image-name in "
+ "BuildInfo is a zip file or directory, for example dtbo.img.")
private String mDtboImageFileName = "dtbo.img";
@Option(
name = "vendor-dlkm-image-file-name",
description =
"The vendor_dlkm image file name to search for if vendor-dlkm-image-name in "
+ "BuildInfo is a zip file or directory, for example vendor_dlkm.img.")
private String mVendorDlkmImageFileName = "vendor_dlkm.img";
@Option(
name = "system-dlkm-image-file-name",
description =
"The system_dlkm image file name to search for if system-dlkm-image-name in "
+ "BuildInfo is a zip file or directory, for example system_dlkm.img.")
private String mSystemDlkmImageFileName = "system_dlkm.img";
@Option(
name = "post-reboot-device-into-user-space",
description = "whether to boot the device in user space after flash.")
private boolean mPostRebootDeviceIntoUserSpace = true;
@Option(
name = "wipe-device-after-gki-flash",
description = "Whether to wipe device after GKI boot image flash.")
private boolean mShouldWipeDevice = true;
@Option(name = "oem-disable-verity", description = "Whether to run oem disable-verity.")
private boolean mShouldDisableOemVerity = false;
@Option(
name = "boot-header-version",
description = "The version of the boot.img header. Set to 3 by default.")
private int mBootHeaderVersion = 3;
@Option(
name = "add-hash-footer",
description =
"Add hash footer to GKI boot image. More info at "
+ "https://android.googlesource.com/platform/external/avb/+/master/README.md")
private boolean mAddHashFooter = false;
private File mBootImg = null;
private File mSystemDlkmImg = null;
/** {@inheritDoc} */
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
// If we use the GKI preparer invalidate baseline
DeviceImageTracker.getDefaultCache()
.invalidateTracking(testInfo.getDevice().getSerialNumber());
ITestDevice device = testInfo.getDevice();
IBuildInfo buildInfo = testInfo.getBuildInfo();
File tmpDir = null;
try {
tmpDir = FileUtil.createTempDir("gki_preparer");
validateGkiBootImg(device, buildInfo, tmpDir);
if (mAddHashFooter) {
addHashFooter(device, buildInfo, tmpDir);
}
buildGkiSystemDlkmImg(device, buildInfo, tmpDir);
flashGki(device, buildInfo, tmpDir);
} catch (IOException ioe) {
throw new TargetSetupError(ioe.getMessage(), ioe, device.getDeviceDescriptor());
} finally {
FileUtil.recursiveDelete(tmpDir);
}
if (!mPostRebootDeviceIntoUserSpace) {
return;
}
// Wait some time after flashing the image.
getRunUtil().sleep(STATE_STABLIZATION_WAIT_TIME);
device.rebootUntilOnline();
if (device.enableAdbRoot()) {
device.setDate(null);
}
try {
device.setRecoveryMode(RecoveryMode.AVAILABLE);
device.waitForDeviceAvailable(mDeviceBootTime);
} catch (DeviceUnresponsiveException e) {
// assume this is a build problem
throw new DeviceFailedToBootError(
String.format(
"Device %s did not become available after flashing GKI. Exception: %s",
device.getSerialNumber(), e),
device.getDeviceDescriptor(),
DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
device.postBootSetup();
CLog.i("Device update completed on %s", device.getDeviceDescriptor());
}
/**
* Get a reference to the {@link IHostOptions}
*
* @return the {@link IHostOptions} to use
*/
@VisibleForTesting
protected IHostOptions getHostOptions() {
return GlobalConfiguration.getInstance().getHostOptions();
}
/**
* Get the {@link IRunUtil} instance to use.
*
* @return the {@link IRunUtil} to use
*/
@VisibleForTesting
protected IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Flash GKI images.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
private void flashGki(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError, DeviceNotAvailableException {
device.rebootIntoBootloader();
if (mShouldDisableOemVerity) {
executeFastbootCmd(device, "oem disable-verity");
}
long start = System.currentTimeMillis();
getHostOptions().takePermit(PermitLimitType.CONCURRENT_FLASHER);
// Ensure snapuserd isn't running
device.waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING);
CLog.v(
"Flashing permit obtained after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
// Don't allow interruptions during flashing operations.
getRunUtil().allowInterrupt(false);
try {
if (buildInfo.getFile(mVendorBootImageName) != null) {
File vendorBootImg =
getRequestedFile(
device,
mVendorBootImageFileName,
buildInfo.getFile(mVendorBootImageName),
tmpDir);
executeFastbootCmd(device, "flash", "vendor_boot", vendorBootImg.getAbsolutePath());
}
if (buildInfo.getFile(mVendorKernelBootImageName) != null) {
File vendorKernelBootImg =
getRequestedFile(
device,
mVendorKernelBootImageFileName,
buildInfo.getFile(mVendorKernelBootImageName),
tmpDir);
executeFastbootCmd(device, "flash", "vendor_kernel_boot",
vendorKernelBootImg.getAbsolutePath());
}
if (buildInfo.getFile(mDtboImageName) != null) {
File dtboImg =
getRequestedFile(
device,
mDtboImageFileName,
buildInfo.getFile(mDtboImageName),
tmpDir);
executeFastbootCmd(device, "flash", "dtbo", dtboImg.getAbsolutePath());
}
executeFastbootCmd(device, "flash", "boot", mBootImg.getAbsolutePath());
if (buildInfo.getFile(mVendorDlkmImageName) != null) {
File vendorDlkmImg =
getRequestedFile(
device,
mVendorDlkmImageFileName,
buildInfo.getFile(mVendorDlkmImageName),
tmpDir);
if (!TestDeviceState.FASTBOOTD.equals(device.getDeviceState())) {
device.rebootIntoFastbootd();
}
executeFastbootCmd(device, "flash", "vendor_dlkm", vendorDlkmImg.getAbsolutePath());
}
if (buildInfo.getFile(mSystemDlkmImageName) != null) {
File systemDlkmImg =
getRequestedFile(
device,
mSystemDlkmImageFileName,
buildInfo.getFile(mSystemDlkmImageName),
tmpDir);
if (!TestDeviceState.FASTBOOTD.equals(device.getDeviceState())) {
device.rebootIntoFastbootd();
}
executeFastbootCmd(device, "flash", "system_dlkm", systemDlkmImg.getAbsolutePath());
}
if (mShouldWipeDevice) {
executeFastbootCmd(device, "-w");
}
} finally {
getHostOptions().returnPermit(PermitLimitType.CONCURRENT_FLASHER);
// Allow interruption at the end no matter what.
getRunUtil().allowInterrupt(true);
CLog.v(
"Flashing permit returned after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
}
}
/**
* Validate GKI boot image is expected. (Obsoleted. Please call with tmpDir provided)
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError if there is no valid gki boot.img
*/
public void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError {
throw new TargetSetupError(
"Obsoleted. Please use validateGkiBootImg(ITestDevice, IBuildInfo, File)",
device.getDeviceDescriptor());
}
/**
* Validate GKI boot image is expected. Throw exception if there is no valid boot.img.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
@VisibleForTesting
protected void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
if (buildInfo.getFile(mGkiBootImageName) != null && mBootImageFileName != null) {
mBootImg =
getRequestedFile(
device,
mBootImageFileName,
buildInfo.getFile(mGkiBootImageName),
tmpDir);
return;
}
if (buildInfo.getFile(KERNEL_IMAGE) == null) {
throw new TargetSetupError(
KERNEL_IMAGE + " is not provided. Can not generate GKI boot.img.",
device.getDeviceDescriptor());
}
if (buildInfo.getFile(mRamdiskImageName) == null) {
throw new TargetSetupError(
mRamdiskImageName + " is not provided. Can not generate GKI boot.img.",
device.getDeviceDescriptor());
}
if (buildInfo.getFile(OTATOOLS_ZIP) == null) {
throw new TargetSetupError(
OTATOOLS_ZIP + " is not provided. Can not generate GKI boot.img.",
device.getDeviceDescriptor());
}
try {
File mkbootimg =
getRequestedFile(device, MKBOOTIMG, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
mkbootimg.setExecutable(true, false);
mBootImg = FileUtil.createTempFile("boot", ".img", tmpDir);
String cmd =
String.format(
"%s --kernel %s --header_version %d --base 0x00000000 "
+ "--pagesize 4096 --ramdisk %s -o %s",
mkbootimg.getAbsolutePath(),
buildInfo.getFile(KERNEL_IMAGE),
mBootHeaderVersion,
buildInfo.getFile(mRamdiskImageName),
mBootImg.getAbsolutePath());
executeHostCommand(device, cmd);
CLog.i("The GKI boot.img is of size %d", mBootImg.length());
if (mBootImg.length() == 0) {
throw new TargetSetupError(
"The mkbootimg tool didn't generate a valid boot.img.",
device.getDeviceDescriptor());
}
buildInfo.setFile(mGkiBootImageName, mBootImg, "0");
} catch (IOException e) {
throw new TargetSetupError(
"Fail to generate GKI boot.img.", e, device.getDeviceDescriptor());
}
}
/**
* Extracts the system_dlkm tar gzip file into the system_dlkm_staging folder. This function is
* a wrapper around {@link TarUtil.extractTarGzipToTemp} in order to stub out the untarring for
* unit testing.
*
* @param systemDlkmArchive the system_dlkm tar gzip file containing GKI modules.
* @return File containing the system_dlkm tar gzip contents.
* @throws IOException
*/
@VisibleForTesting
protected File extractSystemDlkmTarGzip(File systemDlkmArchive) throws IOException {
return TarUtil.extractTarGzipToTemp(systemDlkmArchive, "system_dlkm_staging");
}
/**
* Flatten the system_dlkm staging directory so that all the kernel modules are directly under
* /lib/modules. This is necessary to match the expected system_dlkm file layout for platform
* builds.
*
* @param device the {@link ITestDevice}
* @param systemDlkmStagingDir the system_dlkm staging directory {@link File}
* @throws IOException or TargetSetupError if there is an error flattening the system_dlkm.
*/
@VisibleForTesting
protected void flattenSystemDlkm(ITestDevice device, File systemDlkmStagingDir)
throws IOException, TargetSetupError {
File systemStagingLibModulesDir = new File(systemDlkmStagingDir, "lib/modules");
// Move all modules from the kernel directory to /lib/modules
Path libModulesPath = systemStagingLibModulesDir.toPath();
File[] libModulesVersionFiles = systemStagingLibModulesDir.listFiles();
File libModulesVersionDir = null;
if (libModulesVersionFiles.length == 1) {
// Move all the files under the kernel version folder to be
// under lib/modules.
libModulesVersionDir = libModulesVersionFiles[0];
for (File file : libModulesVersionDir.listFiles()) {
if (file.isFile()) {
File hardLink = new File(systemStagingLibModulesDir, file.getName());
try {
FileUtil.hardlinkFile(file, hardLink, true);
} catch (IOException e) {
throw new TargetSetupError(
String.format(
"Failed to create hardlink of %s to %s",
file.toString(), hardLink.toString()),
device.getDeviceDescriptor());
}
}
}
}
Path libModulesKernel =
new File(
libModulesVersionDir != null
? libModulesVersionDir
: systemStagingLibModulesDir,
"kernel")
.toPath();
try (Stream<Path> allPaths = Files.walk(libModulesKernel)) {
Path[] modulePaths =
allPaths.filter(path -> path.toString().endsWith(".ko")).toArray(Path[]::new);
for (Path path : modulePaths) {
File hardLink = new File(systemStagingLibModulesDir, path.toFile().getName());
try {
FileUtil.hardlinkFile(path.toFile(), hardLink, true);
} catch (IOException e) {
throw new TargetSetupError(
String.format(
"Failed to create a hardlink of %s to %s",
path.toString(), hardLink.toString()),
device.getDeviceDescriptor());
}
}
} catch (NoSuchFileException e) {
// Not a problem. Just means there's either no modules or the
// tarball is already flat.
CLog.i("Didn't find a kernel directory under lib/modules");
}
if (libModulesVersionDir != null) {
FileUtil.recursiveDelete(libModulesVersionDir);
} else if (libModulesKernel != null) {
FileUtil.recursiveDelete(libModulesKernel.toFile());
}
// Remove modules.*.bin and modules.order. These aren't used or
// included in the platform system_dlkm image.
File[] files =
libModulesPath.toFile().listFiles(new PatternFilenameFilter("modules\\..*\\.bin"));
for (File f : files) {
Files.deleteIfExists(f.toPath());
}
Files.deleteIfExists(libModulesPath.resolve("modules.order"));
File[] depmodFiles =
libModulesPath.toFile().listFiles(new PatternFilenameFilter("modules\\..*"));
// Update the depmod files that reference the kernel modules to use the
// new path.
for (File f : depmodFiles) {
String contents = FileUtil.readStringFromFile(f);
contents =
Pattern.compile("kernel[^: \n\t]*/([^: \n\t]+\\.ko)")
.matcher(contents)
.replaceAll("$1");
FileUtil.writeToFile(contents, f);
}
}
/**
* Build GKI system_dlkm image if the system_dlkm archive is provided.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is an error building the image file.
*/
@VisibleForTesting
protected void buildGkiSystemDlkmImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
File systemDlkmStagingDir = null;
if (buildInfo.getFile(mSystemDlkmArchiveName) == null) {
/* Nothing to do here */
return;
}
File systemDlkmArchive =
getRequestedFile(
device,
mSystemDlkmArchiveName,
buildInfo.getFile(mSystemDlkmArchiveName),
tmpDir);
if (systemDlkmArchive == null) {
throw new TargetSetupError(
mSystemDlkmArchiveName
+ " is not provided. Can not generate GKI system_dlkm.img.",
device.getDeviceDescriptor());
}
if (buildInfo.getFile(OTATOOLS_ZIP) == null) {
throw new TargetSetupError(
OTATOOLS_ZIP + " is not provided. Can not generate GKI system_dlkm.img.",
device.getDeviceDescriptor());
}
File build_image =
getRequestedFile(device, BUILD_IMAGE, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
// Get build_image dependencies
File mkuserimg_mke2fs =
getRequestedFile(device, MKUSERIMG_MKE2FS, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
File mke2fs = getRequestedFile(device, MKE2FS, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
File e2fsdroid =
getRequestedFile(device, E2FSDROID, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
build_image.setExecutable(true, false);
mkuserimg_mke2fs.setExecutable(true, false);
mke2fs.setExecutable(true, false);
e2fsdroid.setExecutable(true, false);
try {
systemDlkmStagingDir = extractSystemDlkmTarGzip(systemDlkmArchive);
flattenSystemDlkm(device, systemDlkmStagingDir);
// Create temporary files for the system_dlkm properties and file contexts
File systemDlkmPropsFile = new File(tmpDir, "system_dlkm.props");
File systemDlkmFileContexts = new File(tmpDir, "system_dlkm_file_contexts");
// These are defaults GKI uses. We might want to pull this file from
// a device build if devices require different properties.
PrintWriter systemDlkmFileContextsWriter =
new PrintWriter(new FileWriter(systemDlkmFileContexts));
systemDlkmFileContextsWriter.println(
"/system_dlkm(/.*)? u:object_r:system_dlkm_file:s0");
systemDlkmFileContextsWriter.close();
PrintWriter systemDlkmPropsPrintWriter =
new PrintWriter(new FileWriter(systemDlkmPropsFile));
systemDlkmPropsPrintWriter.println("fs_type=ext4");
systemDlkmPropsPrintWriter.println("use_dynamic_partition_size=true");
systemDlkmPropsPrintWriter.println("ext_mkuserimg=mkuserimg_mke2fs");
systemDlkmPropsPrintWriter.println("ext4_share_dup_blocks=true");
systemDlkmPropsPrintWriter.println("extfs_rsv_pct=0");
systemDlkmPropsPrintWriter.println("journal_size=0");
systemDlkmPropsPrintWriter.println("mount_point=system_dlkm");
systemDlkmPropsPrintWriter.println(
String.format("selinux_fc=%s", systemDlkmFileContexts.getAbsolutePath()));
systemDlkmPropsPrintWriter.close();
mSystemDlkmImg = new File(tmpDir, "system_dlkm.img");
String buildImageCmd =
String.format(
"%s %s %s %s /dev/null",
build_image.getAbsolutePath(),
systemDlkmStagingDir.getAbsolutePath(),
systemDlkmPropsFile.getAbsolutePath(),
mSystemDlkmImg.getAbsolutePath());
executeHostCommand(device, buildImageCmd);
CLog.i("The GKI system_dlkm.img is of size %d", mSystemDlkmImg.length());
if (mSystemDlkmImg.length() == 0) {
throw new TargetSetupError(
"The build_image tool didn't generate a valid system_dlkm.img. (size=0)",
device.getDeviceDescriptor());
}
buildInfo.setFile(mSystemDlkmImageName, mSystemDlkmImg, "0");
} catch (IOException e) {
throw new TargetSetupError(
"Failed to generate GKI system_dlkm.img.", e, device.getDeviceDescriptor());
} finally {
// Clean up the system dlkm staging dir
FileUtil.recursiveDelete(systemDlkmStagingDir);
}
File avbtool = getRequestedFile(device, AVBTOOL, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
avbtool.setExecutable(true, false);
String cmd =
String.format(
"%s add_hashtree_footer --do_not_generate_fec "
+ "--image %s "
+ "--partition_name system_dlkm",
avbtool.getAbsolutePath(), mSystemDlkmImg.getAbsolutePath());
executeHostCommand(device, cmd);
}
/**
* Validate GKI boot image is expected. Throw exception if there is no valid boot.img.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
@VisibleForTesting
protected void addHashFooter(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError, DeviceNotAvailableException {
if (mBootImg == null) {
throw new TargetSetupError(
mGkiBootImageName + " is not provided. Can not add hash footer to it.",
device.getDeviceDescriptor());
}
if (buildInfo.getFile(OTATOOLS_ZIP) == null) {
throw new TargetSetupError(
OTATOOLS_ZIP + " is not provided. Can not add hash footer to GKI boot.img.",
device.getDeviceDescriptor());
}
File avbtool = getRequestedFile(device, AVBTOOL, buildInfo.getFile(OTATOOLS_ZIP), tmpDir);
avbtool.setExecutable(true, false);
String android_version = device.getProperty("ro.build.version.release");
if (Strings.isNullOrEmpty(android_version)) {
throw new TargetSetupError(
"Can not get android version from property ro.build.version.release.",
device.getDeviceDescriptor());
}
String security_path_version = device.getProperty("ro.build.version.security_patch");
if (Strings.isNullOrEmpty(security_path_version)) {
throw new TargetSetupError(
"Can not get security path version from property"
+ " ro.build.version.security_patch.",
device.getDeviceDescriptor());
}
String command = String.format("du -b %s", mBootImg.getAbsolutePath());
CommandResult cmdResult = executeHostCommand(device, command);
String partition_size = cmdResult.getStdout().split("\\s+")[0];
CLog.i("Boot image partition size: %s", partition_size);
String cmd =
String.format(
"%s add_hash_footer --image %s --partition_size %s "
+ "--partition_name boot "
+ "--prop com.android.build.boot.os_version:%s "
+ "--prop com.android.build.boot.security_patch:%s",
avbtool.getAbsolutePath(),
mBootImg.getAbsolutePath(),
partition_size,
android_version,
security_path_version);
executeHostCommand(device, cmd);
}
/**
* Helper method to execute host command.
*
* @param device the {@link ITestDevice}
* @param command the command string
* @return the CommandResult
* @throws TargetSetupError, DeviceNotAvailableException
*/
private CommandResult executeHostCommand(ITestDevice device, final String command)
throws TargetSetupError {
final CommandResult result = getRunUtil().runTimedCmd(300000L, command.split("\\s+"));
switch (result.getStatus()) {
case SUCCESS:
CLog.i(
"Command %s finished successfully, stdout = [%s].",
command, result.getStdout().trim());
break;
case FAILED:
throw new TargetSetupError(
String.format(
"Command %s failed, stdout = [%s], stderr = [%s].",
command, result.getStdout().trim(), result.getStderr().trim()),
device.getDeviceDescriptor());
case TIMED_OUT:
throw new TargetSetupError(
String.format("Command %s timed out.", command),
device.getDeviceDescriptor());
case EXCEPTION:
throw new TargetSetupError(
String.format("Exception occurred when running command %s.", command),
device.getDeviceDescriptor());
}
return result;
}
/**
* Get the requested file from the source file (zip or folder) by requested file name.
*
* <p>The provided source file can be a zip file. The method will unzip it to tempary directory
* and find the requested file by the provided file name.
*
* <p>The provided source file can be a file folder. The method will find the requestd file by
* the provided file name.
*
* @param device the {@link ITestDevice}
* @param requestedFileName the requeste file name String
* @param sourceFile the source file
* @return the file that is specified by the requested file name
* @throws TargetSetupError
*/
@VisibleForTesting
protected File getRequestedFile(
ITestDevice device, String requestedFileName, File sourceFile, File tmpDir)
throws TargetSetupError {
File requestedFile = null;
String baseFileName = new File(requestedFileName).getName();
String subdirPathName = new File(requestedFileName).getParent();
if (sourceFile.getName().endsWith(".zip")) {
try (ZipFile sourceZipFile = new ZipFile(sourceFile)) {
File destDir =
FileUtil.createNamedTempDir(
tmpDir, FileUtil.getBaseName(sourceFile.getName()) + "_zip");
File subdir = null;
if (subdirPathName != null && !subdirPathName.isEmpty()) {
subdir = FileUtil.createNamedTempDir(destDir, subdirPathName);
}
requestedFile = new File(subdir != null ? subdir : destDir, baseFileName);
ZipUtil2.extractFileFromZip(sourceZipFile, requestedFileName, requestedFile);
if (!requestedFile.exists()) {
/* Let's search for the file within the zip archive in case of a regex
* filename before giving up. */
final Enumeration<ZipArchiveEntry> entries = sourceZipFile.getEntries();
while (entries.hasMoreElements()) {
final ZipArchiveEntry entry = entries.nextElement();
if (entry.isDirectory() || !entry.getName().matches(requestedFileName)) {
continue;
}
requestedFile =
new File(subdir != null ? subdir : destDir, entry.getName());
FileUtil.writeToFile(sourceZipFile.getInputStream(entry), requestedFile);
break;
}
}
} catch (IOException e) {
throw new TargetSetupError(
String.format("Fail to get %s from %s", requestedFileName, sourceFile),
e,
device.getDeviceDescriptor());
}
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
} else {
requestedFile = sourceFile;
}
if (requestedFile == null || !requestedFile.exists()) {
throw new TargetSetupError(
String.format(
"Requested file with file_name %s does not exist in provided %s.",
requestedFileName, sourceFile),
device.getDeviceDescriptor());
}
return requestedFile;
}
/**
* Helper method to execute a fastboot command.
*
* @param device the {@link ITestDevice} to execute command on
* @param cmdArgs the arguments to provide to fastboot
* @return String the stderr output from command if non-empty. Otherwise returns the stdout Some
* fastboot commands are weird in that they dump output to stderr on success case
* @throws DeviceNotAvailableException if device is not available
* @throws TargetSetupError if fastboot command fails
*/
private String executeFastbootCmd(ITestDevice device, String... cmdArgs)
throws DeviceNotAvailableException, TargetSetupError {
CLog.i(
"Execute fastboot command %s on %s",
Arrays.toString(cmdArgs), device.getSerialNumber());
CommandResult result = device.executeLongFastbootCommand(cmdArgs);
CLog.v("fastboot stdout: " + result.getStdout());
CLog.v("fastboot stderr: " + result.getStderr());
CommandStatus cmdStatus = result.getStatus();
// fastboot command line output is in stderr even for successful run
if (result.getStderr().contains("FAILED")) {
// if output contains "FAILED", just override to failure
cmdStatus = CommandStatus.FAILED;
}
if (cmdStatus != CommandStatus.SUCCESS) {
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
Arrays.toString(cmdArgs),
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
device.getDeviceDescriptor(),
DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
if (result.getStderr().length() > 0) {
return result.getStderr();
} else {
return result.getStdout();
}
}
}