• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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