xref: /OK3568_Linux_fs/kernel/scripts/mkbootimg (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#!/usr/bin/env python3
2#
3# Copyright 2015, The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of 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,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Creates the boot image."""
18
19from argparse import (ArgumentParser, ArgumentTypeError,
20                      FileType, RawDescriptionHelpFormatter)
21from hashlib import sha1
22from os import fstat
23from struct import pack
24
25import array
26import collections
27import os
28import re
29import tempfile
30
31# from gki.generate_gki_certificate import generate_gki_certificate
32def generate_gki_certificate(image, avbtool, name, algorithm, key, salt,
33                             additional_avb_args, output):
34    """Shell out to avbtool to generate a GKI certificate."""
35
36    # Need to specify a value of --partition_size for avbtool to work.
37    # We use 64 MB below, but avbtool will not resize the boot image to
38    # this size because --do_not_append_vbmeta_image is also specified.
39    avbtool_cmd = [
40        avbtool, 'add_hash_footer',
41        '--partition_name', name,
42        '--partition_size', str(64 * 1024 * 1024),
43        '--image', image,
44        '--algorithm', algorithm,
45        '--key', key,
46        '--do_not_append_vbmeta_image',
47        '--output_vbmeta_image', output,
48    ]
49
50    if salt is not None:
51        avbtool_cmd += ['--salt', salt]
52
53    avbtool_cmd += additional_avb_args
54
55    subprocess.check_call(avbtool_cmd)
56
57
58# Constant and structure definition is in
59# system/tools/mkbootimg/include/bootimg/bootimg.h
60BOOT_MAGIC = 'ANDROID!'
61BOOT_MAGIC_SIZE = 8
62BOOT_NAME_SIZE = 16
63BOOT_ARGS_SIZE = 512
64BOOT_EXTRA_ARGS_SIZE = 1024
65BOOT_IMAGE_HEADER_V1_SIZE = 1648
66BOOT_IMAGE_HEADER_V2_SIZE = 1660
67BOOT_IMAGE_HEADER_V3_SIZE = 1580
68BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
69BOOT_IMAGE_HEADER_V4_SIZE = 1584
70BOOT_IMAGE_V4_SIGNATURE_SIZE = 4096
71
72VENDOR_BOOT_MAGIC = 'VNDRBOOT'
73VENDOR_BOOT_MAGIC_SIZE = 8
74VENDOR_BOOT_NAME_SIZE = BOOT_NAME_SIZE
75VENDOR_BOOT_ARGS_SIZE = 2048
76VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112
77VENDOR_BOOT_IMAGE_HEADER_V4_SIZE = 2128
78
79VENDOR_RAMDISK_TYPE_NONE = 0
80VENDOR_RAMDISK_TYPE_PLATFORM = 1
81VENDOR_RAMDISK_TYPE_RECOVERY = 2
82VENDOR_RAMDISK_TYPE_DLKM = 3
83VENDOR_RAMDISK_NAME_SIZE = 32
84VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16
85VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE = 108
86
87# Names with special meaning, mustn't be specified in --ramdisk_name.
88VENDOR_RAMDISK_NAME_BLOCKLIST = {b'default'}
89
90PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT = '--vendor_ramdisk_fragment'
91
92
93def filesize(f):
94    if f is None:
95        return 0
96    try:
97        return fstat(f.fileno()).st_size
98    except OSError:
99        return 0
100
101
102def update_sha(sha, f):
103    if f:
104        sha.update(f.read())
105        f.seek(0)
106        sha.update(pack('I', filesize(f)))
107    else:
108        sha.update(pack('I', 0))
109
110
111def pad_file(f, padding):
112    pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)
113    f.write(pack(str(pad) + 'x'))
114
115
116def get_number_of_pages(image_size, page_size):
117    """calculates the number of pages required for the image"""
118    return (image_size + page_size - 1) // page_size
119
120
121def get_recovery_dtbo_offset(args):
122    """calculates the offset of recovery_dtbo image in the boot image"""
123    num_header_pages = 1 # header occupies a page
124    num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize)
125    num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk),
126                                            args.pagesize)
127    num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize)
128    dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages +
129                                   num_ramdisk_pages + num_second_pages)
130    return dtbo_offset
131
132
133def should_add_legacy_gki_boot_signature(args):
134    if args.gki_signing_key and args.gki_signing_algorithm:
135        return True
136    return False
137
138
139def write_header_v3_and_above(args):
140    if args.header_version > 3:
141        boot_header_size = BOOT_IMAGE_HEADER_V4_SIZE
142    else:
143        boot_header_size = BOOT_IMAGE_HEADER_V3_SIZE
144
145    args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode()))
146    # kernel size in bytes
147    args.output.write(pack('I', filesize(args.kernel)))
148    # ramdisk size in bytes
149    args.output.write(pack('I', filesize(args.ramdisk)))
150    # os version and patch level
151    args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level))
152    args.output.write(pack('I', boot_header_size))
153    # reserved
154    args.output.write(pack('4I', 0, 0, 0, 0))
155    # version of boot image header
156    args.output.write(pack('I', args.header_version))
157    args.output.write(pack(f'{BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE}s',
158                           args.cmdline))
159    if args.header_version >= 4:
160        # The signature used to verify boot image v4.
161        boot_signature_size = 0
162        if should_add_legacy_gki_boot_signature(args):
163            boot_signature_size = BOOT_IMAGE_V4_SIGNATURE_SIZE
164        args.output.write(pack('I', boot_signature_size))
165    pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE)
166
167
168def write_vendor_boot_header(args):
169    if args.header_version > 3:
170        vendor_ramdisk_size = args.vendor_ramdisk_total_size
171        vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V4_SIZE
172    else:
173        vendor_ramdisk_size = filesize(args.vendor_ramdisk)
174        vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V3_SIZE
175
176    args.vendor_boot.write(pack(f'{VENDOR_BOOT_MAGIC_SIZE}s',
177                                VENDOR_BOOT_MAGIC.encode()))
178    # version of boot image header
179    args.vendor_boot.write(pack('I', args.header_version))
180    # flash page size
181    args.vendor_boot.write(pack('I', args.pagesize))
182    # kernel physical load address
183    args.vendor_boot.write(pack('I', args.base + args.kernel_offset))
184    # ramdisk physical load address
185    args.vendor_boot.write(pack('I', args.base + args.ramdisk_offset))
186    # ramdisk size in bytes
187    args.vendor_boot.write(pack('I', vendor_ramdisk_size))
188    args.vendor_boot.write(pack(f'{VENDOR_BOOT_ARGS_SIZE}s',
189                                args.vendor_cmdline))
190    # kernel tags physical load address
191    args.vendor_boot.write(pack('I', args.base + args.tags_offset))
192    # asciiz product name
193    args.vendor_boot.write(pack(f'{VENDOR_BOOT_NAME_SIZE}s', args.board))
194
195    # header size in bytes
196    args.vendor_boot.write(pack('I', vendor_boot_header_size))
197
198    # dtb size in bytes
199    args.vendor_boot.write(pack('I', filesize(args.dtb)))
200    # dtb physical load address
201    args.vendor_boot.write(pack('Q', args.base + args.dtb_offset))
202
203    if args.header_version > 3:
204        vendor_ramdisk_table_size = (args.vendor_ramdisk_table_entry_num *
205                                     VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE)
206        # vendor ramdisk table size in bytes
207        args.vendor_boot.write(pack('I', vendor_ramdisk_table_size))
208        # number of vendor ramdisk table entries
209        args.vendor_boot.write(pack('I', args.vendor_ramdisk_table_entry_num))
210        # vendor ramdisk table entry size in bytes
211        args.vendor_boot.write(pack('I', VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE))
212        # bootconfig section size in bytes
213        args.vendor_boot.write(pack('I', filesize(args.vendor_bootconfig)))
214    pad_file(args.vendor_boot, args.pagesize)
215
216
217def write_header(args):
218    if args.header_version > 4:
219        raise ValueError(
220            f'Boot header version {args.header_version} not supported')
221    if args.header_version in {3, 4}:
222        return write_header_v3_and_above(args)
223
224    ramdisk_load_address = ((args.base + args.ramdisk_offset)
225                            if filesize(args.ramdisk) > 0 else 0)
226    second_load_address = ((args.base + args.second_offset)
227                           if filesize(args.second) > 0 else 0)
228
229    args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode()))
230    # kernel size in bytes
231    args.output.write(pack('I', filesize(args.kernel)))
232    # kernel physical load address
233    args.output.write(pack('I', args.base + args.kernel_offset))
234    # ramdisk size in bytes
235    args.output.write(pack('I', filesize(args.ramdisk)))
236    # ramdisk physical load address
237    args.output.write(pack('I', ramdisk_load_address))
238    # second bootloader size in bytes
239    args.output.write(pack('I', filesize(args.second)))
240    # second bootloader physical load address
241    args.output.write(pack('I', second_load_address))
242    # kernel tags physical load address
243    args.output.write(pack('I', args.base + args.tags_offset))
244    # flash page size
245    args.output.write(pack('I', args.pagesize))
246    # version of boot image header
247    args.output.write(pack('I', args.header_version))
248    # os version and patch level
249    args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level))
250    # asciiz product name
251    args.output.write(pack(f'{BOOT_NAME_SIZE}s', args.board))
252    args.output.write(pack(f'{BOOT_ARGS_SIZE}s', args.cmdline))
253
254    sha = sha1()
255    update_sha(sha, args.kernel)
256    update_sha(sha, args.ramdisk)
257    update_sha(sha, args.second)
258
259    if args.header_version > 0:
260        update_sha(sha, args.recovery_dtbo)
261    if args.header_version > 1:
262        update_sha(sha, args.dtb)
263
264    img_id = pack('32s', sha.digest())
265
266    args.output.write(img_id)
267    args.output.write(pack(f'{BOOT_EXTRA_ARGS_SIZE}s', args.extra_cmdline))
268
269    if args.header_version > 0:
270        if args.recovery_dtbo:
271            # recovery dtbo size in bytes
272            args.output.write(pack('I', filesize(args.recovery_dtbo)))
273            # recovert dtbo offset in the boot image
274            args.output.write(pack('Q', get_recovery_dtbo_offset(args)))
275        else:
276            # Set to zero if no recovery dtbo
277            args.output.write(pack('I', 0))
278            args.output.write(pack('Q', 0))
279
280    # Populate boot image header size for header versions 1 and 2.
281    if args.header_version == 1:
282        args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE))
283    elif args.header_version == 2:
284        args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE))
285
286    if args.header_version > 1:
287        # if filesize(args.dtb) == 0:
288        #     raise ValueError('DTB image must not be empty.')
289
290        # dtb size in bytes
291        args.output.write(pack('I', filesize(args.dtb)))
292        # dtb physical load address
293        args.output.write(pack('Q', args.base + args.dtb_offset))
294
295    pad_file(args.output, args.pagesize)
296    return img_id
297
298
299class AsciizBytes:
300    """Parses a string and encodes it as an asciiz bytes object.
301
302    >>> AsciizBytes(bufsize=4)('foo')
303    b'foo\\x00'
304    >>> AsciizBytes(bufsize=4)('foob')
305    Traceback (most recent call last):
306        ...
307    argparse.ArgumentTypeError: Encoded asciiz length exceeded: max 4, got 5
308    """
309
310    def __init__(self, bufsize):
311        self.bufsize = bufsize
312
313    def __call__(self, arg):
314        arg_bytes = arg.encode() + b'\x00'
315        if len(arg_bytes) > self.bufsize:
316            raise ArgumentTypeError(
317                'Encoded asciiz length exceeded: '
318                f'max {self.bufsize}, got {len(arg_bytes)}')
319        return arg_bytes
320
321
322class VendorRamdiskTableBuilder:
323    """Vendor ramdisk table builder.
324
325    Attributes:
326        entries: A list of VendorRamdiskTableEntry namedtuple.
327        ramdisk_total_size: Total size in bytes of all ramdisks in the table.
328    """
329
330    VendorRamdiskTableEntry = collections.namedtuple(  # pylint: disable=invalid-name
331        'VendorRamdiskTableEntry',
332        ['ramdisk_path', 'ramdisk_size', 'ramdisk_offset', 'ramdisk_type',
333         'ramdisk_name', 'board_id'])
334
335    def __init__(self):
336        self.entries = []
337        self.ramdisk_total_size = 0
338        self.ramdisk_names = set()
339
340    def add_entry(self, ramdisk_path, ramdisk_type, ramdisk_name, board_id):
341        # Strip any trailing null for simple comparison.
342        stripped_ramdisk_name = ramdisk_name.rstrip(b'\x00')
343        if stripped_ramdisk_name in VENDOR_RAMDISK_NAME_BLOCKLIST:
344            raise ValueError(
345                f'Banned vendor ramdisk name: {stripped_ramdisk_name}')
346        if stripped_ramdisk_name in self.ramdisk_names:
347            raise ValueError(
348                f'Duplicated vendor ramdisk name: {stripped_ramdisk_name}')
349        self.ramdisk_names.add(stripped_ramdisk_name)
350
351        if board_id is None:
352            board_id = array.array(
353                'I', [0] * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)
354        else:
355            board_id = array.array('I', board_id)
356        if len(board_id) != VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE:
357            raise ValueError('board_id size must be '
358                             f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}')
359
360        with open(ramdisk_path, 'rb') as f:
361            ramdisk_size = filesize(f)
362        self.entries.append(self.VendorRamdiskTableEntry(
363            ramdisk_path, ramdisk_size, self.ramdisk_total_size, ramdisk_type,
364            ramdisk_name, board_id))
365        self.ramdisk_total_size += ramdisk_size
366
367    def write_ramdisks_padded(self, fout, alignment):
368        for entry in self.entries:
369            with open(entry.ramdisk_path, 'rb') as f:
370                fout.write(f.read())
371        pad_file(fout, alignment)
372
373    def write_entries_padded(self, fout, alignment):
374        for entry in self.entries:
375            fout.write(pack('I', entry.ramdisk_size))
376            fout.write(pack('I', entry.ramdisk_offset))
377            fout.write(pack('I', entry.ramdisk_type))
378            fout.write(pack(f'{VENDOR_RAMDISK_NAME_SIZE}s',
379                            entry.ramdisk_name))
380            fout.write(entry.board_id)
381        pad_file(fout, alignment)
382
383
384def write_padded_file(f_out, f_in, padding):
385    if f_in is None:
386        return
387    f_out.write(f_in.read())
388    pad_file(f_out, padding)
389
390
391def parse_int(x):
392    return int(x, 0)
393
394
395def parse_os_version(x):
396    match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
397    if match:
398        a = int(match.group(1))
399        b = c = 0
400        if match.lastindex >= 2:
401            b = int(match.group(2))
402        if match.lastindex == 3:
403            c = int(match.group(3))
404        # 7 bits allocated for each field
405        assert a < 128
406        assert b < 128
407        assert c < 128
408        return (a << 14) | (b << 7) | c
409    return 0
410
411
412def parse_os_patch_level(x):
413    match = re.search(r'^(\d{4})-(\d{2})(?:-(\d{2}))?', x)
414    if match:
415        y = int(match.group(1)) - 2000
416        m = int(match.group(2))
417        # 7 bits allocated for the year, 4 bits for the month
418        assert 0 <= y < 128
419        assert 0 < m <= 12
420        return (y << 4) | m
421    return 0
422
423
424def parse_vendor_ramdisk_type(x):
425    type_dict = {
426        'none': VENDOR_RAMDISK_TYPE_NONE,
427        'platform': VENDOR_RAMDISK_TYPE_PLATFORM,
428        'recovery': VENDOR_RAMDISK_TYPE_RECOVERY,
429        'dlkm': VENDOR_RAMDISK_TYPE_DLKM,
430    }
431    if x.lower() in type_dict:
432        return type_dict[x.lower()]
433    return parse_int(x)
434
435
436def get_vendor_boot_v4_usage():
437    return """vendor boot version 4 arguments:
438  --ramdisk_type {none,platform,recovery,dlkm}
439                        specify the type of the ramdisk
440  --ramdisk_name NAME
441                        specify the name of the ramdisk
442  --board_id{0..15} NUMBER
443                        specify the value of the board_id vector, defaults to 0
444  --vendor_ramdisk_fragment VENDOR_RAMDISK_FILE
445                        path to the vendor ramdisk file
446
447  These options can be specified multiple times, where each vendor ramdisk
448  option group ends with a --vendor_ramdisk_fragment option.
449  Each option group appends an additional ramdisk to the vendor boot image.
450"""
451
452
453def parse_vendor_ramdisk_args(args, args_list):
454    """Parses vendor ramdisk specific arguments.
455
456    Args:
457        args: An argparse.Namespace object. Parsed results are stored into this
458            object.
459        args_list: A list of argument strings to be parsed.
460
461    Returns:
462        A list argument strings that are not parsed by this method.
463    """
464    parser = ArgumentParser(add_help=False)
465    parser.add_argument('--ramdisk_type', type=parse_vendor_ramdisk_type,
466                        default=VENDOR_RAMDISK_TYPE_NONE)
467    parser.add_argument('--ramdisk_name',
468                        type=AsciizBytes(bufsize=VENDOR_RAMDISK_NAME_SIZE),
469                        required=True)
470    for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE):
471        parser.add_argument(f'--board_id{i}', type=parse_int, default=0)
472    parser.add_argument(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT, required=True)
473
474    unknown_args = []
475
476    vendor_ramdisk_table_builder = VendorRamdiskTableBuilder()
477    if args.vendor_ramdisk is not None:
478        vendor_ramdisk_table_builder.add_entry(
479            args.vendor_ramdisk.name, VENDOR_RAMDISK_TYPE_PLATFORM, b'', None)
480
481    while PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT in args_list:
482        idx = args_list.index(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT) + 2
483        vendor_ramdisk_args = args_list[:idx]
484        args_list = args_list[idx:]
485
486        ramdisk_args, extra_args = parser.parse_known_args(vendor_ramdisk_args)
487        ramdisk_args_dict = vars(ramdisk_args)
488        unknown_args.extend(extra_args)
489
490        ramdisk_path = ramdisk_args.vendor_ramdisk_fragment
491        ramdisk_type = ramdisk_args.ramdisk_type
492        ramdisk_name = ramdisk_args.ramdisk_name
493        board_id = [ramdisk_args_dict[f'board_id{i}']
494                    for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)]
495        vendor_ramdisk_table_builder.add_entry(ramdisk_path, ramdisk_type,
496                                               ramdisk_name, board_id)
497
498    if len(args_list) > 0:
499        unknown_args.extend(args_list)
500
501    args.vendor_ramdisk_total_size = (vendor_ramdisk_table_builder
502                                      .ramdisk_total_size)
503    args.vendor_ramdisk_table_entry_num = len(vendor_ramdisk_table_builder
504                                              .entries)
505    args.vendor_ramdisk_table_builder = vendor_ramdisk_table_builder
506    return unknown_args
507
508
509def parse_cmdline():
510    version_parser = ArgumentParser(add_help=False)
511    version_parser.add_argument('--header_version', type=parse_int, default=0)
512    if version_parser.parse_known_args()[0].header_version < 3:
513        # For boot header v0 to v2, the kernel commandline field is split into
514        # two fields, cmdline and extra_cmdline. Both fields are asciiz strings,
515        # so we minus one here to ensure the encoded string plus the
516        # null-terminator can fit in the buffer size.
517        cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE - 1
518    else:
519        cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE
520
521    parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
522                            epilog=get_vendor_boot_v4_usage())
523    parser.add_argument('--kernel', type=FileType('rb'),
524                        help='path to the kernel')
525    parser.add_argument('--ramdisk', type=FileType('rb'),
526                        help='path to the ramdisk')
527    parser.add_argument('--second', type=FileType('rb'),
528                        help='path to the second bootloader')
529    parser.add_argument('--dtb', type=FileType('rb'), help='path to the dtb')
530    dtbo_group = parser.add_mutually_exclusive_group()
531    dtbo_group.add_argument('--recovery_dtbo', type=FileType('rb'),
532                            help='path to the recovery DTBO')
533    dtbo_group.add_argument('--recovery_acpio', type=FileType('rb'),
534                            metavar='RECOVERY_ACPIO', dest='recovery_dtbo',
535                            help='path to the recovery ACPIO')
536    parser.add_argument('--cmdline', type=AsciizBytes(bufsize=cmdline_size),
537                        default='', help='kernel command line arguments')
538    parser.add_argument('--vendor_cmdline',
539                        type=AsciizBytes(bufsize=VENDOR_BOOT_ARGS_SIZE),
540                        default='',
541                        help='vendor boot kernel command line arguments')
542    parser.add_argument('--base', type=parse_int, default=0x10000000,
543                        help='base address')
544    parser.add_argument('--kernel_offset', type=parse_int, default=0x00008000,
545                        help='kernel offset')
546    parser.add_argument('--ramdisk_offset', type=parse_int, default=0x01000000,
547                        help='ramdisk offset')
548    parser.add_argument('--second_offset', type=parse_int, default=0x00f00000,
549                        help='second bootloader offset')
550    parser.add_argument('--dtb_offset', type=parse_int, default=0x01f00000,
551                        help='dtb offset')
552
553    parser.add_argument('--os_version', type=parse_os_version, default=0,
554                        help='operating system version')
555    parser.add_argument('--os_patch_level', type=parse_os_patch_level,
556                        default=0, help='operating system patch level')
557    parser.add_argument('--tags_offset', type=parse_int, default=0x00000100,
558                        help='tags offset')
559    parser.add_argument('--board', type=AsciizBytes(bufsize=BOOT_NAME_SIZE),
560                        default='', help='board name')
561    parser.add_argument('--pagesize', type=parse_int,
562                        choices=[2**i for i in range(11, 15)], default=2048,
563                        help='page size')
564    parser.add_argument('--id', action='store_true',
565                        help='print the image ID on standard output')
566    parser.add_argument('--header_version', type=parse_int, default=0,
567                        help='boot image header version')
568    parser.add_argument('-o', '--output', type=FileType('wb'),
569                        help='output file name')
570    parser.add_argument('--vendor_boot', type=FileType('wb'),
571                        help='vendor boot output file name')
572    parser.add_argument('--vendor_ramdisk', type=FileType('rb'),
573                        help='path to the vendor ramdisk')
574    parser.add_argument('--vendor_bootconfig', type=FileType('rb'),
575                        help='path to the vendor bootconfig file')
576
577    gki_2_0_signing_args = parser.add_argument_group(
578        '[DEPRECATED] GKI 2.0 signing arguments')
579    gki_2_0_signing_args.add_argument(
580        '--gki_signing_algorithm', help='GKI signing algorithm to use')
581    gki_2_0_signing_args.add_argument(
582        '--gki_signing_key', help='path to RSA private key file')
583    gki_2_0_signing_args.add_argument(
584        '--gki_signing_signature_args', default='',
585        help='other hash arguments passed to avbtool')
586    gki_2_0_signing_args.add_argument(
587        '--gki_signing_avbtool_path', default='avbtool',
588        help='path to avbtool for boot signature generation')
589
590    args, extra_args = parser.parse_known_args()
591    if args.vendor_boot is not None and args.header_version > 3:
592        extra_args = parse_vendor_ramdisk_args(args, extra_args)
593    if len(extra_args) > 0:
594        raise ValueError(f'Unrecognized arguments: {extra_args}')
595
596    if args.header_version < 3:
597        args.extra_cmdline = args.cmdline[BOOT_ARGS_SIZE-1:]
598        args.cmdline = args.cmdline[:BOOT_ARGS_SIZE-1] + b'\x00'
599        assert len(args.cmdline) <= BOOT_ARGS_SIZE
600        assert len(args.extra_cmdline) <= BOOT_EXTRA_ARGS_SIZE
601
602    return args
603
604
605def add_boot_image_signature(args, pagesize):
606    """Adds the boot image signature.
607
608    Note that the signature will only be verified in VTS to ensure a
609    generic boot.img is used. It will not be used by the device
610    bootloader at boot time. The bootloader should only verify
611    the boot vbmeta at the end of the boot partition (or in the top-level
612    vbmeta partition) via the Android Verified Boot process, when the
613    device boots.
614    """
615    # Flush the buffer for signature calculation.
616    args.output.flush()
617
618    # Outputs the signed vbmeta to a separate file, then append to boot.img
619    # as the boot signature.
620    with tempfile.TemporaryDirectory() as temp_out_dir:
621        boot_signature_output = os.path.join(temp_out_dir, 'boot_signature')
622        generate_gki_certificate(
623            image=args.output.name, avbtool=args.gki_signing_avbtool_path,
624            name='boot', algorithm=args.gki_signing_algorithm,
625            key=args.gki_signing_key, salt='d00df00d',
626            additional_avb_args=args.gki_signing_signature_args.split(),
627            output=boot_signature_output,
628        )
629        with open(boot_signature_output, 'rb') as boot_signature:
630            boot_signature_bytes = boot_signature.read()
631            if len(boot_signature_bytes) > BOOT_IMAGE_V4_SIGNATURE_SIZE:
632                raise ValueError(
633                    f'boot sigature size is > {BOOT_IMAGE_V4_SIGNATURE_SIZE}')
634            boot_signature_bytes += b'\x00' * (
635                BOOT_IMAGE_V4_SIGNATURE_SIZE - len(boot_signature_bytes))
636            assert len(boot_signature_bytes) == BOOT_IMAGE_V4_SIGNATURE_SIZE
637            args.output.write(boot_signature_bytes)
638            pad_file(args.output, pagesize)
639
640
641def write_data(args, pagesize):
642    write_padded_file(args.output, args.kernel, pagesize)
643    write_padded_file(args.output, args.ramdisk, pagesize)
644    write_padded_file(args.output, args.second, pagesize)
645
646    if args.header_version > 0 and args.header_version < 3:
647        write_padded_file(args.output, args.recovery_dtbo, pagesize)
648    if args.header_version == 2:
649        write_padded_file(args.output, args.dtb, pagesize)
650    if args.header_version >= 4 and should_add_legacy_gki_boot_signature(args):
651        add_boot_image_signature(args, pagesize)
652
653
654def write_vendor_boot_data(args):
655    if args.header_version > 3:
656        builder = args.vendor_ramdisk_table_builder
657        builder.write_ramdisks_padded(args.vendor_boot, args.pagesize)
658        write_padded_file(args.vendor_boot, args.dtb, args.pagesize)
659        builder.write_entries_padded(args.vendor_boot, args.pagesize)
660        write_padded_file(args.vendor_boot, args.vendor_bootconfig,
661            args.pagesize)
662    else:
663        write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize)
664        write_padded_file(args.vendor_boot, args.dtb, args.pagesize)
665
666
667def main():
668    args = parse_cmdline()
669    if args.vendor_boot is not None:
670        if args.header_version not in {3, 4}:
671            raise ValueError(
672                '--vendor_boot not compatible with given header version')
673        if args.header_version == 3 and args.vendor_ramdisk is None:
674            raise ValueError('--vendor_ramdisk missing or invalid')
675        write_vendor_boot_header(args)
676        write_vendor_boot_data(args)
677    if args.output is not None:
678        if args.second is not None and args.header_version > 2:
679            raise ValueError(
680                '--second not compatible with given header version')
681        img_id = write_header(args)
682        if args.header_version > 2:
683            write_data(args, BOOT_IMAGE_HEADER_V3_PAGESIZE)
684        else:
685            write_data(args, args.pagesize)
686        if args.id and img_id is not None:
687            print('0x' + ''.join(f'{octet:02x}' for octet in img_id))
688
689
690if __name__ == '__main__':
691    main()
692