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