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