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