• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright (C) 2024 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16
17import argparse
18import os
19import shlex
20import subprocess
21import tempfile
22
23from build_chd_utils import copy_files, unzip_otatools
24
25"""Builds a vendor_boot-chd_debug.img.
26
27The vendor_boot-chd_debug.img is built by adding those CHD specific debugging
28files to a Cuttlefish's vendor_boot-debug.img, using a new ramdisk fragment.
29
30Test command:
31python3 tools/treble/cuttlefish/build_chd_debug_ramdisk.py \
32    $ANDROID_PRODUCT_OUT/vendor_boot-debug.img \
33    -o $ANDROID_PRODUCT_OUT/vendor_boot-chd_debug.img \
34    --otatools_zip $ANDROID_PRODUCT_OUT/otatools.zip \
35    --add_file chd_debug.prop:adb_debug.prop
36"""
37
38# The value of ramdisk type needs to be synchronized with
39# `system/tools/mkbootimg/mkbootimg.py`. We choose `_PLATFORM` here because the
40# CHD debug ramdisk will be used in normal boot (not for _RECOVERY or _DLKM).
41_VENDOR_RAMDISK_TYPE_PLATFORM = '1'
42
43
44def _parse_args():
45  """Parse the arguments for building the chd debug ramdisk.
46
47  Returns:
48    An object of argparse.Namespace.
49  """
50  parser = argparse.ArgumentParser()
51  parser.add_argument('input_img',
52                      help='The input Cuttlefish vendor boot debug image.')
53  parser.add_argument('--output_img', '-o', required=True,
54                      help='The output CHD vendor boot debug image.')
55  parser.add_argument('--otatools_zip', required=True,
56                      help='Path to the otatools.zip.')
57  parser.add_argument('--add_file', action='append', default=[],
58                      help='The file to be added to the CHD debug ramdisk. '
59                           'The format is <src path>:<dst path>.')
60  return parser.parse_args()
61
62
63class BootImage:
64  """A class that supports adding a new ramdisk fragment into a boot.img."""
65
66  def __init__(self, bootimg, bootimg_dir, unpack_bootimg_bin, mkbootfs_bin,
67               mkbootimg_bin, lz4_bin):
68    self._bootimg = bootimg
69    self._bootimg_dir = bootimg_dir
70    self._unpack_bootimg_bin = unpack_bootimg_bin
71    self._mkbootfs_bin = mkbootfs_bin
72    self._mkbootimg_bin = mkbootimg_bin
73    self._lz4_bin = lz4_bin
74    self._bootimg_args = []
75
76  def unpack(self):
77    """Unpacks the boot.img and capture the bootimg arguments."""
78    if self._bootimg_args:
79      raise RuntimeError(f'cannot unpack {self._bootimg} twice')
80    print(f'Unpacking {self._bootimg} to {self._bootimg_dir}')
81    unpack_cmd = [
82        self._unpack_bootimg_bin,
83        '--boot_img', self._bootimg,
84        '--out', self._bootimg_dir,
85        '--format', 'mkbootimg'
86    ]
87    unpack_result = subprocess.run(unpack_cmd, check=True,
88                                   capture_output=True, encoding='utf-8')
89    self._bootimg_args = shlex.split(unpack_result.stdout)
90
91  def add_ramdisk(self, ramdisk_root):
92    """Adds a new ramdisk fragment and update the bootimg arguments."""
93    # Name the new ramdisk using the smallest unused index.
94    ramdisk_files = [file for file in os.listdir(self._bootimg_dir)
95                     if file.startswith('vendor_ramdisk')]
96    new_ramdisk_name = f'vendor_ramdisk{len(ramdisk_files):02d}'
97    new_ramdisk_file = os.path.join(self._bootimg_dir, new_ramdisk_name)
98    if os.path.exists(new_ramdisk_file):
99      raise FileExistsError(f'{new_ramdisk_file} already exists')
100
101    print(f'Adding a new vendor ramdisk fragment {new_ramdisk_file}')
102    mkbootfs_cmd = [self._mkbootfs_bin, ramdisk_root]
103    mkbootfs_result = subprocess.run(mkbootfs_cmd, check=True,
104                                     capture_output=True)
105
106    compress_cmd = [self._lz4_bin, '-l', '-12', '--favor-decSpeed']
107    with open(new_ramdisk_file, 'w') as o:
108      subprocess.run(compress_cmd, check=True,
109                     input=mkbootfs_result.stdout, stdout=o)
110
111    # Update the bootimg arguments to include the new ramdisk file.
112    self._bootimg_args.extend([
113        '--ramdisk_type', _VENDOR_RAMDISK_TYPE_PLATFORM,
114        '--ramdisk_name', 'chd',
115        '--vendor_ramdisk_fragment', new_ramdisk_file
116    ])
117
118  def pack(self, output_img):
119    """Packs the boot.img."""
120    print(f'Packing {output_img} with args: {self._bootimg_args}')
121    mkbootimg_cmd = [
122        self._mkbootimg_bin, '--vendor_boot', output_img
123    ] + self._bootimg_args
124    subprocess.check_call(mkbootimg_cmd)
125
126
127def _prepare_env(otatools_dir):
128  """Get the executable path of the required otatools.
129
130  We need `unpack_bootimg`, `mkbootfs`, `mkbootimg` and `lz4` for building CHD
131  debug ramdisk. This function returns the path to the above tools in order.
132
133  Args:
134    otatools_dir: The path to the otatools directory.
135
136  Raises:
137    FileNotFoundError if any required otatool does not exist.
138  """
139  tools_path = []
140  for tool_name in ['unpack_bootimg', 'mkbootfs', 'mkbootimg', 'lz4']:
141    tool_path = os.path.join(otatools_dir, 'bin', tool_name)
142    if not os.path.exists(tool_path):
143      raise FileNotFoundError(f'otatool {tool_path} does not exist')
144    tools_path.append(tool_path)
145  return tools_path
146
147
148def add_debug_ramdisk_files(input_image, files_to_add, otatools_dir, temp_dir,
149                            output_image):
150  """Add files to a vendor boot debug image.
151
152  This function creates a new ramdisk fragment, add this fragment into the
153  input vendor boot debug image, and generate an output image.
154
155  Args:
156    input_image: The path to the input vendor boot debug image.
157    files_to_add: A list of files to be added in the debug ramdisk, where a
158                  pair defines the src and dst path of each file.
159    otatools_dir: The path to the otatools directory.
160    temp_dir: The path to the temporary directory for ramdisk filesystem.
161    output_img: The path to the output vendor boot debug image.
162
163  Raises:
164    FileExistsError if having duplicated ramdisk fragments.
165    FileNotFoundError if any required otatool does not exist.
166  """
167  print(f'Adding {files_to_add} to {input_image}')
168  ramdisk_root = os.path.join(temp_dir, 'ramdisk_root')
169  os.mkdir(ramdisk_root)
170  copy_files(files_to_add, ramdisk_root)
171
172  bootimg_dir = os.path.join(temp_dir, 'bootimg')
173  unpack_bootimg, mkbootfs, mkbootimg, lz4 = _prepare_env(otatools_dir)
174  bootimg = BootImage(input_image, bootimg_dir, unpack_bootimg, mkbootfs,
175                      mkbootimg, lz4)
176  bootimg.unpack()
177  bootimg.add_ramdisk(ramdisk_root)
178  bootimg.pack(output_image)
179
180
181def main(temp_dir):
182  args = _parse_args()
183  otatools_dir = os.path.join(temp_dir, 'otatools')
184  unzip_otatools(args.otatools_zip, otatools_dir, [
185      'bin/unpack_bootimg', 'bin/mkbootfs', 'bin/mkbootimg', 'bin/lz4',
186      'lib64/*'
187  ])
188  add_debug_ramdisk_files(args.input_img, args.add_file, otatools_dir,
189                          temp_dir, args.output_img)
190
191
192if __name__ == '__main__':
193  with tempfile.TemporaryDirectory() as temp_dir:
194    main(temp_dir)
195