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