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# hostname can be a domain name. "-" in hostname must be replaced with "_". 49_REMOTE_HOST_INSTANCE_NAME_FORMAT = ( 50 "host-goldfish-%(hostname)s-%(console_port)s-%(build_info)s") 51_REMOTE_HOST_INSTANCE_NAME_PATTERN = re.compile( 52 r"host-goldfish-(?P<hostname>[\w.]+)-(?P<console_port>\d+)-.+") 53 54 55def _FindFileByNames(parent_dir, names): 56 """Find file under a directory by names. 57 58 Args: 59 parent_dir: The directory to find the file in. 60 names: A list of file names. 61 62 Returns: 63 The path to the first existing file in the list. 64 65 Raises: 66 errors.GetLocalImageError if none of the files exist. 67 """ 68 for name in names: 69 path = os.path.join(parent_dir, name) 70 if os.path.isfile(path): 71 return path 72 raise errors.GetLocalImageError("No %s in %s." % 73 (", ".join(names), parent_dir)) 74 75 76def _UnpackBootImage(output_dir, boot_image_path, ota): 77 """Unpack a boot image and find kernel images. 78 79 Args: 80 output_dir: The directory where the boot image is unpacked. 81 boot_image_path: The path to the boot image. 82 ota: An instance of ota_tools.OtaTools. 83 84 Returns: 85 The kernel image path and the ramdisk image path. 86 87 Raises: 88 errors.GetLocalImageError if the kernel or the ramdisk is not found. 89 """ 90 ota.UnpackBootImg(output_dir, boot_image_path) 91 92 kernel_path = os.path.join(output_dir, _UNPACKED_KERNEL_IMAGE_NAME) 93 ramdisk_path = os.path.join(output_dir, _UNPACKED_RAMDISK_IMAGE_NAME) 94 if not os.path.isfile(kernel_path): 95 raise errors.GetLocalImageError("No kernel in %s." % boot_image_path) 96 if not os.path.isfile(ramdisk_path): 97 raise errors.GetLocalImageError("No ramdisk in %s." % boot_image_path) 98 return kernel_path, ramdisk_path 99 100 101def _MixRamdiskImages(output_path, original_ramdisk_path, 102 boot_ramdisk_path): 103 """Mix an emulator ramdisk with a boot ramdisk. 104 105 An emulator ramdisk consists of a boot ramdisk and a vendor ramdisk. 106 This method overlays a new boot ramdisk on the emulator ramdisk by 107 concatenating them. 108 109 Args: 110 output_path: The path to the output ramdisk. 111 original_ramdisk_path: The path to the emulator ramdisk. 112 boot_ramdisk_path: The path to the boot ramdisk. 113 """ 114 with open(output_path, "wb") as mixed_ramdisk: 115 with open(original_ramdisk_path, "rb") as ramdisk: 116 shutil.copyfileobj(ramdisk, mixed_ramdisk) 117 with open(boot_ramdisk_path, "rb") as ramdisk: 118 shutil.copyfileobj(ramdisk, mixed_ramdisk) 119 120 121def MixWithBootImage(output_dir, image_dir, boot_image_path, ota): 122 """Mix emulator kernel images with a boot image. 123 124 Args: 125 output_dir: The directory containing the output and intermediate files. 126 image_dir: The directory containing emulator kernel and ramdisk images. 127 boot_image_path: The path to the boot image. 128 ota: An instance of ota_tools.OtaTools. 129 130 Returns: 131 The paths to the kernel and ramdisk images in output_dir. 132 133 Raises: 134 errors.GetLocalImageError if any image is not found. 135 """ 136 unpack_dir = os.path.join(output_dir, _UNPACK_DIR_NAME) 137 if os.path.exists(unpack_dir): 138 shutil.rmtree(unpack_dir) 139 os.makedirs(unpack_dir, exist_ok=True) 140 141 kernel_path, boot_ramdisk_path = _UnpackBootImage( 142 unpack_dir, boot_image_path, ota) 143 # The ramdisk unpacked from boot_image_path does not include emulator's 144 # kernel modules. The ramdisk in image_dir contains the modules. This 145 # method mixes the two ramdisks. 146 mixed_ramdisk_path = os.path.join(output_dir, _MIXED_RAMDISK_IMAGE_NAME) 147 original_ramdisk_path = _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES) 148 _MixRamdiskImages(mixed_ramdisk_path, original_ramdisk_path, 149 boot_ramdisk_path) 150 return kernel_path, mixed_ramdisk_path 151 152 153def FindKernelImages(image_dir): 154 """Find emulator kernel images in a directory. 155 156 Args: 157 image_dir: The directory to find the images in. 158 159 Returns: 160 The paths to the kernel image and the ramdisk image. 161 162 Raises: 163 errors.GetLocalImageError if any image is not found. 164 """ 165 return (_FindFileByNames(image_dir, _KERNEL_IMAGE_NAMES), 166 _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES)) 167 168 169def FindSystemDlkmImage(search_path): 170 """Find system_dlkm image in a path. 171 172 Args: 173 search_path: A path to an image file or an image directory. 174 175 Returns: 176 The system_dlkm image path. 177 178 Raises: 179 errors.GetLocalImageError if search_path does not contain a 180 system_dlkm image. 181 """ 182 return (search_path if os.path.isfile(search_path) else 183 _FindFileByNames(search_path, _SYSTEM_DLKM_IMAGE_NAMES)) 184 185 186def FindDiskImage(image_dir): 187 """Find an emulator disk image in a directory. 188 189 Args: 190 image_dir: The directory to find the image in. 191 192 Returns: 193 The path to the disk image. 194 195 Raises: 196 errors.GetLocalImageError if the image is not found. 197 """ 198 return _FindFileByNames(image_dir, _DISK_IMAGE_NAMES) 199 200 201def MixDiskImage(output_dir, image_dir, system_image_path, 202 system_dlkm_image_path, ota): 203 """Mix emulator images into a disk image. 204 205 Args: 206 output_dir: The path to the output directory. 207 image_dir: The input directory that provides images except 208 system.img. 209 system_image_path: A string or None, the system image path. 210 system_dlkm_image_path: A string or None, the system_dlkm image path. 211 ota: An instance of ota_tools.OtaTools. 212 213 Returns: 214 The path to the mixed disk image in output_dir. 215 216 Raises: 217 errors.GetLocalImageError if any required file is not found. 218 """ 219 os.makedirs(output_dir, exist_ok=True) 220 221 # Create the super image. 222 mixed_super_image_path = os.path.join(output_dir, "mixed_super.img") 223 ota.BuildSuperImage( 224 mixed_super_image_path, 225 _FindFileByNames(image_dir, [_MISC_INFO_FILE_NAME]), 226 lambda partition: ota_tools.GetImageForPartition( 227 partition, image_dir, 228 system=system_image_path, 229 system_dlkm=system_dlkm_image_path)) 230 231 # Create the vbmeta image. 232 vbmeta_image_path = os.path.join(output_dir, "disabled_vbmeta.img") 233 ota.MakeDisabledVbmetaImage(vbmeta_image_path) 234 235 # Create the disk image. 236 disk_image = os.path.join(output_dir, "mixed_disk.img") 237 ota.MkCombinedImg( 238 disk_image, 239 _FindFileByNames(image_dir, [_SYSTEM_QEMU_CONFIG_FILE_NAME]), 240 lambda partition: ota_tools.GetImageForPartition( 241 partition, image_dir, super=mixed_super_image_path, 242 vbmeta=vbmeta_image_path)) 243 return disk_image 244 245 246def FormatRemoteHostInstanceName(hostname, console_port, build_info): 247 """Convert address and build info to a remote host instance name. 248 249 Args: 250 hostname: A string, the IPv4 address or domain name of the host. 251 console_port: An integer, the emulator console port. 252 build_info: A dict containing the build ID and target. 253 254 Returns: 255 A string, the instance name. 256 """ 257 build_id = build_info.get(constants.BUILD_ID) 258 build_target = build_info.get(constants.BUILD_TARGET) 259 build_info_str = (f"{build_id}-{build_target}" if 260 build_id and build_target else 261 "userbuild") 262 return _REMOTE_HOST_INSTANCE_NAME_FORMAT % { 263 "hostname": hostname.replace("-", "_"), 264 "console_port": console_port, 265 "build_info": build_info_str, 266 } 267 268 269def ParseRemoteHostConsoleAddress(instance_name): 270 """Parse emulator console address from a remote host instance name. 271 272 Args: 273 instance_name: A string, the instance name. 274 275 Returns: 276 The hostname as a string and the console port as an integer. 277 None if the name does not represent a goldfish instance on remote host. 278 """ 279 match = _REMOTE_HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name) 280 return ((match.group("hostname").replace("_", "-"), 281 int(match.group("console_port"))) 282 if match else None) 283 284 285def ConvertAvdSpecToArgs(avd_spec): 286 """Convert hardware specification to emulator arguments. 287 288 Args: 289 avd_spec: The AvdSpec object. 290 291 Returns: 292 A list of strings, the arguments. 293 """ 294 args = [] 295 if avd_spec.gpu: 296 args.extend(("-gpu", avd_spec.gpu)) 297 298 if not avd_spec.hw_customize: 299 return args 300 301 cores = avd_spec.hw_property.get(constants.HW_ALIAS_CPUS) 302 if cores: 303 args.extend(("-cores", cores)) 304 x_res = avd_spec.hw_property.get(constants.HW_X_RES) 305 y_res = avd_spec.hw_property.get(constants.HW_Y_RES) 306 if x_res and y_res: 307 args.extend(("-skin", ("%sx%s" % (x_res, y_res)))) 308 dpi = avd_spec.hw_property.get(constants.HW_ALIAS_DPI) 309 if dpi: 310 args.extend(("-dpi-device", dpi)) 311 memory_size_mb = avd_spec.hw_property.get(constants.HW_ALIAS_MEMORY) 312 if memory_size_mb: 313 args.extend(("-memory", memory_size_mb)) 314 userdata_size_mb = avd_spec.hw_property.get(constants.HW_ALIAS_DISK) 315 if userdata_size_mb: 316 args.extend(("-partition-size", userdata_size_mb)) 317 318 return args 319