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