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 write_header(args):
49    BOOT_MAGIC = 'ANDROID!'.encode()
50    args.output.write(pack('8s', BOOT_MAGIC))
51    args.output.write(pack('10I',
52        filesize(args.kernel),                          # size in bytes
53        args.base + args.kernel_offset,                 # physical load addr
54        filesize(args.ramdisk),                         # size in bytes
55        args.base + args.ramdisk_offset,                # physical load addr
56        filesize(args.second),                          # size in bytes
57        args.base + args.second_offset,                 # physical load addr
58        args.base + args.tags_offset,                   # physical addr for kernel tags
59        args.pagesize,                                  # flash page size we assume
60        0,                                              # future expansion: MUST be 0
61        (args.os_version << 11) | args.os_patch_level)) # os version and patch level
62    args.output.write(pack('16s', args.board.encode())) # asciiz product name
63    args.output.write(pack('512s', args.cmdline[:512].encode()))
64
65    sha = sha1()
66    update_sha(sha, args.kernel)
67    update_sha(sha, args.ramdisk)
68    update_sha(sha, args.second)
69    img_id = pack('32s', sha.digest())
70
71    args.output.write(img_id)
72    args.output.write(pack('1024s', args.cmdline[512:].encode()))
73    pad_file(args.output, args.pagesize)
74    return img_id
75
76
77class ValidateStrLenAction(Action):
78    def __init__(self, option_strings, dest, nargs=None, **kwargs):
79        if 'maxlen' not in kwargs:
80            raise ValueError('maxlen must be set')
81        self.maxlen = int(kwargs['maxlen'])
82        del kwargs['maxlen']
83        super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
84
85    def __call__(self, parser, namespace, values, option_string=None):
86        if len(values) > self.maxlen:
87            raise ValueError('String argument too long: max {0:d}, got {1:d}'.
88                format(self.maxlen, len(values)))
89        setattr(namespace, self.dest, values)
90
91
92def write_padded_file(f_out, f_in, padding):
93    if f_in is None:
94        return
95    f_out.write(f_in.read())
96    pad_file(f_out, padding)
97
98
99def parse_int(x):
100    return int(x, 0)
101
102def parse_os_version(x):
103    match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x)
104    if match:
105        a = int(match.group(1))
106        b = c = 0
107        if match.lastindex >= 2:
108            b = int(match.group(2))
109        if match.lastindex == 3:
110            c = int(match.group(3))
111        # 7 bits allocated for each field
112        assert a < 128
113        assert b < 128
114        assert c < 128
115        return (a << 14) | (b << 7) | c
116    return 0
117
118def parse_os_patch_level(x):
119    match = re.search(r'^(\d{4})-(\d{2})-(\d{2})', x)
120    if match:
121        y = int(match.group(1)) - 2000
122        m = int(match.group(2))
123        # 7 bits allocated for the year, 4 bits for the month
124        assert y >= 0 and y < 128
125        assert m > 0 and m <= 12
126        return (y << 4) | m
127    return 0
128
129def parse_cmdline():
130    parser = ArgumentParser()
131    parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),
132                        required=True)
133    parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
134    parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
135    parser.add_argument('--cmdline', help='extra arguments to be passed on the '
136                        'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
137    parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
138    parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
139    parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
140    parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
141                        default=0x00f00000)
142    parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
143                        default=0)
144    parser.add_argument('--os_patch_level', help='operating system patch level',
145                        type=parse_os_patch_level, default=0)
146    parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
147    parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
148                        maxlen=16)
149    parser.add_argument('--pagesize', help='page size', type=parse_int,
150                        choices=[2**i for i in range(11,15)], default=2048)
151    parser.add_argument('--id', help='print the image ID on standard output',
152                        action='store_true')
153    parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
154                        required=True)
155    return parser.parse_args()
156
157
158def write_data(args):
159    write_padded_file(args.output, args.kernel, args.pagesize)
160    write_padded_file(args.output, args.ramdisk, args.pagesize)
161    write_padded_file(args.output, args.second, args.pagesize)
162
163
164def main():
165    args = parse_cmdline()
166    img_id = write_header(args)
167    write_data(args)
168    if args.id:
169        if isinstance(img_id, str):
170            # Python 2's struct.pack returns a string, but py3 returns bytes.
171            img_id = [ord(x) for x in img_id]
172        print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
173
174if __name__ == '__main__':
175    main()
176