| # Copyright 2021 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Utility functions that process goldfish images.""" |
| |
| import os |
| import shutil |
| |
| from acloud import errors |
| from acloud.internal.lib import ota_tools |
| |
| |
| # File names under working directory. |
| _UNPACK_DIR_NAME = "unpacked_boot_img" |
| _MIXED_RAMDISK_IMAGE_NAME = "mixed_ramdisk" |
| # File names in unpacked boot image. |
| _UNPACKED_KERNEL_IMAGE_NAME = "kernel" |
| _UNPACKED_RAMDISK_IMAGE_NAME = "ramdisk" |
| # File names in a build environment or an SDK repository. |
| SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img" |
| _SDK_REPO_SYSTEM_IMAGE_NAME = "system.img" |
| _MISC_INFO_FILE_NAME = "misc_info.txt" |
| _SYSTEM_QEMU_CONFIG_FILE_NAME = "system-qemu-config.txt" |
| # File names in the search order of emulator. |
| _DISK_IMAGE_NAMES = (SYSTEM_QEMU_IMAGE_NAME, _SDK_REPO_SYSTEM_IMAGE_NAME) |
| _KERNEL_IMAGE_NAMES = ("kernel-ranchu", "kernel-ranchu-64", "kernel") |
| _RAMDISK_IMAGE_NAMES = ("ramdisk-qemu.img", "ramdisk.img") |
| |
| |
| def _FindFileByNames(parent_dir, names): |
| """Find file under a directory by names. |
| |
| Args: |
| parent_dir: The directory to find the file in. |
| names: A list of file names. |
| |
| Returns: |
| The path to the first existing file in the list. |
| |
| Raises: |
| errors.GetLocalImageError if none of the files exist. |
| """ |
| for name in names: |
| path = os.path.join(parent_dir, name) |
| if os.path.isfile(path): |
| return path |
| raise errors.GetLocalImageError("No %s in %s." % |
| (", ".join(names), parent_dir)) |
| |
| |
| def _UnpackBootImage(output_dir, boot_image_path, ota): |
| """Unpack a boot image and find kernel images. |
| |
| Args: |
| output_dir: The directory where the boot image is unpacked. |
| boot_image_path: The path to the boot image. |
| ota: An instance of ota_tools.OtaTools. |
| |
| Returns: |
| The kernel image path and the ramdisk image path. |
| |
| Raises: |
| errors.GetLocalImageError if the kernel or the ramdisk is not found. |
| """ |
| ota.UnpackBootImg(output_dir, boot_image_path) |
| |
| kernel_path = os.path.join(output_dir, _UNPACKED_KERNEL_IMAGE_NAME) |
| ramdisk_path = os.path.join(output_dir, _UNPACKED_RAMDISK_IMAGE_NAME) |
| if not os.path.isfile(kernel_path): |
| raise errors.GetLocalImageError("No kernel in %s." % boot_image_path) |
| if not os.path.isfile(ramdisk_path): |
| raise errors.GetLocalImageError("No ramdisk in %s." % boot_image_path) |
| return kernel_path, ramdisk_path |
| |
| |
| def _MixRamdiskImages(output_path, original_ramdisk_path, |
| boot_ramdisk_path): |
| """Mix an emulator ramdisk with a boot ramdisk. |
| |
| An emulator ramdisk consists of a boot ramdisk and a vendor ramdisk. |
| This method overlays a new boot ramdisk on the emulator ramdisk by |
| concatenating them. |
| |
| Args: |
| output_path: The path to the output ramdisk. |
| original_ramdisk_path: The path to the emulator ramdisk. |
| boot_ramdisk_path: The path to the boot ramdisk. |
| """ |
| with open(output_path, "wb") as mixed_ramdisk: |
| with open(original_ramdisk_path, "rb") as ramdisk: |
| shutil.copyfileobj(ramdisk, mixed_ramdisk) |
| with open(boot_ramdisk_path, "rb") as ramdisk: |
| shutil.copyfileobj(ramdisk, mixed_ramdisk) |
| |
| |
| def MixWithBootImage(output_dir, image_dir, boot_image_path, ota): |
| """Mix emulator kernel images with a boot image. |
| |
| Args: |
| output_dir: The directory containing the output and intermediate files. |
| image_dir: The directory containing emulator kernel and ramdisk images. |
| boot_image_path: The path to the boot image. |
| ota: An instance of ota_tools.OtaTools. |
| |
| Returns: |
| The paths to the kernel and ramdisk images in output_dir. |
| |
| Raises: |
| errors.GetLocalImageError if any image is not found. |
| """ |
| unpack_dir = os.path.join(output_dir, _UNPACK_DIR_NAME) |
| if os.path.exists(unpack_dir): |
| shutil.rmtree(unpack_dir) |
| os.makedirs(unpack_dir, exist_ok=True) |
| |
| kernel_path, boot_ramdisk_path = _UnpackBootImage( |
| unpack_dir, boot_image_path, ota) |
| # The ramdisk unpacked from boot_image_path does not include emulator's |
| # kernel modules. The ramdisk in image_dir contains the modules. This |
| # method mixes the two ramdisks. |
| mixed_ramdisk_path = os.path.join(output_dir, _MIXED_RAMDISK_IMAGE_NAME) |
| original_ramdisk_path = _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES) |
| _MixRamdiskImages(mixed_ramdisk_path, original_ramdisk_path, |
| boot_ramdisk_path) |
| return kernel_path, mixed_ramdisk_path |
| |
| |
| def FindKernelImages(image_dir): |
| """Find emulator kernel images in a directory. |
| |
| Args: |
| image_dir: The directory to find the images in. |
| |
| Returns: |
| The paths to the kernel image and the ramdisk image. |
| |
| Raises: |
| errors.GetLocalImageError if any image is not found. |
| """ |
| return (_FindFileByNames(image_dir, _KERNEL_IMAGE_NAMES), |
| _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES)) |
| |
| |
| def FindDiskImage(image_dir): |
| """Find an emulator disk image in a directory. |
| |
| Args: |
| image_dir: The directory to find the image in. |
| |
| Returns: |
| The path to the disk image. |
| |
| Raises: |
| errors.GetLocalImageError if the image is not found. |
| """ |
| return _FindFileByNames(image_dir, _DISK_IMAGE_NAMES) |
| |
| |
| def MixWithSystemImage(output_dir, image_dir, system_image_path, ota): |
| """Mix emulator images and a system image into a disk image. |
| |
| Args: |
| output_dir: The path to the output directory. |
| image_dir: The input directory that provides images except |
| system.img. |
| system_image_path: The path to the system image. |
| ota: An instance of ota_tools.OtaTools. |
| |
| Returns: |
| The path to the mixed disk image in output_dir. |
| |
| Raises: |
| errors.GetLocalImageError if any required file is not found. |
| """ |
| os.makedirs(output_dir, exist_ok=True) |
| |
| # Create the super image. |
| mixed_super_image_path = os.path.join(output_dir, "mixed_super.img") |
| ota.BuildSuperImage( |
| mixed_super_image_path, |
| _FindFileByNames(image_dir, [_MISC_INFO_FILE_NAME]), |
| lambda partition: ota_tools.GetImageForPartition( |
| partition, image_dir, system=system_image_path)) |
| |
| # Create the vbmeta image. |
| vbmeta_image_path = os.path.join(output_dir, "disabled_vbmeta.img") |
| ota.MakeDisabledVbmetaImage(vbmeta_image_path) |
| |
| # Create the disk image. |
| disk_image = os.path.join(output_dir, "mixed_disk.img") |
| ota.MkCombinedImg( |
| disk_image, |
| _FindFileByNames(image_dir, [_SYSTEM_QEMU_CONFIG_FILE_NAME]), |
| lambda partition: ota_tools.GetImageForPartition( |
| partition, image_dir, super=mixed_super_image_path, |
| vbmeta=vbmeta_image_path)) |
| return disk_image |