• 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"
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