1# Copyright 2021 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Utility functions that process goldfish images and arguments.""" 16 17import os 18import re 19import shutil 20 21from acloud import errors 22from acloud.internal import constants 23from acloud.internal.lib import ota_tools 24 25 26# File names under working directory. 27_UNPACK_DIR_NAME = "unpacked_boot_img" 28_MIXED_RAMDISK_IMAGE_NAME = "mixed_ramdisk" 29# File names in unpacked boot image. 30_UNPACKED_KERNEL_IMAGE_NAME = "kernel" 31_UNPACKED_RAMDISK_IMAGE_NAME = "ramdisk" 32# File names in a build environment or an SDK repository. 33SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img" 34VERIFIED_BOOT_PARAMS_FILE_NAME = "VerifiedBootParams.textproto" 35_SDK_REPO_SYSTEM_IMAGE_NAME = "system.img" 36_MISC_INFO_FILE_NAME = "misc_info.txt" 37_SYSTEM_QEMU_CONFIG_FILE_NAME = "system-qemu-config.txt" 38# File names in the search order of emulator. 39_DISK_IMAGE_NAMES = (SYSTEM_QEMU_IMAGE_NAME, _SDK_REPO_SYSTEM_IMAGE_NAME) 40_KERNEL_IMAGE_NAMES = ("kernel-ranchu", "kernel-ranchu-64", "kernel") 41_RAMDISK_IMAGE_NAMES = ("ramdisk-qemu.img", "ramdisk.img") 42_SYSTEM_DLKM_IMAGE_NAMES = ( 43 "system_dlkm.flatten.erofs.img", # GKI artifact 44 "system_dlkm.flatten.ext4.img", # GKI artifact 45 "system_dlkm.img", # goldfish artifact 46) 47# Remote host instance name. 48_REMOTE_HOST_INSTANCE_NAME_FORMAT = ( 49 "host-goldfish-%(ip_addr)s-%(console_port)s-%(build_info)s") 50_REMOTE_HOST_INSTANCE_NAME_PATTERN = re.compile( 51 r"host-goldfish-(?P<ip_addr>[\d.]+)-(?P<console_port>\d+)-.+") 52 53 54def _FindFileByNames(parent_dir, names): 55 """Find file under a directory by names. 56 57 Args: 58 parent_dir: The directory to find the file in. 59 names: A list of file names. 60 61 Returns: 62 The path to the first existing file in the list. 63 64 Raises: 65 errors.GetLocalImageError if none of the files exist. 66 """ 67 for name in names: 68 path = os.path.join(parent_dir, name) 69 if os.path.isfile(path): 70 return path 71 raise errors.GetLocalImageError("No %s in %s." % 72 (", ".join(names), parent_dir)) 73 74 75def _UnpackBootImage(output_dir, boot_image_path, ota): 76 """Unpack a boot image and find kernel images. 77 78 Args: 79 output_dir: The directory where the boot image is unpacked. 80 boot_image_path: The path to the boot image. 81 ota: An instance of ota_tools.OtaTools. 82 83 Returns: 84 The kernel image path and the ramdisk image path. 85 86 Raises: 87 errors.GetLocalImageError if the kernel or the ramdisk is not found. 88 """ 89 ota.UnpackBootImg(output_dir, boot_image_path) 90 91 kernel_path = os.path.join(output_dir, _UNPACKED_KERNEL_IMAGE_NAME) 92 ramdisk_path = os.path.join(output_dir, _UNPACKED_RAMDISK_IMAGE_NAME) 93 if not os.path.isfile(kernel_path): 94 raise errors.GetLocalImageError("No kernel in %s." % boot_image_path) 95 if not os.path.isfile(ramdisk_path): 96 raise errors.GetLocalImageError("No ramdisk in %s." % boot_image_path) 97 return kernel_path, ramdisk_path 98 99 100def _MixRamdiskImages(output_path, original_ramdisk_path, 101 boot_ramdisk_path): 102 """Mix an emulator ramdisk with a boot ramdisk. 103 104 An emulator ramdisk consists of a boot ramdisk and a vendor ramdisk. 105 This method overlays a new boot ramdisk on the emulator ramdisk by 106 concatenating them. 107 108 Args: 109 output_path: The path to the output ramdisk. 110 original_ramdisk_path: The path to the emulator ramdisk. 111 boot_ramdisk_path: The path to the boot ramdisk. 112 """ 113 with open(output_path, "wb") as mixed_ramdisk: 114 with open(original_ramdisk_path, "rb") as ramdisk: 115 shutil.copyfileobj(ramdisk, mixed_ramdisk) 116 with open(boot_ramdisk_path, "rb") as ramdisk: 117 shutil.copyfileobj(ramdisk, mixed_ramdisk) 118 119 120def MixWithBootImage(output_dir, image_dir, boot_image_path, ota): 121 """Mix emulator kernel images with a boot image. 122 123 Args: 124 output_dir: The directory containing the output and intermediate files. 125 image_dir: The directory containing emulator kernel and ramdisk images. 126 boot_image_path: The path to the boot image. 127 ota: An instance of ota_tools.OtaTools. 128 129 Returns: 130 The paths to the kernel and ramdisk images in output_dir. 131 132 Raises: 133 errors.GetLocalImageError if any image is not found. 134 """ 135 unpack_dir = os.path.join(output_dir, _UNPACK_DIR_NAME) 136 if os.path.exists(unpack_dir): 137 shutil.rmtree(unpack_dir) 138 os.makedirs(unpack_dir, exist_ok=True) 139 140 kernel_path, boot_ramdisk_path = _UnpackBootImage( 141 unpack_dir, boot_image_path, ota) 142 # The ramdisk unpacked from boot_image_path does not include emulator's 143 # kernel modules. The ramdisk in image_dir contains the modules. This 144 # method mixes the two ramdisks. 145 mixed_ramdisk_path = os.path.join(output_dir, _MIXED_RAMDISK_IMAGE_NAME) 146 original_ramdisk_path = _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES) 147 _MixRamdiskImages(mixed_ramdisk_path, original_ramdisk_path, 148 boot_ramdisk_path) 149 return kernel_path, mixed_ramdisk_path 150 151 152def FindKernelImages(image_dir): 153 """Find emulator kernel images in a directory. 154 155 Args: 156 image_dir: The directory to find the images in. 157 158 Returns: 159 The paths to the kernel image and the ramdisk image. 160 161 Raises: 162 errors.GetLocalImageError if any image is not found. 163 """ 164 return (_FindFileByNames(image_dir, _KERNEL_IMAGE_NAMES), 165 _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES)) 166 167 168def FindSystemDlkmImage(search_path): 169 """Find system_dlkm image in a path. 170 171 Args: 172 search_path: A path to an image file or an image directory. 173 174 Returns: 175 The system_dlkm image path. 176 177 Raises: 178 errors.GetLocalImageError if search_path does not contain a 179 system_dlkm image. 180 """ 181 return (search_path if os.path.isfile(search_path) else 182 _FindFileByNames(search_path, _SYSTEM_DLKM_IMAGE_NAMES)) 183 184 185def FindDiskImage(image_dir): 186 """Find an emulator disk image in a directory. 187 188 Args: 189 image_dir: The directory to find the image in. 190 191 Returns: 192 The path to the disk image. 193 194 Raises: 195 errors.GetLocalImageError if the image is not found. 196 """ 197 return _FindFileByNames(image_dir, _DISK_IMAGE_NAMES) 198 199 200def MixDiskImage(output_dir, image_dir, system_image_path, 201 system_dlkm_image_path, ota): 202 """Mix emulator images into a disk image. 203 204 Args: 205 output_dir: The path to the output directory. 206 image_dir: The input directory that provides images except 207 system.img. 208 system_image_path: A string or None, the system image path. 209 system_dlkm_image_path: A string or None, the system_dlkm image path. 210 ota: An instance of ota_tools.OtaTools. 211 212 Returns: 213 The path to the mixed disk image in output_dir. 214 215 Raises: 216 errors.GetLocalImageError if any required file is not found. 217 """ 218 os.makedirs(output_dir, exist_ok=True) 219 220 # Create the super image. 221 mixed_super_image_path = os.path.join(output_dir, "mixed_super.img") 222 ota.BuildSuperImage( 223 mixed_super_image_path, 224 _FindFileByNames(image_dir, [_MISC_INFO_FILE_NAME]), 225 lambda partition: ota_tools.GetImageForPartition( 226 partition, image_dir, 227 system=system_image_path, 228 system_dlkm=system_dlkm_image_path)) 229 230 # Create the vbmeta image. 231 vbmeta_image_path = os.path.join(output_dir, "disabled_vbmeta.img") 232 ota.MakeDisabledVbmetaImage(vbmeta_image_path) 233 234 # Create the disk image. 235 disk_image = os.path.join(output_dir, "mixed_disk.img") 236 ota.MkCombinedImg( 237 disk_image, 238 _FindFileByNames(image_dir, [_SYSTEM_QEMU_CONFIG_FILE_NAME]), 239 lambda partition: ota_tools.GetImageForPartition( 240 partition, image_dir, super=mixed_super_image_path, 241 vbmeta=vbmeta_image_path)) 242 return disk_image 243 244 245def FormatRemoteHostInstanceName(ip_addr, console_port, build_info): 246 """Convert address and build info to a remote host instance name. 247 248 Args: 249 ip_addr: A string, the IP address of the host. 250 console_port: An integer, the emulator console port. 251 build_info: A dict containing the build ID and target. 252 253 Returns: 254 A string, the instance name. 255 """ 256 build_id = build_info.get(constants.BUILD_ID) 257 build_target = build_info.get(constants.BUILD_TARGET) 258 build_info_str = (f"{build_id}-{build_target}" if 259 build_id and build_target else 260 "userbuild") 261 return _REMOTE_HOST_INSTANCE_NAME_FORMAT % { 262 "ip_addr": ip_addr, 263 "console_port": console_port, 264 "build_info": build_info_str, 265 } 266 267 268def ParseRemoteHostConsoleAddress(instance_name): 269 """Parse emulator console address from a remote host instance name. 270 271 Args: 272 instance_name: A string, the instance name. 273 274 Returns: 275 The IP address as a string and the console port as an integer. 276 None if the name does not represent a goldfish instance on remote host. 277 """ 278 match = _REMOTE_HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name) 279 return ((match.group("ip_addr"), int(match.group("console_port"))) 280 if match else None) 281 282 283def ConvertAvdSpecToArgs(avd_spec): 284 """Convert hardware specification to emulator arguments. 285 286 Args: 287 avd_spec: The AvdSpec object. 288 289 Returns: 290 A list of strings, the arguments. 291 """ 292 args = [] 293 if avd_spec.gpu: 294 args.extend(("-gpu", avd_spec.gpu)) 295 296 if not avd_spec.hw_customize: 297 return args 298 299 cores = avd_spec.hw_property.get(constants.HW_ALIAS_CPUS) 300 if cores: 301 args.extend(("-cores", cores)) 302 x_res = avd_spec.hw_property.get(constants.HW_X_RES) 303 y_res = avd_spec.hw_property.get(constants.HW_Y_RES) 304 if x_res and y_res: 305 args.extend(("-skin", ("%sx%s" % (x_res, y_res)))) 306 dpi = avd_spec.hw_property.get(constants.HW_ALIAS_DPI) 307 if dpi: 308 args.extend(("-dpi-device", dpi)) 309 memory_size_mb = avd_spec.hw_property.get(constants.HW_ALIAS_MEMORY) 310 if memory_size_mb: 311 args.extend(("-memory", memory_size_mb)) 312 userdata_size_mb = avd_spec.hw_property.get(constants.HW_ALIAS_DISK) 313 if userdata_size_mb: 314 args.extend(("-partition-size", userdata_size_mb)) 315 316 return args 317