• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2015, The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import print_function
17from sys import argv, exit, stderr
18from argparse import ArgumentParser, FileType, Action
19from os import fstat
20from struct import pack
21from hashlib import sha1
22import sys
23import re
24
25def filesize(f):
26    if f is None:
27        return 0
28    try:
29        return fstat(f.fileno()).st_size
30    except OSError:
31        return 0
32
33
34def update_sha(sha, f):
35    if f:
36        sha.update(f.read())
37        f.seek(0)
38        sha.update(pack('I', filesize(f)))
39    else:
40        sha.update(pack('I', 0))
41
42
43def pad_file(f, padding):
44    pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)
45    f.write(pack(str(pad) + 'x'))
46
47
48def get_number_of_pages(image_size, page_size):
49    """calculates the number of pages required for the image"""
50    return (image_size + page_size - 1) / page_size
51
52
53def get_recovery_dtbo_offset(args):
54    """calculates the offset of recovery_dtbo image in the boot image"""
55    num_header_pages = 1 # header occupies a page
56    num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize)
57    num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize)
58    num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize)
59    dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages +
60                                   num_ramdisk_pages + num_second_pages)
61    return dtbo_offset
62
63
64def write_header(args):
65    BOOT_MAGIC = 'ANDROID!'.encode()
66    args.output.write(pack('8s', BOOT_MAGIC))
67    args.output.write(pack('10I',
68        filesize(args.kernel),                          # size in bytes
69        args.base + args.kernel_offset,                 # physical load addr
70        filesize(args.ramdisk),                         # size in bytes
71        args.base + args.ramdisk_offset,                # physical load addr
72        filesize(args.second),                          # size in bytes
73        args.base + args.second_offset,                 # physical load addr
74        args.base + args.tags_offset,                   # physical addr for kernel tags
75        args.pagesize,                                  # flash page size we assume
76        args.header_version,                            # version of bootimage header
77        (args.os_version << 11) | args.os_patch_level)) # os version and patch level
78    args.output.write(pack('16s', args.board.encode())) # asciiz product name
79    args.output.write(pack('512s', args.cmdline[:512].encode()))
80
81    sha = sha1()
82    update_sha(sha, args.kernel)
83    update_sha(sha, args.ramdisk)
84    update_sha(sha, args.second)
85
86    if args.header_version > 0:
87        update_sha(sha, args.recovery_dtbo)
88
89    img_id = pack('32s', sha.digest())
90
91    args.output.write(img_id)
92    args.output.write(pack('1024s', args.cmdline[512:].encode()))
93
94    if args.header_version > 0:
95        args.output.write(pack('I', filesize(args.recovery_dtbo)))   # size in bytes
96        if args.recovery_dtbo:
97            args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset
98        else:
99            args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
100        args.output.write(pack('I', args.output.tell() + 4))         # size of boot header
101
102    pad_file(args.output, args.pagesize)
103    return img_id
104
105
106class ValidateStrLenAction(Action):
107    def __init__(self, option_strings, dest, nargs=None, **kwargs):
108        if 'maxlen' not in kwargs:
109            raise ValueError('maxlen must be set')
110        self.maxlen = int(kwargs['maxlen'])
111        del kwargs['maxlen']
112        super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
113
114    def __call__(self, parser, namespace, values, option_string=None):
115        if len(values) > self.maxlen:
116            raise ValueError('String argument too long: max {0:d}, got {1:d}'.
117                format(self.maxlen, len(values)))
118        setattr(namespace, self.dest, values)
119
120
121def write_padded_file(f_out, f_in, padding):
122    if f_in is None:
123        return
124    f_out.write(f_in.read())
125    pad_file(f_out, padding)
126
127
128def parse_int(x):
129    return int(x, 0)
130
131def parse_os_version(x):
132    match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
133    if match:
134        a = int(match.group(1))
135        b = c = 0
136        if match.lastindex >= 2:
137            b = int(match.group(2))
138        if match.lastindex == 3:
139            c = int(match.group(3))
140        # 7 bits allocated for each field
141        assert a < 128
142        assert b < 128
143        assert c < 128
144        return (a << 14) | (b << 7) | c
145    return 0
146
147def parse_os_patch_level(x):
148    match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x)
149    if match:
150        y = int(match.group(1)) - 2000
151        m = int(match.group(2))
152        # 7 bits allocated for the year, 4 bits for the month
153        assert y >= 0 and y < 128
154        assert m > 0 and m <= 12
155        return (y << 4) | m
156    return 0
157
158def parse_cmdline():
159    parser = ArgumentParser()
160    parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),
161                        required=True)
162    parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
163    parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
164    parser.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb'))
165    parser.add_argument('--cmdline', help='extra arguments to be passed on the '
166                        'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
167    parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
168    parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
169    parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
170    parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
171                        default=0x00f00000)
172    parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
173                        default=0)
174    parser.add_argument('--os_patch_level', help='operating system patch level',
175                        type=parse_os_patch_level, default=0)
176    parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
177    parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
178                        maxlen=16)
179    parser.add_argument('--pagesize', help='page size', type=parse_int,
180                        choices=[2**i for i in range(11,15)], default=2048)
181    parser.add_argument('--id', help='print the image ID on standard output',
182                        action='store_true')
183    parser.add_argument('--header_version', help='boot image header version', type=parse_int, default=0)
184    parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
185                        required=True)
186    return parser.parse_args()
187
188
189def write_data(args):
190    write_padded_file(args.output, args.kernel, args.pagesize)
191    write_padded_file(args.output, args.ramdisk, args.pagesize)
192    write_padded_file(args.output, args.second, args.pagesize)
193
194    if args.header_version > 0:
195        write_padded_file(args.output, args.recovery_dtbo, args.pagesize)
196
197def main():
198    args = parse_cmdline()
199    img_id = write_header(args)
200    write_data(args)
201    if args.id:
202        if isinstance(img_id, str):
203            # Python 2's struct.pack returns a string, but py3 returns bytes.
204            img_id = [ord(x) for x in img_id]
205        print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
206
207if __name__ == '__main__':
208    main()
209