xref: /aosp_15_r20/tools/acloud/internal/lib/goldfish_utils.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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