Snap for 7550930 from 3a0c86e2eb47ab09cf1befbed8febe39ea2e27d4 to mainline-resolv-release
Change-Id: Ibb88ca1052d308608a3c6f1f23c62306b12aa0d9
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/Android.bp b/Android.bp
index c3cf746..23c55b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,9 @@
// Copyright 2012 The Android Open Source Project
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_headers {
name: "libmkbootimg_abi_headers",
vendor_available: true,
@@ -17,6 +21,10 @@
enabled: true,
},
},
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
cc_library {
@@ -34,15 +42,13 @@
python_defaults {
name: "mkbootimg_defaults",
-
version: {
py2: {
- enabled: true,
- embedded_launcher: true,
+ enabled: false,
},
py3: {
- enabled: false,
- embedded_launcher: false,
+ enabled: true,
+ embedded_launcher: true,
},
},
}
@@ -53,6 +59,9 @@
srcs: [
"mkbootimg.py",
],
+ required: [
+ "avbtool",
+ ],
}
python_binary_host {
@@ -62,3 +71,38 @@
"unpack_bootimg.py",
],
}
+
+
+python_binary_host {
+ name: "repack_bootimg",
+ defaults: ["mkbootimg_defaults"],
+ srcs: [
+ "repack_bootimg.py",
+ ],
+ required: [
+ "lz4",
+ "minigzip",
+ "mkbootfs",
+ "mkbootimg",
+ "toybox",
+ "unpack_bootimg",
+ ],
+}
+
+python_test_host {
+ name: "mkbootimg_test",
+ defaults: ["mkbootimg_defaults"],
+ main: "tests/mkbootimg_test.py",
+ srcs: [
+ "tests/mkbootimg_test.py",
+ ],
+ data: [
+ ":avbtool",
+ ":mkbootimg",
+ ":unpack_bootimg",
+ "tests/data/*",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/OWNERS b/OWNERS
index de2d568..51e09a2 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,3 @@
hridya@google.com
smuckle@google.com
+yochiang@google.com
\ No newline at end of file
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index d937da1..734a94d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,2 @@
[Builtin Hooks]
-pylint2 = true
-pylint3 = true
+pylint = true
diff --git a/include/bootimg/bootimg.h b/include/bootimg/bootimg.h
index 8c9f6ee..8ad95a8 100644
--- a/include/bootimg/bootimg.h
+++ b/include/bootimg/bootimg.h
@@ -29,8 +29,41 @@
#define VENDOR_BOOT_ARGS_SIZE 2048
#define VENDOR_BOOT_NAME_SIZE 16
-// The bootloader expects the structure of boot_img_hdr with header
-// version 0 to be as follows:
+#define VENDOR_RAMDISK_TYPE_NONE 0
+#define VENDOR_RAMDISK_TYPE_PLATFORM 1
+#define VENDOR_RAMDISK_TYPE_RECOVERY 2
+#define VENDOR_RAMDISK_TYPE_DLKM 3
+#define VENDOR_RAMDISK_NAME_SIZE 32
+#define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16
+
+/* When a boot header is of version 0, the structure of boot image is as
+ * follows:
+ *
+ * +-----------------+
+ * | boot header | 1 page
+ * +-----------------+
+ * | kernel | n pages
+ * +-----------------+
+ * | ramdisk | m pages
+ * +-----------------+
+ * | second stage | o pages
+ * +-----------------+
+ *
+ * n = (kernel_size + page_size - 1) / page_size
+ * m = (ramdisk_size + page_size - 1) / page_size
+ * o = (second_size + page_size - 1) / page_size
+ *
+ * 0. all entities are page_size aligned in flash
+ * 1. kernel and ramdisk are required (size != 0)
+ * 2. second is optional (second_size == 0 -> no second)
+ * 3. load each element (kernel, ramdisk, second) at
+ * the specified physical address (kernel_addr, etc)
+ * 4. prepare tags at tag_addr. kernel_args[] is
+ * appended to the kernel commandline in the tags.
+ * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 6. if second_size != 0: jump to second_addr
+ * else: jump to kernel_addr
+ */
struct boot_img_hdr_v0 {
// Must be BOOT_MAGIC.
uint8_t magic[BOOT_MAGIC_SIZE];
@@ -70,12 +103,13 @@
uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
- uint8_t cmdline[BOOT_ARGS_SIZE];
+ uint8_t cmdline[BOOT_ARGS_SIZE]; /* asciiz kernel commandline */
uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
// Supplemental command line data; kept here to maintain
// binary compatibility with older versions of mkbootimg.
+ // Asciiz.
uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
} __attribute__((packed));
@@ -85,35 +119,40 @@
*/
typedef struct boot_img_hdr_v0 boot_img_hdr;
-/* When a boot header is of version 0, the structure of boot image is as
+/* When a boot header is of version 1, the structure of boot image is as
* follows:
*
- * +-----------------+
- * | boot header | 1 page
- * +-----------------+
- * | kernel | n pages
- * +-----------------+
- * | ramdisk | m pages
- * +-----------------+
- * | second stage | o pages
- * +-----------------+
+ * +---------------------+
+ * | boot header | 1 page
+ * +---------------------+
+ * | kernel | n pages
+ * +---------------------+
+ * | ramdisk | m pages
+ * +---------------------+
+ * | second stage | o pages
+ * +---------------------+
+ * | recovery dtbo/acpio | p pages
+ * +---------------------+
*
* n = (kernel_size + page_size - 1) / page_size
* m = (ramdisk_size + page_size - 1) / page_size
* o = (second_size + page_size - 1) / page_size
+ * p = (recovery_dtbo_size + page_size - 1) / page_size
*
* 0. all entities are page_size aligned in flash
* 1. kernel and ramdisk are required (size != 0)
- * 2. second is optional (second_size == 0 -> no second)
- * 3. load each element (kernel, ramdisk, second) at
+ * 2. recovery_dtbo/recovery_acpio is required for recovery.img in non-A/B
+ * devices(recovery_dtbo_size != 0)
+ * 3. second is optional (second_size == 0 -> no second)
+ * 4. load each element (kernel, ramdisk, second) at
* the specified physical address (kernel_addr, etc)
- * 4. prepare tags at tag_addr. kernel_args[] is
- * appended to the kernel commandline in the tags.
- * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
- * 6. if second_size != 0: jump to second_addr
+ * 5. If booting to recovery mode in a non-A/B device, extract recovery
+ * dtbo/acpio and apply the correct set of overlays on the base device tree
+ * depending on the hardware/product revision.
+ * 6. set up registers for kernel entry as required by your architecture
+ * 7. if second_size != 0: jump to second_addr
* else: jump to kernel_addr
*/
-
struct boot_img_hdr_v1 : public boot_img_hdr_v0 {
uint32_t recovery_dtbo_size; /* size in bytes for recovery DTBO/ACPIO image */
uint64_t recovery_dtbo_offset; /* offset to recovery dtbo/acpio in boot image */
@@ -240,6 +279,7 @@
// Version of the boot image header.
uint32_t header_version;
+ // Asciiz kernel commandline.
uint8_t cmdline[BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE];
} __attribute__((packed));
@@ -257,7 +297,7 @@
uint32_t vendor_ramdisk_size; /* size in bytes */
- uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE];
+ uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE]; /* asciiz kernel commandline */
uint32_t tags_addr; /* physical addr for kernel tags (if required) */
uint8_t name[VENDOR_BOOT_NAME_SIZE]; /* asciiz product name */
@@ -267,3 +307,114 @@
uint32_t dtb_size; /* size in bytes for DTB image */
uint64_t dtb_addr; /* physical load address for DTB image */
} __attribute__((packed));
+
+/* When the boot image header has a version of 4, the structure of the boot
+ * image is as follows:
+ *
+ * +---------------------+
+ * | boot header | 4096 bytes
+ * +---------------------+
+ * | kernel | m pages
+ * +---------------------+
+ * | ramdisk | n pages
+ * +---------------------+
+ * | boot signature | g pages
+ * +---------------------+
+ *
+ * m = (kernel_size + 4096 - 1) / 4096
+ * n = (ramdisk_size + 4096 - 1) / 4096
+ * g = (signature_size + 4096 - 1) / 4096
+ *
+ * Note that in version 4 of the boot image header, page size is fixed at 4096
+ * bytes.
+ *
+ * The structure of the vendor boot image version 4, which is required to be
+ * present when a version 4 boot image is used, is as follows:
+ *
+ * +------------------------+
+ * | vendor boot header | o pages
+ * +------------------------+
+ * | vendor ramdisk section | p pages
+ * +------------------------+
+ * | dtb | q pages
+ * +------------------------+
+ * | vendor ramdisk table | r pages
+ * +------------------------+
+ * | bootconfig | s pages
+ * +------------------------+
+ *
+ * o = (2128 + page_size - 1) / page_size
+ * p = (vendor_ramdisk_size + page_size - 1) / page_size
+ * q = (dtb_size + page_size - 1) / page_size
+ * r = (vendor_ramdisk_table_size + page_size - 1) / page_size
+ * s = (vendor_bootconfig_size + page_size - 1) / page_size
+ *
+ * Note that in version 4 of the vendor boot image, multiple vendor ramdisks can
+ * be included in the vendor boot image. The bootloader can select a subset of
+ * ramdisks to load at runtime. To help the bootloader select the ramdisks, each
+ * ramdisk is tagged with a type tag and a set of hardware identifiers
+ * describing the board, soc or platform that this ramdisk is intended for.
+ *
+ * The vendor ramdisk section is consist of multiple ramdisk images concatenated
+ * one after another, and vendor_ramdisk_size is the size of the section, which
+ * is the total size of all the ramdisks included in the vendor boot image.
+ *
+ * The vendor ramdisk table holds the size, offset, type, name and hardware
+ * identifiers of each ramdisk. The type field denotes the type of its content.
+ * The vendor ramdisk names are unique. The hardware identifiers are specified
+ * in the board_id field in each table entry. The board_id field is consist of a
+ * vector of unsigned integer words, and the encoding scheme is defined by the
+ * hardware vendor.
+ *
+ * For the different type of ramdisks, there are:
+ * - VENDOR_RAMDISK_TYPE_NONE indicates the value is unspecified.
+ * - VENDOR_RAMDISK_TYPE_PLATFORM ramdisks contain platform specific bits, so
+ * the bootloader should always load these into memory.
+ * - VENDOR_RAMDISK_TYPE_RECOVERY ramdisks contain recovery resources, so
+ * the bootloader should load these when booting into recovery.
+ * - VENDOR_RAMDISK_TYPE_DLKM ramdisks contain dynamic loadable kernel
+ * modules.
+ *
+ * Version 4 of the vendor boot image also adds a bootconfig section to the end
+ * of the image. This section contains Boot Configuration parameters known at
+ * build time. The bootloader is responsible for placing this section directly
+ * after the generic ramdisk, followed by the bootconfig trailer, before
+ * entering the kernel.
+ *
+ * 0. all entities in the boot image are 4096-byte aligned in flash, all
+ * entities in the vendor boot image are page_size (determined by the vendor
+ * and specified in the vendor boot image header) aligned in flash
+ * 1. kernel, ramdisk, and DTB are required (size != 0)
+ * 2. load the kernel and DTB at the specified physical address (kernel_addr,
+ * dtb_addr)
+ * 3. load the vendor ramdisks at ramdisk_addr
+ * 4. load the generic ramdisk immediately following the vendor ramdisk in
+ * memory
+ * 5. load the bootconfig immediately following the generic ramdisk. Add
+ * additional bootconfig parameters followed by the bootconfig trailer.
+ * 6. set up registers for kernel entry as required by your architecture
+ * 7. if the platform has a second stage bootloader jump to it (must be
+ * contained outside boot and vendor boot partitions), otherwise
+ * jump to kernel_addr
+ */
+struct boot_img_hdr_v4 : public boot_img_hdr_v3 {
+ uint32_t signature_size; /* size in bytes */
+} __attribute__((packed));
+
+struct vendor_boot_img_hdr_v4 : public vendor_boot_img_hdr_v3 {
+ uint32_t vendor_ramdisk_table_size; /* size in bytes for the vendor ramdisk table */
+ uint32_t vendor_ramdisk_table_entry_num; /* number of entries in the vendor ramdisk table */
+ uint32_t vendor_ramdisk_table_entry_size; /* size in bytes for a vendor ramdisk table entry */
+ uint32_t bootconfig_size; /* size in bytes for the bootconfig section */
+} __attribute__((packed));
+
+struct vendor_ramdisk_table_entry_v4 {
+ uint32_t ramdisk_size; /* size in bytes for the ramdisk image */
+ uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */
+ uint32_t ramdisk_type; /* type of the ramdisk */
+ uint8_t ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */
+
+ // Hardware identifiers describing the board, soc or platform which this
+ // ramdisk is intended to be loaded on.
+ uint32_t board_id[VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE];
+} __attribute__((packed));
diff --git a/mkbootimg.py b/mkbootimg.py
old mode 100644
new mode 100755
index 00a4623..e0b0839
--- a/mkbootimg.py
+++ b/mkbootimg.py
@@ -1,4 +1,5 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
+#
# Copyright 2015, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,16 +14,55 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import print_function
+"""Creates the boot image."""
-from argparse import ArgumentParser, FileType, Action
+from argparse import (ArgumentParser, ArgumentTypeError,
+ FileType, RawDescriptionHelpFormatter)
from hashlib import sha1
from os import fstat
-import re
from struct import pack
+import array
+import collections
+import os
+import re
+import subprocess
+import tempfile
+# Constant and structure definition is in
+# system/tools/mkbootimg/include/bootimg/bootimg.h
+BOOT_MAGIC = 'ANDROID!'
+BOOT_MAGIC_SIZE = 8
+BOOT_NAME_SIZE = 16
+BOOT_ARGS_SIZE = 512
+BOOT_EXTRA_ARGS_SIZE = 1024
+BOOT_IMAGE_HEADER_V1_SIZE = 1648
+BOOT_IMAGE_HEADER_V2_SIZE = 1660
+BOOT_IMAGE_HEADER_V3_SIZE = 1580
BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
+BOOT_IMAGE_HEADER_V4_SIZE = 1584
+BOOT_IMAGE_V4_SIGNATURE_SIZE = 4096
+
+VENDOR_BOOT_MAGIC = 'VNDRBOOT'
+VENDOR_BOOT_MAGIC_SIZE = 8
+VENDOR_BOOT_NAME_SIZE = BOOT_NAME_SIZE
+VENDOR_BOOT_ARGS_SIZE = 2048
+VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112
+VENDOR_BOOT_IMAGE_HEADER_V4_SIZE = 2128
+
+VENDOR_RAMDISK_TYPE_NONE = 0
+VENDOR_RAMDISK_TYPE_PLATFORM = 1
+VENDOR_RAMDISK_TYPE_RECOVERY = 2
+VENDOR_RAMDISK_TYPE_DLKM = 3
+VENDOR_RAMDISK_NAME_SIZE = 32
+VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16
+VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE = 108
+
+# Names with special meaning, mustn't be specified in --ramdisk_name.
+VENDOR_RAMDISK_NAME_BLOCKLIST = {b'default'}
+
+PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT = '--vendor_ramdisk_fragment'
+
def filesize(f):
if f is None:
@@ -49,87 +89,135 @@
def get_number_of_pages(image_size, page_size):
"""calculates the number of pages required for the image"""
- return (image_size + page_size - 1) / page_size
+ return (image_size + page_size - 1) // page_size
def get_recovery_dtbo_offset(args):
"""calculates the offset of recovery_dtbo image in the boot image"""
num_header_pages = 1 # header occupies a page
num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize)
- num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize)
+ num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk),
+ args.pagesize)
num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize)
dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages +
num_ramdisk_pages + num_second_pages)
return dtbo_offset
-def write_header_v3(args):
- BOOT_IMAGE_HEADER_V3_SIZE = 1580
- BOOT_MAGIC = 'ANDROID!'.encode()
+def write_header_v3_and_above(args):
+ if args.header_version > 3:
+ boot_header_size = BOOT_IMAGE_HEADER_V4_SIZE
+ else:
+ boot_header_size = BOOT_IMAGE_HEADER_V3_SIZE
- args.output.write(pack('8s', BOOT_MAGIC))
- args.output.write(pack(
- '4I',
- filesize(args.kernel), # kernel size in bytes
- filesize(args.ramdisk), # ramdisk size in bytes
- (args.os_version << 11) | args.os_patch_level, # os version and patch level
- BOOT_IMAGE_HEADER_V3_SIZE))
-
- args.output.write(pack('4I', 0, 0, 0, 0)) # reserved
-
- args.output.write(pack('I', args.header_version)) # version of bootimage header
- args.output.write(pack('1536s', args.cmdline.encode()))
+ args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode()))
+ # kernel size in bytes
+ args.output.write(pack('I', filesize(args.kernel)))
+ # ramdisk size in bytes
+ args.output.write(pack('I', filesize(args.ramdisk)))
+ # os version and patch level
+ args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level))
+ args.output.write(pack('I', boot_header_size))
+ # reserved
+ args.output.write(pack('4I', 0, 0, 0, 0))
+ # version of boot image header
+ args.output.write(pack('I', args.header_version))
+ args.output.write(pack(f'{BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE}s',
+ args.cmdline))
+ if args.header_version >= 4:
+ # The signature used to verify boot image v4.
+ args.output.write(pack('I', BOOT_IMAGE_V4_SIGNATURE_SIZE))
pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE)
+
def write_vendor_boot_header(args):
- VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112
- BOOT_MAGIC = 'VNDRBOOT'.encode()
-
- args.vendor_boot.write(pack('8s', BOOT_MAGIC))
- args.vendor_boot.write(pack(
- '5I',
- args.header_version, # version of header
- args.pagesize, # flash page size we assume
- args.base + args.kernel_offset, # kernel physical load addr
- args.base + args.ramdisk_offset, # ramdisk physical load addr
- filesize(args.vendor_ramdisk))) # vendor ramdisk size in bytes
- args.vendor_boot.write(pack('2048s', args.vendor_cmdline.encode()))
- args.vendor_boot.write(pack('I', args.base + args.tags_offset)) # physical addr for kernel tags
- args.vendor_boot.write(pack('16s', args.board.encode())) # asciiz product name
- args.vendor_boot.write(pack('I', VENDOR_BOOT_IMAGE_HEADER_V3_SIZE)) # header size in bytes
if filesize(args.dtb) == 0:
- raise ValueError("DTB image must not be empty.")
- args.vendor_boot.write(pack('I', filesize(args.dtb))) # size in bytes
- args.vendor_boot.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address
- pad_file(args.vendor_boot, args.pagesize)
-
-def write_header(args):
- BOOT_IMAGE_HEADER_V1_SIZE = 1648
- BOOT_IMAGE_HEADER_V2_SIZE = 1660
- BOOT_MAGIC = 'ANDROID!'.encode()
+ raise ValueError('DTB image must not be empty.')
if args.header_version > 3:
- raise ValueError('Boot header version %d not supported' % args.header_version)
- elif args.header_version == 3:
- return write_header_v3(args)
+ vendor_ramdisk_size = args.vendor_ramdisk_total_size
+ vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V4_SIZE
+ else:
+ vendor_ramdisk_size = filesize(args.vendor_ramdisk)
+ vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V3_SIZE
- args.output.write(pack('8s', BOOT_MAGIC))
- final_ramdisk_offset = (args.base + args.ramdisk_offset) if filesize(args.ramdisk) > 0 else 0
- final_second_offset = (args.base + args.second_offset) if filesize(args.second) > 0 else 0
- args.output.write(pack(
- '10I',
- filesize(args.kernel), # size in bytes
- args.base + args.kernel_offset, # physical load addr
- filesize(args.ramdisk), # size in bytes
- final_ramdisk_offset, # physical load addr
- filesize(args.second), # size in bytes
- final_second_offset, # physical load addr
- args.base + args.tags_offset, # physical addr for kernel tags
- args.pagesize, # flash page size we assume
- args.header_version, # version of bootimage header
- (args.os_version << 11) | args.os_patch_level)) # os version and patch level
- args.output.write(pack('16s', args.board.encode())) # asciiz product name
- args.output.write(pack('512s', args.cmdline[:512].encode()))
+ args.vendor_boot.write(pack(f'{VENDOR_BOOT_MAGIC_SIZE}s',
+ VENDOR_BOOT_MAGIC.encode()))
+ # version of boot image header
+ args.vendor_boot.write(pack('I', args.header_version))
+ # flash page size
+ args.vendor_boot.write(pack('I', args.pagesize))
+ # kernel physical load address
+ args.vendor_boot.write(pack('I', args.base + args.kernel_offset))
+ # ramdisk physical load address
+ args.vendor_boot.write(pack('I', args.base + args.ramdisk_offset))
+ # ramdisk size in bytes
+ args.vendor_boot.write(pack('I', vendor_ramdisk_size))
+ args.vendor_boot.write(pack(f'{VENDOR_BOOT_ARGS_SIZE}s',
+ args.vendor_cmdline))
+ # kernel tags physical load address
+ args.vendor_boot.write(pack('I', args.base + args.tags_offset))
+ # asciiz product name
+ args.vendor_boot.write(pack(f'{VENDOR_BOOT_NAME_SIZE}s', args.board))
+
+ # header size in bytes
+ args.vendor_boot.write(pack('I', vendor_boot_header_size))
+
+ # dtb size in bytes
+ args.vendor_boot.write(pack('I', filesize(args.dtb)))
+ # dtb physical load address
+ args.vendor_boot.write(pack('Q', args.base + args.dtb_offset))
+
+ if args.header_version > 3:
+ vendor_ramdisk_table_size = (args.vendor_ramdisk_table_entry_num *
+ VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE)
+ # vendor ramdisk table size in bytes
+ args.vendor_boot.write(pack('I', vendor_ramdisk_table_size))
+ # number of vendor ramdisk table entries
+ args.vendor_boot.write(pack('I', args.vendor_ramdisk_table_entry_num))
+ # vendor ramdisk table entry size in bytes
+ args.vendor_boot.write(pack('I', VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE))
+ # bootconfig section size in bytes
+ args.vendor_boot.write(pack('I', filesize(args.vendor_bootconfig)))
+ pad_file(args.vendor_boot, args.pagesize)
+
+
+def write_header(args):
+ if args.header_version > 4:
+ raise ValueError(
+ f'Boot header version {args.header_version} not supported')
+ if args.header_version in {3, 4}:
+ return write_header_v3_and_above(args)
+
+ ramdisk_load_address = ((args.base + args.ramdisk_offset)
+ if filesize(args.ramdisk) > 0 else 0)
+ second_load_address = ((args.base + args.second_offset)
+ if filesize(args.second) > 0 else 0)
+
+ args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode()))
+ # kernel size in bytes
+ args.output.write(pack('I', filesize(args.kernel)))
+ # kernel physical load address
+ args.output.write(pack('I', args.base + args.kernel_offset))
+ # ramdisk size in bytes
+ args.output.write(pack('I', filesize(args.ramdisk)))
+ # ramdisk physical load address
+ args.output.write(pack('I', ramdisk_load_address))
+ # second bootloader size in bytes
+ args.output.write(pack('I', filesize(args.second)))
+ # second bootloader physical load address
+ args.output.write(pack('I', second_load_address))
+ # kernel tags physical load address
+ args.output.write(pack('I', args.base + args.tags_offset))
+ # flash page size
+ args.output.write(pack('I', args.pagesize))
+ # version of boot image header
+ args.output.write(pack('I', args.header_version))
+ # os version and patch level
+ args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level))
+ # asciiz product name
+ args.output.write(pack(f'{BOOT_NAME_SIZE}s', args.board))
+ args.output.write(pack(f'{BOOT_ARGS_SIZE}s', args.cmdline))
sha = sha1()
update_sha(sha, args.kernel)
@@ -144,14 +232,18 @@
img_id = pack('32s', sha.digest())
args.output.write(img_id)
- args.output.write(pack('1024s', args.cmdline[512:].encode()))
+ args.output.write(pack(f'{BOOT_EXTRA_ARGS_SIZE}s', args.extra_cmdline))
if args.header_version > 0:
- args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes
if args.recovery_dtbo:
- args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset
+ # recovery dtbo size in bytes
+ args.output.write(pack('I', filesize(args.recovery_dtbo)))
+ # recovert dtbo offset in the boot image
+ args.output.write(pack('Q', get_recovery_dtbo_offset(args)))
else:
- args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
+ # Set to zero if no recovery dtbo
+ args.output.write(pack('I', 0))
+ args.output.write(pack('Q', 0))
# Populate boot image header size for header versions 1 and 2.
if args.header_version == 1:
@@ -160,29 +252,101 @@
args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE))
if args.header_version > 1:
-
if filesize(args.dtb) == 0:
- raise ValueError("DTB image must not be empty.")
+ raise ValueError('DTB image must not be empty.')
- args.output.write(pack('I', filesize(args.dtb))) # size in bytes
- args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address
+ # dtb size in bytes
+ args.output.write(pack('I', filesize(args.dtb)))
+ # dtb physical load address
+ args.output.write(pack('Q', args.base + args.dtb_offset))
+
pad_file(args.output, args.pagesize)
return img_id
-class ValidateStrLenAction(Action):
- def __init__(self, option_strings, dest, nargs=None, **kwargs):
- if 'maxlen' not in kwargs:
- raise ValueError('maxlen must be set')
- self.maxlen = int(kwargs['maxlen'])
- del kwargs['maxlen']
- super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
+class AsciizBytes:
+ """Parses a string and encodes it as an asciiz bytes object.
- def __call__(self, parser, namespace, values, option_string=None):
- if len(values) > self.maxlen:
+ >>> AsciizBytes(bufsize=4)('foo')
+ b'foo\\x00'
+ >>> AsciizBytes(bufsize=4)('foob')
+ Traceback (most recent call last):
+ ...
+ argparse.ArgumentTypeError: Encoded asciiz length exceeded: max 4, got 5
+ """
+
+ def __init__(self, bufsize):
+ self.bufsize = bufsize
+
+ def __call__(self, arg):
+ arg_bytes = arg.encode() + b'\x00'
+ if len(arg_bytes) > self.bufsize:
+ raise ArgumentTypeError(
+ 'Encoded asciiz length exceeded: '
+ f'max {self.bufsize}, got {len(arg_bytes)}')
+ return arg_bytes
+
+
+class VendorRamdiskTableBuilder:
+ """Vendor ramdisk table builder.
+
+ Attributes:
+ entries: A list of VendorRamdiskTableEntry namedtuple.
+ ramdisk_total_size: Total size in bytes of all ramdisks in the table.
+ """
+
+ VendorRamdiskTableEntry = collections.namedtuple( # pylint: disable=invalid-name
+ 'VendorRamdiskTableEntry',
+ ['ramdisk_path', 'ramdisk_size', 'ramdisk_offset', 'ramdisk_type',
+ 'ramdisk_name', 'board_id'])
+
+ def __init__(self):
+ self.entries = []
+ self.ramdisk_total_size = 0
+ self.ramdisk_names = set()
+
+ def add_entry(self, ramdisk_path, ramdisk_type, ramdisk_name, board_id):
+ # Strip any trailing null for simple comparison.
+ stripped_ramdisk_name = ramdisk_name.rstrip(b'\x00')
+ if stripped_ramdisk_name in VENDOR_RAMDISK_NAME_BLOCKLIST:
raise ValueError(
- 'String argument too long: max {0:d}, got {1:d}'.format(self.maxlen, len(values)))
- setattr(namespace, self.dest, values)
+ f'Banned vendor ramdisk name: {stripped_ramdisk_name}')
+ if stripped_ramdisk_name in self.ramdisk_names:
+ raise ValueError(
+ f'Duplicated vendor ramdisk name: {stripped_ramdisk_name}')
+ self.ramdisk_names.add(stripped_ramdisk_name)
+
+ if board_id is None:
+ board_id = array.array(
+ 'I', [0] * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)
+ else:
+ board_id = array.array('I', board_id)
+ if len(board_id) != VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE:
+ raise ValueError('board_id size must be '
+ f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}')
+
+ with open(ramdisk_path, 'rb') as f:
+ ramdisk_size = filesize(f)
+ self.entries.append(self.VendorRamdiskTableEntry(
+ ramdisk_path, ramdisk_size, self.ramdisk_total_size, ramdisk_type,
+ ramdisk_name, board_id))
+ self.ramdisk_total_size += ramdisk_size
+
+ def write_ramdisks_padded(self, fout, alignment):
+ for entry in self.entries:
+ with open(entry.ramdisk_path, 'rb') as f:
+ fout.write(f.read())
+ pad_file(fout, alignment)
+
+ def write_entries_padded(self, fout, alignment):
+ for entry in self.entries:
+ fout.write(pack('I', entry.ramdisk_size))
+ fout.write(pack('I', entry.ramdisk_offset))
+ fout.write(pack('I', entry.ramdisk_type))
+ fout.write(pack(f'{VENDOR_RAMDISK_NAME_SIZE}s',
+ entry.ramdisk_name))
+ fout.write(entry.board_id)
+ pad_file(fout, alignment)
def write_padded_file(f_out, f_in, padding):
@@ -225,49 +389,236 @@
return 0
+def parse_vendor_ramdisk_type(x):
+ type_dict = {
+ 'none': VENDOR_RAMDISK_TYPE_NONE,
+ 'platform': VENDOR_RAMDISK_TYPE_PLATFORM,
+ 'recovery': VENDOR_RAMDISK_TYPE_RECOVERY,
+ 'dlkm': VENDOR_RAMDISK_TYPE_DLKM,
+ }
+ if x.lower() in type_dict:
+ return type_dict[x.lower()]
+ return parse_int(x)
+
+
+def get_vendor_boot_v4_usage():
+ return """vendor boot version 4 arguments:
+ --ramdisk_type {none,platform,recovery,dlkm}
+ specify the type of the ramdisk
+ --ramdisk_name NAME
+ specify the name of the ramdisk
+ --board_id{0..15} NUMBER
+ specify the value of the board_id vector, defaults to 0
+ --vendor_ramdisk_fragment VENDOR_RAMDISK_FILE
+ path to the vendor ramdisk file
+
+ These options can be specified multiple times, where each vendor ramdisk
+ option group ends with a --vendor_ramdisk_fragment option.
+ Each option group appends an additional ramdisk to the vendor boot image.
+"""
+
+
+def parse_vendor_ramdisk_args(args, args_list):
+ """Parses vendor ramdisk specific arguments.
+
+ Args:
+ args: An argparse.Namespace object. Parsed results are stored into this
+ object.
+ args_list: A list of argument strings to be parsed.
+
+ Returns:
+ A list argument strings that are not parsed by this method.
+ """
+ parser = ArgumentParser(add_help=False)
+ parser.add_argument('--ramdisk_type', type=parse_vendor_ramdisk_type,
+ default=VENDOR_RAMDISK_TYPE_NONE)
+ parser.add_argument('--ramdisk_name',
+ type=AsciizBytes(bufsize=VENDOR_RAMDISK_NAME_SIZE),
+ required=True)
+ for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE):
+ parser.add_argument(f'--board_id{i}', type=parse_int, default=0)
+ parser.add_argument(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT, required=True)
+
+ unknown_args = []
+
+ vendor_ramdisk_table_builder = VendorRamdiskTableBuilder()
+ if args.vendor_ramdisk is not None:
+ vendor_ramdisk_table_builder.add_entry(
+ args.vendor_ramdisk.name, VENDOR_RAMDISK_TYPE_PLATFORM, b'', None)
+
+ while PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT in args_list:
+ idx = args_list.index(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT) + 2
+ vendor_ramdisk_args = args_list[:idx]
+ args_list = args_list[idx:]
+
+ ramdisk_args, extra_args = parser.parse_known_args(vendor_ramdisk_args)
+ ramdisk_args_dict = vars(ramdisk_args)
+ unknown_args.extend(extra_args)
+
+ ramdisk_path = ramdisk_args.vendor_ramdisk_fragment
+ ramdisk_type = ramdisk_args.ramdisk_type
+ ramdisk_name = ramdisk_args.ramdisk_name
+ board_id = [ramdisk_args_dict[f'board_id{i}']
+ for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)]
+ vendor_ramdisk_table_builder.add_entry(ramdisk_path, ramdisk_type,
+ ramdisk_name, board_id)
+
+ if len(args_list) > 0:
+ unknown_args.extend(args_list)
+
+ args.vendor_ramdisk_total_size = (vendor_ramdisk_table_builder
+ .ramdisk_total_size)
+ args.vendor_ramdisk_table_entry_num = len(vendor_ramdisk_table_builder
+ .entries)
+ args.vendor_ramdisk_table_builder = vendor_ramdisk_table_builder
+ return unknown_args
+
+
def parse_cmdline():
- parser = ArgumentParser()
- parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'))
- parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
- parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
- parser.add_argument('--dtb', help='path to dtb', type=FileType('rb'))
- recovery_dtbo_group = parser.add_mutually_exclusive_group()
- recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO',
- type=FileType('rb'))
- recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO',
- type=FileType('rb'), metavar='RECOVERY_ACPIO',
- dest='recovery_dtbo')
- parser.add_argument('--cmdline', help='extra arguments to be passed on the '
- 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
+ version_parser = ArgumentParser(add_help=False)
+ version_parser.add_argument('--header_version', type=parse_int, default=0)
+ if version_parser.parse_known_args()[0].header_version < 3:
+ # For boot header v0 to v2, the kernel commandline field is split into
+ # two fields, cmdline and extra_cmdline. Both fields are asciiz strings,
+ # so we minus one here to ensure the encoded string plus the
+ # null-terminator can fit in the buffer size.
+ cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE - 1
+ else:
+ cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE
+
+ parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
+ epilog=get_vendor_boot_v4_usage())
+ parser.add_argument('--kernel', type=FileType('rb'),
+ help='path to the kernel')
+ parser.add_argument('--ramdisk', type=FileType('rb'),
+ help='path to the ramdisk')
+ parser.add_argument('--second', type=FileType('rb'),
+ help='path to the second bootloader')
+ parser.add_argument('--dtb', type=FileType('rb'), help='path to the dtb')
+ dtbo_group = parser.add_mutually_exclusive_group()
+ dtbo_group.add_argument('--recovery_dtbo', type=FileType('rb'),
+ help='path to the recovery DTBO')
+ dtbo_group.add_argument('--recovery_acpio', type=FileType('rb'),
+ metavar='RECOVERY_ACPIO', dest='recovery_dtbo',
+ help='path to the recovery ACPIO')
+ parser.add_argument('--cmdline', type=AsciizBytes(bufsize=cmdline_size),
+ default='', help='kernel command line arguments')
parser.add_argument('--vendor_cmdline',
- help='kernel command line arguments contained in vendor boot',
- default='', action=ValidateStrLenAction, maxlen=2048)
- parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
- parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
- parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int,
- default=0x01000000)
- parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
- default=0x00f00000)
- parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000)
+ type=AsciizBytes(bufsize=VENDOR_BOOT_ARGS_SIZE),
+ default='',
+ help='vendor boot kernel command line arguments')
+ parser.add_argument('--base', type=parse_int, default=0x10000000,
+ help='base address')
+ parser.add_argument('--kernel_offset', type=parse_int, default=0x00008000,
+ help='kernel offset')
+ parser.add_argument('--ramdisk_offset', type=parse_int, default=0x01000000,
+ help='ramdisk offset')
+ parser.add_argument('--second_offset', type=parse_int, default=0x00f00000,
+ help='second bootloader offset')
+ parser.add_argument('--dtb_offset', type=parse_int, default=0x01f00000,
+ help='dtb offset')
- parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
- default=0)
- parser.add_argument('--os_patch_level', help='operating system patch level',
- type=parse_os_patch_level, default=0)
- parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
- parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
- maxlen=16)
- parser.add_argument('--pagesize', help='page size', type=parse_int,
- choices=[2**i for i in range(11, 15)], default=2048)
- parser.add_argument('--id', help='print the image ID on standard output',
- action='store_true')
- parser.add_argument('--header_version', help='boot image header version', type=parse_int,
- default=0)
- parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'))
- parser.add_argument('--vendor_boot', help='vendor boot output file name', type=FileType('wb'))
- parser.add_argument('--vendor_ramdisk', help='path to the vendor ramdisk', type=FileType('rb'))
+ parser.add_argument('--os_version', type=parse_os_version, default=0,
+ help='operating system version')
+ parser.add_argument('--os_patch_level', type=parse_os_patch_level,
+ default=0, help='operating system patch level')
+ parser.add_argument('--tags_offset', type=parse_int, default=0x00000100,
+ help='tags offset')
+ parser.add_argument('--board', type=AsciizBytes(bufsize=BOOT_NAME_SIZE),
+ default='', help='board name')
+ parser.add_argument('--pagesize', type=parse_int,
+ choices=[2**i for i in range(11, 15)], default=2048,
+ help='page size')
+ parser.add_argument('--id', action='store_true',
+ help='print the image ID on standard output')
+ parser.add_argument('--header_version', type=parse_int, default=0,
+ help='boot image header version')
+ parser.add_argument('-o', '--output', type=FileType('wb'),
+ help='output file name')
+ parser.add_argument('--gki_signing_algorithm',
+ help='GKI signing algorithm to use')
+ parser.add_argument('--gki_signing_key',
+ help='path to RSA private key file')
+ parser.add_argument('--gki_signing_signature_args',
+ help='other hash arguments passed to avbtool')
+ parser.add_argument('--gki_signing_avbtool_path',
+ help='path to avbtool for boot signature generation')
+ parser.add_argument('--vendor_boot', type=FileType('wb'),
+ help='vendor boot output file name')
+ parser.add_argument('--vendor_ramdisk', type=FileType('rb'),
+ help='path to the vendor ramdisk')
+ parser.add_argument('--vendor_bootconfig', type=FileType('rb'),
+ help='path to the vendor bootconfig file')
- return parser.parse_args()
+ args, extra_args = parser.parse_known_args()
+ if args.vendor_boot is not None and args.header_version > 3:
+ extra_args = parse_vendor_ramdisk_args(args, extra_args)
+ if len(extra_args) > 0:
+ raise ValueError(f'Unrecognized arguments: {extra_args}')
+
+ if args.header_version < 3:
+ args.extra_cmdline = args.cmdline[BOOT_ARGS_SIZE-1:]
+ args.cmdline = args.cmdline[:BOOT_ARGS_SIZE-1] + b'\x00'
+ assert len(args.cmdline) <= BOOT_ARGS_SIZE
+ assert len(args.extra_cmdline) <= BOOT_EXTRA_ARGS_SIZE
+
+ return args
+
+
+def add_boot_image_signature(args, pagesize):
+ """Adds the boot image signature.
+
+ Note that the signature will only be verified in VTS to ensure a
+ generic boot.img is used. It will not be used by the device
+ bootloader at boot time. The bootloader should only verify
+ the boot vbmeta at the end of the boot partition (or in the top-level
+ vbmeta partition) via the Android Verified Boot process, when the
+ device boots.
+ """
+ args.output.flush() # Flush the buffer for signature calculation.
+
+ # Appends zeros if the signing key is not specified.
+ if not args.gki_signing_key or not args.gki_signing_algorithm:
+ zeros = b'\x00' * BOOT_IMAGE_V4_SIGNATURE_SIZE
+ args.output.write(zeros)
+ pad_file(args.output, pagesize)
+ return
+
+ avbtool = 'avbtool' # Used from otatools.zip or Android build env.
+
+ # We need to specify the path of avbtool in build/core/Makefile.
+ # Because avbtool is not guaranteed to be in $PATH there.
+ if args.gki_signing_avbtool_path:
+ avbtool = args.gki_signing_avbtool_path
+
+ # Need to specify a value of --partition_size for avbtool to work.
+ # We use 64 MB below, but avbtool will not resize the boot image to
+ # this size because --do_not_append_vbmeta_image is also specified.
+ avbtool_cmd = [
+ avbtool, 'add_hash_footer',
+ '--partition_name', 'boot',
+ '--partition_size', str(64 * 1024 * 1024),
+ '--image', args.output.name,
+ '--algorithm', args.gki_signing_algorithm,
+ '--key', args.gki_signing_key,
+ '--salt', 'd00df00d'] # TODO: use a hash of kernel/ramdisk as the salt.
+
+ # Additional arguments passed to avbtool.
+ if args.gki_signing_signature_args:
+ avbtool_cmd += args.gki_signing_signature_args.split()
+
+ # Outputs the signed vbmeta to a separate file, then append to boot.img
+ # as the boot signature.
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ boot_signature_output = os.path.join(temp_out_dir, 'boot_signature')
+ avbtool_cmd += ['--do_not_append_vbmeta_image',
+ '--output_vbmeta_image', boot_signature_output]
+ subprocess.check_call(avbtool_cmd)
+ with open(boot_signature_output, 'rb') as boot_signature:
+ if filesize(boot_signature) > BOOT_IMAGE_V4_SIGNATURE_SIZE:
+ raise ValueError(
+ f'boot sigature size is > {BOOT_IMAGE_V4_SIGNATURE_SIZE}')
+ write_padded_file(args.output, boot_signature, pagesize)
def write_data(args, pagesize):
@@ -279,37 +630,44 @@
write_padded_file(args.output, args.recovery_dtbo, pagesize)
if args.header_version == 2:
write_padded_file(args.output, args.dtb, pagesize)
+ if args.header_version >= 4:
+ add_boot_image_signature(args, pagesize)
def write_vendor_boot_data(args):
- write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize)
- write_padded_file(args.vendor_boot, args.dtb, args.pagesize)
+ if args.header_version > 3:
+ builder = args.vendor_ramdisk_table_builder
+ builder.write_ramdisks_padded(args.vendor_boot, args.pagesize)
+ write_padded_file(args.vendor_boot, args.dtb, args.pagesize)
+ builder.write_entries_padded(args.vendor_boot, args.pagesize)
+ write_padded_file(args.vendor_boot, args.vendor_bootconfig,
+ args.pagesize)
+ else:
+ write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize)
+ write_padded_file(args.vendor_boot, args.dtb, args.pagesize)
def main():
args = parse_cmdline()
if args.vendor_boot is not None:
- if args.header_version < 3:
- raise ValueError('--vendor_boot not compatible with given header version')
- if args.vendor_ramdisk is None:
+ if args.header_version not in {3, 4}:
+ raise ValueError(
+ '--vendor_boot not compatible with given header version')
+ if args.header_version == 3 and args.vendor_ramdisk is None:
raise ValueError('--vendor_ramdisk missing or invalid')
write_vendor_boot_header(args)
write_vendor_boot_data(args)
if args.output is not None:
- if args.kernel is None:
- raise ValueError('kernel must be supplied when creating a boot image')
if args.second is not None and args.header_version > 2:
- raise ValueError('--second not compatible with given header version')
+ raise ValueError(
+ '--second not compatible with given header version')
img_id = write_header(args)
if args.header_version > 2:
write_data(args, BOOT_IMAGE_HEADER_V3_PAGESIZE)
else:
write_data(args, args.pagesize)
if args.id and img_id is not None:
- # Python 2's struct.pack returns a string, but py3 returns bytes.
- if isinstance(img_id, str):
- img_id = [ord(x) for x in img_id]
- print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
+ print('0x' + ''.join(f'{octet:02x}' for octet in img_id))
if __name__ == '__main__':
diff --git a/pylintrc b/pylintrc
index 1990be7..b65d218 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,46 +1,33 @@
+# This Pylint rcfile contains a best-effort configuration to uphold the
+# best-practices and style described in the Google Python style guide:
+# https://google.github.io/styleguide/pyguide.html
+#
+# Its canonical open-source location is:
+# https://google.github.io/styleguide/pylintrc
+
[MASTER]
-# Specify a configuration file.
-#rcfile=
+# Files or directories to be skipped. They should be base names, not paths.
+ignore=third_party
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Profiled execution.
-profile=no
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS
+# Files or directories matching the regex patterns are skipped. The regex
+# matches against base names, not paths.
+ignore-patterns=
# Pickle collected data for later comparisons.
-persistent=yes
+persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
-jobs=1
+jobs=4
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code
-extension-pkg-whitelist=
-
-# Allow optimization of some AST trees. This will activate a peephole AST
-# optimizer, which will apply various small optimizations. For instance, it can
-# be used to obtain the result of joining multiple strings with the addition
-# operator. Joining a lot of strings can lead to a maximum recursion error in
-# Pylint and this flag can prevent that. It has one side effect, the resulting
-# AST will be different than the one from reality.
-optimize-ast=no
-
[MESSAGES CONTROL]
@@ -50,7 +37,8 @@
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
-# multiple time. See also the "--disable" option for examples.
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
@@ -62,7 +50,102 @@
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
-disable=invalid-name,missing-docstring,too-many-branches,too-many-locals,too-many-arguments,too-many-statements,duplicate-code,too-few-public-methods,too-many-instance-attributes,too-many-lines,too-many-public-methods,locally-disabled,fixme,not-callable
+disable=abstract-method,
+ apply-builtin,
+ arguments-differ,
+ attribute-defined-outside-init,
+ backtick,
+ bad-option-value,
+ basestring-builtin,
+ buffer-builtin,
+ c-extension-no-member,
+ consider-using-enumerate,
+ cmp-builtin,
+ cmp-method,
+ coerce-builtin,
+ coerce-method,
+ delslice-method,
+ div-method,
+ duplicate-code,
+ eq-without-hash,
+ execfile-builtin,
+ file-builtin,
+ filter-builtin-not-iterating,
+ fixme,
+ getslice-method,
+ global-statement,
+ hex-method,
+ idiv-method,
+ implicit-str-concat-in-sequence,
+ import-error,
+ import-self,
+ import-star-module-level,
+ inconsistent-return-statements,
+ input-builtin,
+ intern-builtin,
+ invalid-str-codec,
+ locally-disabled,
+ long-builtin,
+ long-suffix,
+ map-builtin-not-iterating,
+ misplaced-comparison-constant,
+ missing-function-docstring,
+ metaclass-assignment,
+ next-method-called,
+ next-method-defined,
+ no-absolute-import,
+ no-else-break,
+ no-else-continue,
+ no-else-raise,
+ no-else-return,
+ no-init, # added
+ no-member,
+ no-name-in-module,
+ no-self-use,
+ nonzero-method,
+ oct-method,
+ old-division,
+ old-ne-operator,
+ old-octal-literal,
+ old-raise-syntax,
+ parameter-unpacking,
+ print-statement,
+ raising-string,
+ range-builtin-not-iterating,
+ raw_input-builtin,
+ rdiv-method,
+ reduce-builtin,
+ relative-import,
+ reload-builtin,
+ round-builtin,
+ setslice-method,
+ signature-differs,
+ standarderror-builtin,
+ suppressed-message,
+ sys-max-int,
+ too-few-public-methods,
+ too-many-ancestors,
+ too-many-arguments,
+ too-many-boolean-expressions,
+ too-many-branches,
+ too-many-instance-attributes,
+ too-many-locals,
+ too-many-nested-blocks,
+ too-many-public-methods,
+ too-many-return-statements,
+ too-many-statements,
+ trailing-newlines,
+ unichr-builtin,
+ unicode-builtin,
+ unnecessary-pass,
+ unpacking-in-except,
+ useless-else-on-loop,
+ useless-object-inheritance,
+ useless-suppression,
+ using-cmp-argument,
+ wrong-import-order,
+ xrange-builtin,
+ zip-builtin-not-iterating,
[REPORTS]
@@ -74,11 +157,12 @@
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
+# written in a file name "pylint_global.[txt|html]". This option is deprecated
+# and it will be removed in Pylint 2.0.
files-output=no
# Tells whether to display a full report or only the messages
-reports=yes
+reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
@@ -87,15 +171,177 @@
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (RP0004).
-comment=no
-
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
+[BASIC]
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=main,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
+
+# Regular expression matching correct function names
+function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
+
+# Regular expression matching correct variable names
+variable-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct constant names
+const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Regular expression matching correct attribute names
+attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
+
+# Regular expression matching correct argument names
+argument-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct class names
+class-rgx=^_?[A-Z][a-zA-Z0-9]*$
+
+# Regular expression matching correct module names
+module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
+
+# Regular expression matching correct method names
+method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=10
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
+# lines made too long by directives to pytype.
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=(?x)(
+ ^\s*(\#\ )?<?https?://\S+>?$|
+ ^\s*(from\s+\S+\s+)?import\s+.+$)
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=yes
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=
+
+# Maximum number of lines in a module
+max-module-lines=99999
+
+# String used as indentation unit. The internal Google style guide mandates 2
+# spaces. Google's externaly-published style guide says 4, consistent with
+# PEP 8.
+indent-string=' '
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=LF
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=TODO
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=yes
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging,absl.logging,tensorflow.io.logging
+
+
[SIMILARITIES]
# Minimum lines number of a similarity.
@@ -111,124 +357,6 @@
ignore-imports=no
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis
-ignored-modules=
-
-# List of classes names for which member attributes should not be checked
-# (useful for classes with attributes dynamically set).
-ignored-classes=SQLObject
-
-# When zope mode is activated, add a predefined set of Zope acquired attributes
-# to generated-members.
-zope=no
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E0201 when accessed. Python regular
-# expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
-[BASIC]
-
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter,input
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
-
-# Regular expression matching correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for function names
-function-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for variable names
-variable-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct constant names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression matching correct attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for attribute names
-attr-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for argument names
-argument-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
-
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression matching correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression matching correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for method names
-method-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=__.*__
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
@@ -246,98 +374,14 @@
spelling-store-unknown-words=no
-[FORMAT]
-
-# Maximum number of characters on a single line.
-max-line-length=100
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-# List of optional constructs for which whitespace checking is disabled
-no-space-check=trailing-comma,dict-separator
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
-# tab).
-indent-string=' '
-
-# Number of spaces of indent required inside a hanging or continued line.
-indent-after-paren=4
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=LF
-
-
-[LOGGING]
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format
-logging-modules=logging
-
-
-[VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=_$|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,_cb
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=5
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-max-branches=12
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=regsub,TERMIOS,Bastion,rexec
+deprecated-modules=regsub,
+ TERMIOS,
+ Bastion,
+ rexec,
+ sets
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
@@ -351,25 +395,46 @@
# not be disabled)
int-import-graph=
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant, absl
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
+defining-attr-methods=__init__,
+ __new__,
+ setUp
# List of member names, which should be excluded from the protected access
# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls,
+ class_
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
-overgeneral-exceptions=Exception
+overgeneral-exceptions=StandardError,
+ Exception,
+ BaseException
diff --git a/repack_bootimg.py b/repack_bootimg.py
new file mode 100755
index 0000000..c320018
--- /dev/null
+++ b/repack_bootimg.py
@@ -0,0 +1,362 @@
+#!/usr/bin/env python3
+#
+# Copyright 2021, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Repacks the boot image.
+
+Unpacks the boot image and the ramdisk inside, then add files into
+the ramdisk to repack the boot image.
+"""
+
+import argparse
+import datetime
+import enum
+import glob
+import os
+import shlex
+import shutil
+import subprocess
+import tempfile
+
+
+class TempFileManager:
+ """Manages temporary files and dirs."""
+
+ def __init__(self):
+ self._temp_files = []
+
+ def __del__(self):
+ """Removes temp dirs and files."""
+ for f in self._temp_files:
+ if os.path.isdir(f):
+ shutil.rmtree(f, ignore_errors=True)
+ else:
+ os.remove(f)
+
+ def make_temp_dir(self, prefix='tmp', suffix=''):
+ """Makes a temporary dir that will be cleaned up in the destructor.
+
+ Returns:
+ The absolute pathname of the new directory.
+ """
+ dir_name = tempfile.mkdtemp(prefix=prefix, suffix=suffix)
+ self._temp_files.append(dir_name)
+ return dir_name
+
+ def make_temp_file(self, prefix='tmp', suffix=''):
+ """Make a temp file that will be deleted in the destructor.
+
+ Returns:
+ The absolute pathname of the new file.
+ """
+ fd, file_name = tempfile.mkstemp(prefix=prefix, suffix=suffix)
+ os.close(fd)
+ self._temp_files.append(file_name)
+ return file_name
+
+
+class RamdiskFormat(enum.Enum):
+ """Enum class for different ramdisk compression formats."""
+ LZ4 = 1
+ GZIP = 2
+
+
+class BootImageType(enum.Enum):
+ """Enum class for different boot image types."""
+ BOOT_IMAGE = 1
+ VENDOR_BOOT_IMAGE = 2
+ SINGLE_RAMDISK_FRAGMENT = 3
+ MULTIPLE_RAMDISK_FRAGMENTS = 4
+
+
+class RamdiskImage:
+ """A class that supports packing/unpacking a ramdisk."""
+ def __init__(self, ramdisk_img, unpack=True):
+ self._ramdisk_img = ramdisk_img
+ self._ramdisk_format = None
+ self._ramdisk_dir = None
+ self._temp_file_manager = TempFileManager()
+
+ if unpack:
+ self._unpack_ramdisk()
+ else:
+ self._ramdisk_dir = self._temp_file_manager.make_temp_dir(
+ suffix='_new_ramdisk')
+
+ def _unpack_ramdisk(self):
+ """Unpacks the ramdisk."""
+ self._ramdisk_dir = self._temp_file_manager.make_temp_dir(
+ suffix='_' + os.path.basename(self._ramdisk_img))
+
+ # The compression format might be in 'lz4' or 'gzip' format,
+ # trying lz4 first.
+ for compression_type, compression_util in [
+ (RamdiskFormat.LZ4, 'lz4'),
+ (RamdiskFormat.GZIP, 'minigzip')]:
+
+ # Command arguments:
+ # -d: decompression
+ # -c: write to stdout
+ decompression_cmd = [
+ compression_util, '-d', '-c', self._ramdisk_img]
+
+ decompressed_result = subprocess.run(
+ decompression_cmd, check=False, capture_output=True)
+
+ if decompressed_result.returncode == 0:
+ self._ramdisk_format = compression_type
+ break
+
+ if self._ramdisk_format is not None:
+ # toybox cpio arguments:
+ # -i: extract files from stdin
+ # -d: create directories if needed
+ # -u: override existing files
+ subprocess.run(
+ ['toybox', 'cpio', '-idu'], check=True,
+ input=decompressed_result.stdout, cwd=self._ramdisk_dir)
+
+ print("=== Unpacked ramdisk: '{}' ===".format(
+ self._ramdisk_img))
+ else:
+ raise RuntimeError('Failed to decompress ramdisk.')
+
+ def repack_ramdisk(self, out_ramdisk_file):
+ """Repacks a ramdisk from self._ramdisk_dir.
+
+ Args:
+ out_ramdisk_file: the output ramdisk file to save.
+ """
+ compression_cmd = ['lz4', '-l', '-12', '--favor-decSpeed']
+ if self._ramdisk_format == RamdiskFormat.GZIP:
+ compression_cmd = ['minigzip']
+
+ print('Repacking ramdisk, which might take a few seconds ...')
+
+ mkbootfs_result = subprocess.run(
+ ['mkbootfs', self._ramdisk_dir], check=True, capture_output=True)
+
+ with open(out_ramdisk_file, 'w') as output_fd:
+ subprocess.run(compression_cmd, check=True,
+ input=mkbootfs_result.stdout, stdout=output_fd)
+
+ print("=== Repacked ramdisk: '{}' ===".format(out_ramdisk_file))
+
+ @property
+ def ramdisk_dir(self):
+ """Returns the internal ramdisk dir."""
+ return self._ramdisk_dir
+
+
+class BootImage:
+ """A class that supports packing/unpacking a boot.img and ramdisk."""
+
+ def __init__(self, bootimg):
+ self._bootimg = bootimg
+ self._bootimg_dir = None
+ self._bootimg_type = None
+ self._ramdisk = None
+ self._previous_mkbootimg_args = []
+ self._temp_file_manager = TempFileManager()
+
+ self._unpack_bootimg()
+
+ def _get_vendor_ramdisks(self):
+ """Returns a list of vendor ramdisks after unpack."""
+ return sorted(glob.glob(
+ os.path.join(self._bootimg_dir, 'vendor_ramdisk*')))
+
+ def _unpack_bootimg(self):
+ """Unpacks the boot.img and the ramdisk inside."""
+ self._bootimg_dir = self._temp_file_manager.make_temp_dir(
+ suffix='_' + os.path.basename(self._bootimg))
+
+ # Unpacks the boot.img first.
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', self._bootimg,
+ '--out', self._bootimg_dir,
+ '--format=mkbootimg',
+ ]
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ self._previous_mkbootimg_args = shlex.split(result.stdout)
+ print("=== Unpacked boot image: '{}' ===".format(self._bootimg))
+
+ # From the output dir, checks there is 'ramdisk' or 'vendor_ramdisk'.
+ ramdisk = os.path.join(self._bootimg_dir, 'ramdisk')
+ vendor_ramdisk = os.path.join(self._bootimg_dir, 'vendor_ramdisk')
+ vendor_ramdisks = self._get_vendor_ramdisks()
+ if os.path.exists(ramdisk):
+ self._ramdisk = RamdiskImage(ramdisk)
+ self._bootimg_type = BootImageType.BOOT_IMAGE
+ elif os.path.exists(vendor_ramdisk):
+ self._ramdisk = RamdiskImage(vendor_ramdisk)
+ self._bootimg_type = BootImageType.VENDOR_BOOT_IMAGE
+ elif len(vendor_ramdisks) == 1:
+ self._ramdisk = RamdiskImage(vendor_ramdisks[0])
+ self._bootimg_type = BootImageType.SINGLE_RAMDISK_FRAGMENT
+ elif len(vendor_ramdisks) > 1:
+ # Creates an empty RamdiskImage() below, without unpack.
+ # We'll then add files into this newly created ramdisk, then pack
+ # it with other vendor ramdisks together.
+ self._ramdisk = RamdiskImage(ramdisk_img=None, unpack=False)
+ self._bootimg_type = BootImageType.MULTIPLE_RAMDISK_FRAGMENTS
+ else:
+ raise RuntimeError('Both ramdisk and vendor_ramdisk do not exist.')
+
+ def repack_bootimg(self):
+ """Repacks the ramdisk and rebuild the boot.img"""
+
+ new_ramdisk = self._temp_file_manager.make_temp_file(
+ prefix='ramdisk-patched')
+ self._ramdisk.repack_ramdisk(new_ramdisk)
+
+ mkbootimg_cmd = ['mkbootimg']
+
+ # Uses previous mkbootimg args, e.g., --vendor_cmdline, --dtb_offset.
+ mkbootimg_cmd.extend(self._previous_mkbootimg_args)
+
+ ramdisk_option = ''
+ if self._bootimg_type == BootImageType.BOOT_IMAGE:
+ ramdisk_option = '--ramdisk'
+ mkbootimg_cmd.extend(['--output', self._bootimg])
+ elif self._bootimg_type == BootImageType.VENDOR_BOOT_IMAGE:
+ ramdisk_option = '--vendor_ramdisk'
+ mkbootimg_cmd.extend(['--vendor_boot', self._bootimg])
+ elif self._bootimg_type == BootImageType.SINGLE_RAMDISK_FRAGMENT:
+ ramdisk_option = '--vendor_ramdisk_fragment'
+ mkbootimg_cmd.extend(['--vendor_boot', self._bootimg])
+ elif self._bootimg_type == BootImageType.MULTIPLE_RAMDISK_FRAGMENTS:
+ mkbootimg_cmd.extend(['--ramdisk_type', 'PLATFORM'])
+ ramdisk_name = (
+ 'RAMDISK_' +
+ datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S'))
+ mkbootimg_cmd.extend(['--ramdisk_name', ramdisk_name])
+ mkbootimg_cmd.extend(['--vendor_ramdisk_fragment', new_ramdisk])
+ mkbootimg_cmd.extend(['--vendor_boot', self._bootimg])
+
+ if ramdisk_option and ramdisk_option not in mkbootimg_cmd:
+ raise RuntimeError("Failed to find '{}' from:\n {}".format(
+ ramdisk_option, shlex.join(mkbootimg_cmd)))
+ # Replaces the original ramdisk with the newly packed ramdisk.
+ if ramdisk_option:
+ ramdisk_index = mkbootimg_cmd.index(ramdisk_option) + 1
+ mkbootimg_cmd[ramdisk_index] = new_ramdisk
+
+ subprocess.check_call(mkbootimg_cmd)
+ print("=== Repacked boot image: '{}' ===".format(self._bootimg))
+
+ def add_files(self, src_dir, files):
+ """Copy files from the src_dir into current ramdisk.
+
+ Args:
+ src_dir: a source dir containing the files to copy from.
+ files: a list of files or src_file:dst_file pairs to copy from
+ src_dir to the current ramdisk.
+ """
+ # Creates missing parent dirs with 0o755.
+ original_mask = os.umask(0o022)
+ for f in files:
+ if ':' in f:
+ src_file = os.path.join(src_dir, f.split(':')[0])
+ dst_file = os.path.join(self.ramdisk_dir, f.split(':')[1])
+ else:
+ src_file = os.path.join(src_dir, f)
+ dst_file = os.path.join(self.ramdisk_dir, f)
+
+ dst_dir = os.path.dirname(dst_file)
+ if not os.path.exists(dst_dir):
+ print("Creating dir '{}'".format(dst_dir))
+ os.makedirs(dst_dir, 0o755)
+ print("Copying file '{}' into '{}'".format(src_file, dst_file))
+ shutil.copy2(src_file, dst_file)
+ os.umask(original_mask)
+
+ @property
+ def ramdisk_dir(self):
+ """Returns the internal ramdisk dir."""
+ return self._ramdisk.ramdisk_dir
+
+
+def _get_repack_usage():
+ return """Usage examples:
+
+ * --ramdisk_add
+
+ Specifies a list of files or src_file:dst_file pairs to copy from
+ --src_bootimg's ramdisk into --dst_bootimg's ramdisk.
+
+ $ repack_bootimg \\
+ --src_bootimg boot-debug-5.4.img --dst_bootimg vendor_boot-debug.img \\
+ --ramdisk_add first_stage_ramdisk/userdebug_plat_sepolicy.cil:userdebug_plat_sepolicy.cil
+
+ The above command copies '/first_stage_ramdisk/userdebug_plat_sepolicy.cil'
+ from --src_bootimg's ramdisk to '/userdebug_plat_sepolicy.cil' of
+ --dst_bootimg's ramdisk, then repacks the --dst_bootimg.
+
+ $ repack_bootimg \\
+ --src_bootimg boot-debug-5.4.img --dst_bootimg vendor_boot-debug.img \\
+ --ramdisk_add first_stage_ramdisk/userdebug_plat_sepolicy.cil
+
+ This is similar to the previous example, but the source file path and
+ destination file path are the same:
+ '/first_stage_ramdisk/userdebug_plat_sepolicy.cil'.
+
+ We can also combine both usage together with a list of copy instructions.
+ For example:
+
+ $ repack_bootimg \\
+ --src_bootimg boot-debug-5.4.img --dst_bootimg vendor_boot-debug.img \\
+ --ramdisk_add file1 file2:/subdir/file2 file3
+"""
+
+
+def _parse_args():
+ """Parse command-line options."""
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description='Repacks boot, recovery or vendor_boot image by importing'
+ 'ramdisk files from --src_bootimg to --dst_bootimg.',
+ epilog=_get_repack_usage(),
+ )
+
+ parser.add_argument(
+ '--src_bootimg', help='filename to source boot image',
+ type=str, required=True)
+ parser.add_argument(
+ '--dst_bootimg', help='filename to destination boot image',
+ type=str, required=True)
+ parser.add_argument(
+ '--ramdisk_add', nargs='+',
+ help='a list of files or src_file:dst_file pairs to add into '
+ 'the ramdisk',
+ default=['userdebug_plat_sepolicy.cil']
+ )
+
+ return parser.parse_args()
+
+
+def main():
+ """Parse arguments and repack boot image."""
+ args = _parse_args()
+ src_bootimg = BootImage(args.src_bootimg)
+ dst_bootimg = BootImage(args.dst_bootimg)
+ dst_bootimg.add_files(src_bootimg.ramdisk_dir, args.ramdisk_add)
+ dst_bootimg.repack_bootimg()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/data/testkey_rsa2048.pem b/tests/data/testkey_rsa2048.pem
new file mode 100644
index 0000000..867dcff
--- /dev/null
+++ b/tests/data/testkey_rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
+4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
+gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
+DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
+uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
+YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
+SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
+jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
+z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
+mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
+o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
+zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
+5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
+BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
+vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
+i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
+iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
+mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
+b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
+oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
+lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
+nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
+PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
+vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
+GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
+-----END RSA PRIVATE KEY-----
diff --git a/tests/mkbootimg_test.py b/tests/mkbootimg_test.py
new file mode 100644
index 0000000..ae5cf6b
--- /dev/null
+++ b/tests/mkbootimg_test.py
@@ -0,0 +1,735 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests mkbootimg and unpack_bootimg."""
+
+import filecmp
+import logging
+import os
+import random
+import shlex
+import subprocess
+import sys
+import tempfile
+import unittest
+
+BOOT_ARGS_OFFSET = 64
+BOOT_ARGS_SIZE = 512
+BOOT_EXTRA_ARGS_OFFSET = 608
+BOOT_EXTRA_ARGS_SIZE = 1024
+BOOT_V3_ARGS_OFFSET = 44
+VENDOR_BOOT_ARGS_OFFSET = 28
+VENDOR_BOOT_ARGS_SIZE = 2048
+
+BOOT_IMAGE_V4_SIGNATURE_SIZE = 4096
+
+TEST_KERNEL_CMDLINE = (
+ 'printk.devkmsg=on firmware_class.path=/vendor/etc/ init=/init '
+ 'kfence.sample_interval=500 loop.max_part=7 bootconfig'
+)
+
+
+def generate_test_file(pathname, size, seed=None):
+ """Generates a gibberish-filled test file and returns its pathname."""
+ random.seed(os.path.basename(pathname) if seed is None else seed)
+ with open(pathname, 'wb') as f:
+ f.write(random.randbytes(size))
+ return pathname
+
+
+def subsequence_of(list1, list2):
+ """Returns True if list1 is a subsequence of list2.
+
+ >>> subsequence_of([], [1])
+ True
+ >>> subsequence_of([2, 4], [1, 2, 3, 4])
+ True
+ >>> subsequence_of([1, 2, 2], [1, 2, 3])
+ False
+ """
+ if len(list1) == 0:
+ return True
+ if len(list2) == 0:
+ return False
+ if list1[0] == list2[0]:
+ return subsequence_of(list1[1:], list2[1:])
+ return subsequence_of(list1, list2[1:])
+
+
+class MkbootimgTest(unittest.TestCase):
+ """Tests the functionalities of mkbootimg and unpack_bootimg."""
+
+ def setUp(self):
+ # Saves the test executable directory so that relative path references
+ # to test dependencies don't rely on being manually run from the
+ # executable directory.
+ # With this, we can just open "./tests/data/testkey_rsa2048.pem" in the
+ # following tests with subprocess.run(..., cwd=self._exec_dir, ...).
+ self._exec_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
+
+ self._avbtool_path = os.path.join(self._exec_dir, 'avbtool')
+
+ # Set self.maxDiff to None to see full diff in assertion.
+ # C0103: invalid-name for maxDiff.
+ self.maxDiff = None # pylint: disable=C0103
+
+ def _test_boot_image_v4_signature(self, avbtool_path):
+ """Tests the boot_signature in boot.img v4."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '4',
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', TEST_KERNEL_CMDLINE,
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-01',
+ '--gki_signing_algorithm', 'SHA256_RSA2048',
+ '--gki_signing_key', './tests/data/testkey_rsa2048.pem',
+ '--gki_signing_signature_args',
+ '--prop foo:bar --prop gki:nice',
+ '--output', boot_img,
+ ]
+
+ if avbtool_path:
+ mkbootimg_cmds.extend(
+ ['--gki_signing_avbtool_path', avbtool_path])
+
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ ]
+
+ # cwd=self._exec_dir is required to read
+ # ./tests/data/testkey_rsa2048.pem for --gki_signing_key.
+ subprocess.run(mkbootimg_cmds, check=True, cwd=self._exec_dir)
+ subprocess.run(unpack_bootimg_cmds, check=True)
+
+ # Checks the content of the boot signature.
+ expected_boot_signature_info = (
+ 'Minimum libavb version: 1.0\n'
+ 'Header Block: 256 bytes\n'
+ 'Authentication Block: 320 bytes\n'
+ 'Auxiliary Block: 832 bytes\n'
+ 'Public key (sha1): '
+ 'cdbb77177f731920bbe0a0f94f84d9038ae0617d\n'
+ 'Algorithm: SHA256_RSA2048\n'
+ 'Rollback Index: 0\n'
+ 'Flags: 0\n'
+ 'Rollback Index Location: 0\n'
+ "Release String: 'avbtool 1.2.0'\n"
+ 'Descriptors:\n'
+ ' Hash descriptor:\n'
+ ' Image Size: 12288 bytes\n'
+ ' Hash Algorithm: sha256\n'
+ ' Partition Name: boot\n'
+ ' Salt: d00df00d\n'
+ ' Digest: '
+ 'cf3755630856f23ab70e501900050fee'
+ 'f30b633b3e82a9085a578617e344f9c7\n'
+ ' Flags: 0\n'
+ " Prop: foo -> 'bar'\n"
+ " Prop: gki -> 'nice'\n"
+ )
+
+ avbtool_info_cmds = [
+ # use avbtool_path if it is not None.
+ avbtool_path or 'avbtool',
+ 'info_image', '--image',
+ os.path.join(temp_out_dir, 'out', 'boot_signature')
+ ]
+ result = subprocess.run(avbtool_info_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+
+ self.assertEqual(result.stdout, expected_boot_signature_info)
+
+ def test_boot_image_v4_signature_without_avbtool_path(self):
+ """Boot signature generation without --gki_signing_avbtool_path."""
+ self._test_boot_image_v4_signature(avbtool_path=None)
+
+ def test_boot_image_v4_signature_with_avbtool_path(self):
+ """Boot signature generation with --gki_signing_avbtool_path."""
+ self._test_boot_image_v4_signature(avbtool_path=self._avbtool_path)
+
+ def test_boot_image_v4_signature_exceed_size(self):
+ """Tests the boot signature size exceeded in a boot image version 4."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '4',
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', TEST_KERNEL_CMDLINE,
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-01',
+ '--gki_signing_avbtool_path', self._avbtool_path,
+ '--gki_signing_algorithm', 'SHA256_RSA2048',
+ '--gki_signing_key', './tests/data/testkey_rsa2048.pem',
+ '--gki_signing_signature_args',
+ # Makes it exceed the signature max size.
+ '--prop foo:bar --prop gki:nice ' * 64,
+ '--output', boot_img,
+ ]
+
+ # cwd=self._exec_dir is required to read
+ # ./tests/data/testkey_rsa2048.pem for --gki_signing_key.
+ try:
+ subprocess.run(mkbootimg_cmds, check=True, capture_output=True,
+ cwd=self._exec_dir, encoding='utf-8')
+ self.fail('Exceeding signature size assertion is not raised')
+ except subprocess.CalledProcessError as e:
+ self.assertIn('ValueError: boot sigature size is > 4096',
+ e.stderr)
+
+ def test_boot_image_v4_signature_zeros(self):
+ """Tests no boot signature in a boot image version 4."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+
+ # The boot signature will be zeros if no
+ # --gki_signing_[algorithm|key] is provided.
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '4',
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', TEST_KERNEL_CMDLINE,
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-01',
+ '--output', boot_img,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ subprocess.run(unpack_bootimg_cmds, check=True)
+
+ boot_signature = os.path.join(
+ temp_out_dir, 'out', 'boot_signature')
+ with open(boot_signature) as f:
+ zeros = '\x00' * BOOT_IMAGE_V4_SIGNATURE_SIZE
+ self.assertEqual(f.read(), zeros)
+
+ def test_vendor_boot_v4(self):
+ """Tests vendor_boot version 4."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
+ dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
+ ramdisk1 = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk1'), 0x1000)
+ ramdisk2 = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk2'), 0x2000)
+ bootconfig = generate_test_file(
+ os.path.join(temp_out_dir, 'bootconfig'), 0x1000)
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '4',
+ '--vendor_boot', vendor_boot_img,
+ '--dtb', dtb,
+ '--vendor_ramdisk', ramdisk1,
+ '--ramdisk_type', 'PLATFORM',
+ '--ramdisk_name', 'RAMDISK1',
+ '--vendor_ramdisk_fragment', ramdisk1,
+ '--ramdisk_type', 'DLKM',
+ '--ramdisk_name', 'RAMDISK2',
+ '--board_id0', '0xC0FFEE',
+ '--board_id15', '0x15151515',
+ '--vendor_ramdisk_fragment', ramdisk2,
+ '--vendor_cmdline', TEST_KERNEL_CMDLINE,
+ '--vendor_bootconfig', bootconfig,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', vendor_boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ ]
+ expected_output = [
+ 'boot magic: VNDRBOOT',
+ 'vendor boot image header version: 4',
+ 'vendor ramdisk total size: 16384',
+ f'vendor command line args: {TEST_KERNEL_CMDLINE}',
+ 'dtb size: 4096',
+ 'vendor ramdisk table size: 324',
+ 'size: 4096', 'offset: 0', 'type: 0x1', 'name:',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ 'size: 4096', 'offset: 4096', 'type: 0x1', 'name: RAMDISK1',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ 'size: 8192', 'offset: 8192', 'type: 0x3', 'name: RAMDISK2',
+ '0x00c0ffee, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x00000000,',
+ '0x00000000, 0x00000000, 0x00000000, 0x15151515,',
+ 'vendor bootconfig size: 4096',
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ output = [line.strip() for line in result.stdout.splitlines()]
+ if not subsequence_of(expected_output, output):
+ msg = '\n'.join([
+ 'Unexpected unpack_bootimg output:',
+ 'Expected:',
+ ' ' + '\n '.join(expected_output),
+ '',
+ 'Actual:',
+ ' ' + '\n '.join(output),
+ ])
+ self.fail(msg)
+
+ def test_unpack_vendor_boot_image_v4(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
+ vendor_boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'vendor_boot.img.reconstructed')
+ dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
+ ramdisk1 = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk1'), 0x121212)
+ ramdisk2 = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk2'), 0x212121)
+ bootconfig = generate_test_file(
+ os.path.join(temp_out_dir, 'bootconfig'), 0x1000)
+
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '4',
+ '--vendor_boot', vendor_boot_img,
+ '--dtb', dtb,
+ '--vendor_ramdisk', ramdisk1,
+ '--ramdisk_type', 'PLATFORM',
+ '--ramdisk_name', 'RAMDISK1',
+ '--vendor_ramdisk_fragment', ramdisk1,
+ '--ramdisk_type', 'DLKM',
+ '--ramdisk_name', 'RAMDISK2',
+ '--board_id0', '0xC0FFEE',
+ '--board_id15', '0x15151515',
+ '--vendor_ramdisk_fragment', ramdisk2,
+ '--vendor_cmdline', TEST_KERNEL_CMDLINE,
+ '--vendor_bootconfig', bootconfig,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', vendor_boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--vendor_boot', vendor_boot_img_reconstructed,
+ ]
+ unpack_format_args = shlex.split(result.stdout)
+ mkbootimg_cmds.extend(unpack_format_args)
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(vendor_boot_img, vendor_boot_img_reconstructed),
+ 'reconstructed vendor_boot image differ from the original')
+
+ # Also check that -0, --null are as expected.
+ unpack_bootimg_cmds.append('--null')
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ unpack_format_null_args = result.stdout
+ self.assertEqual('\0'.join(unpack_format_args) + '\0',
+ unpack_format_null_args)
+
+ def test_unpack_vendor_boot_image_v3(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
+ vendor_boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'vendor_boot.img.reconstructed')
+ dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x121212)
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '3',
+ '--vendor_boot', vendor_boot_img,
+ '--vendor_ramdisk', ramdisk,
+ '--dtb', dtb,
+ '--vendor_cmdline', TEST_KERNEL_CMDLINE,
+ '--board', 'product_name',
+ '--base', '0x00000000',
+ '--dtb_offset', '0x01f00000',
+ '--kernel_offset', '0x00008000',
+ '--pagesize', '0x00001000',
+ '--ramdisk_offset', '0x01000000',
+ '--tags_offset', '0x00000100',
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', vendor_boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--vendor_boot', vendor_boot_img_reconstructed,
+ ]
+ mkbootimg_cmds.extend(shlex.split(result.stdout))
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(vendor_boot_img, vendor_boot_img_reconstructed),
+ 'reconstructed vendor_boot image differ from the original')
+
+ def test_unpack_boot_image_v3(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'boot.img.reconstructed')
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '3',
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', TEST_KERNEL_CMDLINE,
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-01',
+ '--output', boot_img,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--out', boot_img_reconstructed,
+ ]
+ mkbootimg_cmds.extend(shlex.split(result.stdout))
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(boot_img, boot_img_reconstructed),
+ 'reconstructed boot image differ from the original')
+
+ def test_unpack_boot_image_v2(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ # Output image path.
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'boot.img.reconstructed')
+ # Creates blank images first.
+ kernel = generate_test_file(
+ os.path.join(temp_out_dir, 'kernel'), 0x1000)
+ ramdisk = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
+ second = generate_test_file(
+ os.path.join(temp_out_dir, 'second'), 0x1000)
+ recovery_dtbo = generate_test_file(
+ os.path.join(temp_out_dir, 'recovery_dtbo'), 0x1000)
+ dtb = generate_test_file(
+ os.path.join(temp_out_dir, 'dtb'), 0x1000)
+
+ cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
+ extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
+
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '2',
+ '--base', '0x00000000',
+ '--kernel', kernel,
+ '--kernel_offset', '0x00008000',
+ '--ramdisk', ramdisk,
+ '--ramdisk_offset', '0x01000000',
+ '--second', second,
+ '--second_offset', '0x40000000',
+ '--recovery_dtbo', recovery_dtbo,
+ '--dtb', dtb,
+ '--dtb_offset', '0x01f00000',
+ '--tags_offset', '0x00000100',
+ '--pagesize', '0x00001000',
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-03',
+ '--board', 'boot_v2',
+ '--cmdline', cmdline + extra_cmdline,
+ '--output', boot_img,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--out', boot_img_reconstructed,
+ ]
+ mkbootimg_cmds.extend(shlex.split(result.stdout))
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(boot_img, boot_img_reconstructed),
+ 'reconstructed boot image differ from the original')
+
+ def test_unpack_boot_image_v1(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ # Output image path.
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'boot.img.reconstructed')
+ # Creates blank images first.
+ kernel = generate_test_file(
+ os.path.join(temp_out_dir, 'kernel'), 0x1000)
+ ramdisk = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
+ recovery_dtbo = generate_test_file(
+ os.path.join(temp_out_dir, 'recovery_dtbo'), 0x1000)
+
+ cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
+ extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
+
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '1',
+ '--base', '0x00000000',
+ '--kernel', kernel,
+ '--kernel_offset', '0x00008000',
+ '--ramdisk', ramdisk,
+ '--ramdisk_offset', '0x01000000',
+ '--recovery_dtbo', recovery_dtbo,
+ '--tags_offset', '0x00000100',
+ '--pagesize', '0x00001000',
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-03',
+ '--board', 'boot_v1',
+ '--cmdline', cmdline + extra_cmdline,
+ '--output', boot_img,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--out', boot_img_reconstructed,
+ ]
+ mkbootimg_cmds.extend(shlex.split(result.stdout))
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(boot_img, boot_img_reconstructed),
+ 'reconstructed boot image differ from the original')
+
+ def test_unpack_boot_image_v0(self):
+ """Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ # Output image path.
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ boot_img_reconstructed = os.path.join(
+ temp_out_dir, 'boot.img.reconstructed')
+ # Creates blank images first.
+ kernel = generate_test_file(
+ os.path.join(temp_out_dir, 'kernel'), 0x1000)
+ ramdisk = generate_test_file(
+ os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
+ second = generate_test_file(
+ os.path.join(temp_out_dir, 'second'), 0x1000)
+
+ cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
+ extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
+
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '0',
+ '--base', '0x00000000',
+ '--kernel', kernel,
+ '--kernel_offset', '0x00008000',
+ '--ramdisk', ramdisk,
+ '--ramdisk_offset', '0x01000000',
+ '--second', second,
+ '--second_offset', '0x40000000',
+ '--tags_offset', '0x00000100',
+ '--pagesize', '0x00001000',
+ '--os_version', '11.0.0',
+ '--os_patch_level', '2021-03',
+ '--board', 'boot_v0',
+ '--cmdline', cmdline + extra_cmdline,
+ '--output', boot_img,
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ ]
+ unpack_bootimg_cmds = [
+ 'unpack_bootimg',
+ '--boot_img', boot_img,
+ '--out', os.path.join(temp_out_dir, 'out'),
+ '--format=mkbootimg',
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ result = subprocess.run(unpack_bootimg_cmds, check=True,
+ capture_output=True, encoding='utf-8')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--out', boot_img_reconstructed,
+ ]
+ mkbootimg_cmds.extend(shlex.split(result.stdout))
+
+ subprocess.run(mkbootimg_cmds, check=True)
+ self.assertTrue(
+ filecmp.cmp(boot_img, boot_img_reconstructed),
+ 'reconstructed boot image differ from the original')
+
+ def test_boot_image_v2_cmdline_null_terminator(self):
+ """Tests that kernel commandline is null-terminated."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
+ extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '2',
+ '--dtb', dtb,
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', cmdline + extra_cmdline,
+ '--output', boot_img,
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+
+ with open(boot_img, 'rb') as f:
+ raw_boot_img = f.read()
+ raw_cmdline = raw_boot_img[BOOT_ARGS_OFFSET:][:BOOT_ARGS_SIZE]
+ raw_extra_cmdline = (raw_boot_img[BOOT_EXTRA_ARGS_OFFSET:]
+ [:BOOT_EXTRA_ARGS_SIZE])
+ self.assertEqual(raw_cmdline, cmdline.encode() + b'\x00')
+ self.assertEqual(raw_extra_cmdline,
+ extra_cmdline.encode() + b'\x00')
+
+ def test_boot_image_v3_cmdline_null_terminator(self):
+ """Tests that kernel commandline is null-terminated."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
+ 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ cmdline = BOOT_ARGS_SIZE * 'x' + (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
+ boot_img = os.path.join(temp_out_dir, 'boot.img')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '3',
+ '--kernel', kernel,
+ '--ramdisk', ramdisk,
+ '--cmdline', cmdline,
+ '--output', boot_img,
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+
+ with open(boot_img, 'rb') as f:
+ raw_boot_img = f.read()
+ raw_cmdline = (raw_boot_img[BOOT_V3_ARGS_OFFSET:]
+ [:BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE])
+ self.assertEqual(raw_cmdline, cmdline.encode() + b'\x00')
+
+ def test_vendor_boot_image_v3_cmdline_null_terminator(self):
+ """Tests that kernel commandline is null-terminated."""
+ with tempfile.TemporaryDirectory() as temp_out_dir:
+ dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
+ ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
+ 0x1000)
+ vendor_cmdline = (VENDOR_BOOT_ARGS_SIZE - 1) * 'x'
+ vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
+ mkbootimg_cmds = [
+ 'mkbootimg',
+ '--header_version', '3',
+ '--dtb', dtb,
+ '--vendor_ramdisk', ramdisk,
+ '--vendor_cmdline', vendor_cmdline,
+ '--vendor_boot', vendor_boot_img,
+ ]
+
+ subprocess.run(mkbootimg_cmds, check=True)
+
+ with open(vendor_boot_img, 'rb') as f:
+ raw_vendor_boot_img = f.read()
+ raw_vendor_cmdline = (raw_vendor_boot_img[VENDOR_BOOT_ARGS_OFFSET:]
+ [:VENDOR_BOOT_ARGS_SIZE])
+ self.assertEqual(raw_vendor_cmdline,
+ vendor_cmdline.encode() + b'\x00')
+
+
+# I don't know how, but we need both the logger configuration and verbosity
+# level > 2 to make atest work. And yes this line needs to be at the very top
+# level, not even in the "__main__" indentation block.
+logging.basicConfig(stream=sys.stdout)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/unpack_bootimg.py b/unpack_bootimg.py
index 83c2bbe..2b176e5 100755
--- a/unpack_bootimg.py
+++ b/unpack_bootimg.py
@@ -1,4 +1,5 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
+#
# Copyright 2018, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,18 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""unpacks the bootimage.
+"""Unpacks the boot image.
Extracts the kernel, ramdisk, second bootloader, dtb and recovery dtbo images.
"""
-from __future__ import print_function
-from argparse import ArgumentParser, FileType
+from argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter
from struct import unpack
import os
+import shlex
BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
-VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112
+VENDOR_RAMDISK_NAME_SIZE = 32
+VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16
+
def create_out_dir(dir_path):
"""creates a directory 'dir_path' if it does not exist"""
@@ -63,182 +66,480 @@
return '{:04d}-{:02d}'.format(y, m)
-def print_os_version_patch_level(value):
- os_version = value >> 11
- os_patch_level = value & ((1<<11) - 1)
- print('os version: %s' % format_os_version(os_version))
- print('os patch level: %s' % format_os_patch_level(os_patch_level))
+def decode_os_version_patch_level(os_version_patch_level):
+ """Returns a tuple of (os_version, os_patch_level)."""
+ os_version = os_version_patch_level >> 11
+ os_patch_level = os_version_patch_level & ((1<<11) - 1)
+ return (format_os_version(os_version),
+ format_os_patch_level(os_patch_level))
-def unpack_bootimage(args):
+class BootImageInfoFormatter:
+ """Formats the boot image info."""
+
+ def format_pretty_text(self):
+ lines = []
+ lines.append(f'boot magic: {self.boot_magic}')
+
+ if self.header_version < 3:
+ lines.append(f'kernel_size: {self.kernel_size}')
+ lines.append(
+ f'kernel load address: {self.kernel_load_address:#010x}')
+ lines.append(f'ramdisk size: {self.ramdisk_size}')
+ lines.append(
+ f'ramdisk load address: {self.ramdisk_load_address:#010x}')
+ lines.append(f'second bootloader size: {self.second_size}')
+ lines.append(
+ f'second bootloader load address: '
+ f'{self.second_load_address:#010x}')
+ lines.append(
+ f'kernel tags load address: {self.tags_load_address:#010x}')
+ lines.append(f'page size: {self.page_size}')
+ else:
+ lines.append(f'kernel_size: {self.kernel_size}')
+ lines.append(f'ramdisk size: {self.ramdisk_size}')
+
+ lines.append(f'os version: {self.os_version}')
+ lines.append(f'os patch level: {self.os_patch_level}')
+ lines.append(f'boot image header version: {self.header_version}')
+
+ if self.header_version < 3:
+ lines.append(f'product name: {self.product_name}')
+
+ lines.append(f'command line args: {self.cmdline}')
+
+ if self.header_version < 3:
+ lines.append(f'additional command line args: {self.extra_cmdline}')
+
+ if self.header_version in {1, 2}:
+ lines.append(f'recovery dtbo size: {self.recovery_dtbo_size}')
+ lines.append(
+ f'recovery dtbo offset: {self.recovery_dtbo_offset:#018x}')
+ lines.append(f'boot header size: {self.boot_header_size}')
+
+ if self.header_version == 2:
+ lines.append(f'dtb size: {self.dtb_size}')
+ lines.append(f'dtb address: {self.dtb_load_address:#018x}')
+
+ if self.header_version >= 4:
+ lines.append(
+ f'boot.img signature size: {self.boot_signature_size}')
+
+ return '\n'.join(lines)
+
+ def format_mkbootimg_argument(self):
+ args = []
+ args.extend(['--header_version', str(self.header_version)])
+ args.extend(['--os_version', self.os_version])
+ args.extend(['--os_patch_level', self.os_patch_level])
+
+ args.extend(['--kernel', os.path.join(self.image_dir, 'kernel')])
+ args.extend(['--ramdisk', os.path.join(self.image_dir, 'ramdisk')])
+
+ if self.header_version <= 2:
+ if self.second_size > 0:
+ args.extend(['--second',
+ os.path.join(self.image_dir, 'second')])
+ if self.recovery_dtbo_size > 0:
+ args.extend(['--recovery_dtbo',
+ os.path.join(self.image_dir, 'recovery_dtbo')])
+ if self.dtb_size > 0:
+ args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')])
+
+ args.extend(['--pagesize', f'{self.page_size:#010x}'])
+
+ # Kernel load address is base + kernel_offset in mkbootimg.py.
+ # However we don't know the value of 'base' when unpacking a boot
+ # image in this script, so we set 'base' to zero and 'kernel_offset'
+ # to the kernel load address, 'ramdisk_offset' to the ramdisk load
+ # address, ... etc.
+ args.extend(['--base', f'{0:#010x}'])
+ args.extend(['--kernel_offset',
+ f'{self.kernel_load_address:#010x}'])
+ args.extend(['--ramdisk_offset',
+ f'{self.ramdisk_load_address:#010x}'])
+ args.extend(['--second_offset',
+ f'{self.second_load_address:#010x}'])
+ args.extend(['--tags_offset', f'{self.tags_load_address:#010x}'])
+
+ # dtb is added in boot image v2, and is absent in v1 or v0.
+ if self.header_version == 2:
+ # dtb_offset is uint64_t.
+ args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}'])
+
+ args.extend(['--board', self.product_name])
+ args.extend(['--cmdline', self.cmdline + self.extra_cmdline])
+ else:
+ args.extend(['--cmdline', self.cmdline])
+
+ return args
+
+
+def unpack_boot_image(args):
"""extracts kernel, ramdisk, second bootloader and recovery dtbo"""
+ info = BootImageInfoFormatter()
+ info.boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
+
kernel_ramdisk_second_info = unpack('9I', args.boot_img.read(9 * 4))
- version = kernel_ramdisk_second_info[8]
- if version < 3:
- print('kernel_size: %s' % kernel_ramdisk_second_info[0])
- print('kernel load address: %#x' % kernel_ramdisk_second_info[1])
- print('ramdisk size: %s' % kernel_ramdisk_second_info[2])
- print('ramdisk load address: %#x' % kernel_ramdisk_second_info[3])
- print('second bootloader size: %s' % kernel_ramdisk_second_info[4])
- print('second bootloader load address: %#x' % kernel_ramdisk_second_info[5])
- print('kernel tags load address: %#x' % kernel_ramdisk_second_info[6])
- print('page size: %s' % kernel_ramdisk_second_info[7])
- print_os_version_patch_level(unpack('I', args.boot_img.read(1 * 4))[0])
+ # header_version is always at [8] regardless of the value of header_version.
+ info.header_version = kernel_ramdisk_second_info[8]
+
+ if info.header_version < 3:
+ info.kernel_size = kernel_ramdisk_second_info[0]
+ info.kernel_load_address = kernel_ramdisk_second_info[1]
+ info.ramdisk_size = kernel_ramdisk_second_info[2]
+ info.ramdisk_load_address = kernel_ramdisk_second_info[3]
+ info.second_size = kernel_ramdisk_second_info[4]
+ info.second_load_address = kernel_ramdisk_second_info[5]
+ info.tags_load_address = kernel_ramdisk_second_info[6]
+ info.page_size = kernel_ramdisk_second_info[7]
+ os_version_patch_level = unpack('I', args.boot_img.read(1 * 4))[0]
else:
- print('kernel_size: %s' % kernel_ramdisk_second_info[0])
- print('ramdisk size: %s' % kernel_ramdisk_second_info[1])
- print_os_version_patch_level(kernel_ramdisk_second_info[2])
+ info.kernel_size = kernel_ramdisk_second_info[0]
+ info.ramdisk_size = kernel_ramdisk_second_info[1]
+ os_version_patch_level = kernel_ramdisk_second_info[2]
+ info.second_size = 0
+ info.page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE
- print('boot image header version: %s' % version)
+ info.os_version, info.os_patch_level = decode_os_version_patch_level(
+ os_version_patch_level)
- if version < 3:
- product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode())
- print('product name: %s' % product_name)
- cmdline = cstr(unpack('512s', args.boot_img.read(512))[0].decode())
- print('command line args: %s' % cmdline)
- else:
- cmdline = cstr(unpack('1536s', args.boot_img.read(1536))[0].decode())
- print('command line args: %s' % cmdline)
-
- if version < 3:
+ if info.header_version < 3:
+ info.product_name = cstr(unpack('16s',
+ args.boot_img.read(16))[0].decode())
+ info.cmdline = cstr(unpack('512s', args.boot_img.read(512))[0].decode())
args.boot_img.read(32) # ignore SHA
-
- if version < 3:
- extra_cmdline = cstr(unpack('1024s',
- args.boot_img.read(1024))[0].decode())
- print('additional command line args: %s' % extra_cmdline)
-
- if version < 3:
- kernel_size = kernel_ramdisk_second_info[0]
- ramdisk_size = kernel_ramdisk_second_info[2]
- second_size = kernel_ramdisk_second_info[4]
- page_size = kernel_ramdisk_second_info[7]
+ info.extra_cmdline = cstr(unpack('1024s',
+ args.boot_img.read(1024))[0].decode())
else:
- kernel_size = kernel_ramdisk_second_info[0]
- ramdisk_size = kernel_ramdisk_second_info[1]
- second_size = 0
- page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE
+ info.cmdline = cstr(unpack('1536s',
+ args.boot_img.read(1536))[0].decode())
- if 0 < version < 3:
- recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0]
- print('recovery dtbo size: %s' % recovery_dtbo_size)
- recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0]
- print('recovery dtbo offset: %#x' % recovery_dtbo_offset)
- boot_header_size = unpack('I', args.boot_img.read(4))[0]
- print('boot header size: %s' % boot_header_size)
+ if info.header_version in {1, 2}:
+ info.recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0]
+ info.recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0]
+ info.boot_header_size = unpack('I', args.boot_img.read(4))[0]
else:
- recovery_dtbo_size = 0
- if 1 < version < 3:
- dtb_size = unpack('I', args.boot_img.read(4))[0]
- print('dtb size: %s' % dtb_size)
- dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
- print('dtb address: %#x' % dtb_load_address)
- else:
- dtb_size = 0
+ info.recovery_dtbo_size = 0
+ if info.header_version == 2:
+ info.dtb_size = unpack('I', args.boot_img.read(4))[0]
+ info.dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
+ else:
+ info.dtb_size = 0
+ info.dtb_load_address = 0
+
+ if info.header_version >= 4:
+ info.boot_signature_size = unpack('I', args.boot_img.read(4))[0]
+ else:
+ info.boot_signature_size = 0
# The first page contains the boot header
num_header_pages = 1
- num_kernel_pages = get_number_of_pages(kernel_size, page_size)
- kernel_offset = page_size * num_header_pages # header occupies a page
- image_info_list = [(kernel_offset, kernel_size, 'kernel')]
+ # Convenient shorthand.
+ page_size = info.page_size
- num_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size)
+ num_kernel_pages = get_number_of_pages(info.kernel_size, page_size)
+ kernel_offset = page_size * num_header_pages # header occupies a page
+ image_info_list = [(kernel_offset, info.kernel_size, 'kernel')]
+
+ num_ramdisk_pages = get_number_of_pages(info.ramdisk_size, page_size)
ramdisk_offset = page_size * (num_header_pages + num_kernel_pages
) # header + kernel
- image_info_list.append((ramdisk_offset, ramdisk_size, 'ramdisk'))
+ image_info_list.append((ramdisk_offset, info.ramdisk_size, 'ramdisk'))
- if second_size > 0:
+ if info.second_size > 0:
second_offset = page_size * (
num_header_pages + num_kernel_pages + num_ramdisk_pages
) # header + kernel + ramdisk
- image_info_list.append((second_offset, second_size, 'second'))
+ image_info_list.append((second_offset, info.second_size, 'second'))
- if recovery_dtbo_size > 0:
- image_info_list.append((recovery_dtbo_offset, recovery_dtbo_size,
+ if info.recovery_dtbo_size > 0:
+ image_info_list.append((info.recovery_dtbo_offset,
+ info.recovery_dtbo_size,
'recovery_dtbo'))
- if dtb_size > 0:
- num_second_pages = get_number_of_pages(second_size, page_size)
- num_recovery_dtbo_pages = get_number_of_pages(recovery_dtbo_size, page_size)
+ if info.dtb_size > 0:
+ num_second_pages = get_number_of_pages(info.second_size, page_size)
+ num_recovery_dtbo_pages = get_number_of_pages(
+ info.recovery_dtbo_size, page_size)
dtb_offset = page_size * (
- num_header_pages + num_kernel_pages + num_ramdisk_pages + num_second_pages +
- num_recovery_dtbo_pages
- )
+ num_header_pages + num_kernel_pages + num_ramdisk_pages +
+ num_second_pages + num_recovery_dtbo_pages)
- image_info_list.append((dtb_offset, dtb_size, 'dtb'))
+ image_info_list.append((dtb_offset, info.dtb_size, 'dtb'))
- for image_info in image_info_list:
- extract_image(image_info[0], image_info[1], args.boot_img,
- os.path.join(args.out, image_info[2]))
+ if info.boot_signature_size > 0:
+ # boot signature only exists in boot.img version >= v4.
+ # There are only kernel and ramdisk pages before the signature.
+ boot_signature_offset = page_size * (
+ num_header_pages + num_kernel_pages + num_ramdisk_pages)
+
+ image_info_list.append((boot_signature_offset, info.boot_signature_size,
+ 'boot_signature'))
+
+ create_out_dir(args.out)
+ for offset, size, name in image_info_list:
+ extract_image(offset, size, args.boot_img, os.path.join(args.out, name))
+ info.image_dir = args.out
+
+ return info
-def unpack_vendor_bootimage(args):
- kernel_ramdisk_info = unpack('5I', args.boot_img.read(5 * 4))
- print('vendor boot image header version: %s' % kernel_ramdisk_info[0])
- print('kernel load address: %#x' % kernel_ramdisk_info[2])
- print('ramdisk load address: %#x' % kernel_ramdisk_info[3])
- print('vendor ramdisk size: %s' % kernel_ramdisk_info[4])
+class VendorBootImageInfoFormatter:
+ """Formats the vendor_boot image info."""
- cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode())
- print('vendor command line args: %s' % cmdline)
+ def format_pretty_text(self):
+ lines = []
+ lines.append(f'boot magic: {self.boot_magic}')
+ lines.append(f'vendor boot image header version: {self.header_version}')
+ lines.append(f'page size: {self.page_size:#010x}')
+ lines.append(f'kernel load address: {self.kernel_load_address:#010x}')
+ lines.append(f'ramdisk load address: {self.ramdisk_load_address:#010x}')
+ if self.header_version > 3:
+ lines.append(
+ f'vendor ramdisk total size: {self.vendor_ramdisk_size}')
+ else:
+ lines.append(f'vendor ramdisk size: {self.vendor_ramdisk_size}')
+ lines.append(f'vendor command line args: {self.cmdline}')
+ lines.append(
+ f'kernel tags load address: {self.tags_load_address:#010x}')
+ lines.append(f'product name: {self.product_name}')
+ lines.append(f'vendor boot image header size: {self.header_size}')
+ lines.append(f'dtb size: {self.dtb_size}')
+ lines.append(f'dtb address: {self.dtb_load_address:#018x}')
+ if self.header_version > 3:
+ lines.append(
+ f'vendor ramdisk table size: {self.vendor_ramdisk_table_size}')
+ lines.append('vendor ramdisk table: [')
+ indent = lambda level: ' ' * 4 * level
+ for entry in self.vendor_ramdisk_table:
+ (output_ramdisk_name, ramdisk_size, ramdisk_offset,
+ ramdisk_type, ramdisk_name, board_id) = entry
+ lines.append(indent(1) + f'{output_ramdisk_name}: ''{')
+ lines.append(indent(2) + f'size: {ramdisk_size}')
+ lines.append(indent(2) + f'offset: {ramdisk_offset}')
+ lines.append(indent(2) + f'type: {ramdisk_type:#x}')
+ lines.append(indent(2) + f'name: {ramdisk_name}')
+ lines.append(indent(2) + 'board_id: [')
+ stride = 4
+ for row_idx in range(0, len(board_id), stride):
+ row = board_id[row_idx:row_idx + stride]
+ lines.append(
+ indent(3) + ' '.join(f'{e:#010x},' for e in row))
+ lines.append(indent(2) + ']')
+ lines.append(indent(1) + '}')
+ lines.append(']')
+ lines.append(
+ f'vendor bootconfig size: {self.vendor_bootconfig_size}')
- tags_load_address = unpack('I', args.boot_img.read(1 * 4))[0]
- print('kernel tags load address: %#x' % tags_load_address)
+ return '\n'.join(lines)
- product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode())
- print('product name: %s' % product_name)
+ def format_mkbootimg_argument(self):
+ args = []
+ args.extend(['--header_version', str(self.header_version)])
+ args.extend(['--pagesize', f'{self.page_size:#010x}'])
+ args.extend(['--base', f'{0:#010x}'])
+ args.extend(['--kernel_offset', f'{self.kernel_load_address:#010x}'])
+ args.extend(['--ramdisk_offset', f'{self.ramdisk_load_address:#010x}'])
+ args.extend(['--tags_offset', f'{self.tags_load_address:#010x}'])
+ args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}'])
+ args.extend(['--vendor_cmdline', self.cmdline])
+ args.extend(['--board', self.product_name])
- dtb_size = unpack('2I', args.boot_img.read(2 * 4))[1]
- print('dtb size: %s' % dtb_size)
- dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
- print('dtb address: %#x' % dtb_load_address)
+ args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')])
- ramdisk_size = kernel_ramdisk_info[4]
- page_size = kernel_ramdisk_info[1]
+ if self.header_version > 3:
+ args.extend(['--vendor_bootconfig',
+ os.path.join(self.image_dir, 'bootconfig')])
+ for entry in self.vendor_ramdisk_table:
+ (output_ramdisk_name, _, _, ramdisk_type,
+ ramdisk_name, board_id) = entry
+ args.extend(['--ramdisk_type', str(ramdisk_type)])
+ args.extend(['--ramdisk_name', ramdisk_name])
+ for idx, e in enumerate(board_id):
+ if e:
+ args.extend([f'--board_id{idx}', f'{e:#010x}'])
+ vendor_ramdisk_path = os.path.join(
+ self.image_dir, output_ramdisk_name)
+ args.extend(['--vendor_ramdisk_fragment', vendor_ramdisk_path])
+ else:
+ args.extend(['--vendor_ramdisk',
+ os.path.join(self.image_dir, 'vendor_ramdisk')])
+
+ return args
+
+
+def unpack_vendor_boot_image(args):
+ info = VendorBootImageInfoFormatter()
+ info.boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
+ info.header_version = unpack('I', args.boot_img.read(4))[0]
+ info.page_size = unpack('I', args.boot_img.read(4))[0]
+ info.kernel_load_address = unpack('I', args.boot_img.read(4))[0]
+ info.ramdisk_load_address = unpack('I', args.boot_img.read(4))[0]
+ info.vendor_ramdisk_size = unpack('I', args.boot_img.read(4))[0]
+ info.cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode())
+ info.tags_load_address = unpack('I', args.boot_img.read(4))[0]
+ info.product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode())
+ info.header_size = unpack('I', args.boot_img.read(4))[0]
+ info.dtb_size = unpack('I', args.boot_img.read(4))[0]
+ info.dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
+
+ # Convenient shorthand.
+ page_size = info.page_size
# The first pages contain the boot header
- num_boot_header_pages = get_number_of_pages(VENDOR_BOOT_IMAGE_HEADER_V3_SIZE, page_size)
- num_boot_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size)
- ramdisk_offset = page_size * num_boot_header_pages
- image_info_list = [(ramdisk_offset, ramdisk_size, 'vendor_ramdisk')]
+ num_boot_header_pages = get_number_of_pages(info.header_size, page_size)
+ num_boot_ramdisk_pages = get_number_of_pages(
+ info.vendor_ramdisk_size, page_size)
+ num_boot_dtb_pages = get_number_of_pages(info.dtb_size, page_size)
+
+ ramdisk_offset_base = page_size * num_boot_header_pages
+ image_info_list = []
+
+ if info.header_version > 3:
+ info.vendor_ramdisk_table_size = unpack('I', args.boot_img.read(4))[0]
+ vendor_ramdisk_table_entry_num = unpack('I', args.boot_img.read(4))[0]
+ vendor_ramdisk_table_entry_size = unpack('I', args.boot_img.read(4))[0]
+ info.vendor_bootconfig_size = unpack('I', args.boot_img.read(4))[0]
+ num_vendor_ramdisk_table_pages = get_number_of_pages(
+ info.vendor_ramdisk_table_size, page_size)
+ vendor_ramdisk_table_offset = page_size * (
+ num_boot_header_pages + num_boot_ramdisk_pages + num_boot_dtb_pages)
+
+ vendor_ramdisk_table = []
+ vendor_ramdisk_symlinks = []
+ for idx in range(vendor_ramdisk_table_entry_num):
+ entry_offset = vendor_ramdisk_table_offset + (
+ vendor_ramdisk_table_entry_size * idx)
+ args.boot_img.seek(entry_offset)
+ ramdisk_size = unpack('I', args.boot_img.read(4))[0]
+ ramdisk_offset = unpack('I', args.boot_img.read(4))[0]
+ ramdisk_type = unpack('I', args.boot_img.read(4))[0]
+ ramdisk_name = cstr(unpack(
+ f'{VENDOR_RAMDISK_NAME_SIZE}s',
+ args.boot_img.read(VENDOR_RAMDISK_NAME_SIZE))[0].decode())
+ board_id = unpack(
+ f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I',
+ args.boot_img.read(
+ 4 * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE))
+ output_ramdisk_name = f'vendor_ramdisk{idx:02}'
+
+ image_info_list.append((ramdisk_offset_base + ramdisk_offset,
+ ramdisk_size, output_ramdisk_name))
+ vendor_ramdisk_symlinks.append((output_ramdisk_name, ramdisk_name))
+ vendor_ramdisk_table.append(
+ (output_ramdisk_name, ramdisk_size, ramdisk_offset,
+ ramdisk_type, ramdisk_name, board_id))
+
+ info.vendor_ramdisk_table = vendor_ramdisk_table
+
+ bootconfig_offset = page_size * (num_boot_header_pages
+ + num_boot_ramdisk_pages + num_boot_dtb_pages
+ + num_vendor_ramdisk_table_pages)
+ image_info_list.append((bootconfig_offset, info.vendor_bootconfig_size,
+ 'bootconfig'))
+ else:
+ image_info_list.append(
+ (ramdisk_offset_base, info.vendor_ramdisk_size, 'vendor_ramdisk'))
dtb_offset = page_size * (num_boot_header_pages + num_boot_ramdisk_pages
) # header + vendor_ramdisk
- image_info_list.append((dtb_offset, dtb_size, 'dtb'))
+ image_info_list.append((dtb_offset, info.dtb_size, 'dtb'))
- for image_info in image_info_list:
- extract_image(image_info[0], image_info[1], args.boot_img,
- os.path.join(args.out, image_info[2]))
+ create_out_dir(args.out)
+ for offset, size, name in image_info_list:
+ extract_image(offset, size, args.boot_img, os.path.join(args.out, name))
+ info.image_dir = args.out
+
+ if info.header_version > 3:
+ vendor_ramdisk_by_name_dir = os.path.join(
+ args.out, 'vendor-ramdisk-by-name')
+ create_out_dir(vendor_ramdisk_by_name_dir)
+ for src, dst in vendor_ramdisk_symlinks:
+ src_pathname = os.path.join('..', src)
+ dst_pathname = os.path.join(
+ vendor_ramdisk_by_name_dir, f'ramdisk_{dst}')
+ if os.path.lexists(dst_pathname):
+ os.remove(dst_pathname)
+ os.symlink(src_pathname, dst_pathname)
+
+ return info
def unpack_image(args):
boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
- print('boot_magic: %s' % boot_magic)
- if boot_magic == "ANDROID!":
- unpack_bootimage(args)
- elif boot_magic == "VNDRBOOT":
- unpack_vendor_bootimage(args)
+ args.boot_img.seek(0)
+ if boot_magic == 'ANDROID!':
+ info = unpack_boot_image(args)
+ elif boot_magic == 'VNDRBOOT':
+ info = unpack_vendor_boot_image(args)
+ else:
+ raise ValueError(f'Not an Android boot image, magic: {boot_magic}')
+
+ if args.format == 'mkbootimg':
+ mkbootimg_args = info.format_mkbootimg_argument()
+ if args.null:
+ print('\0'.join(mkbootimg_args) + '\0', end='')
+ else:
+ print(shlex.join(mkbootimg_args))
+ else:
+ print(info.format_pretty_text())
+
+
+def get_unpack_usage():
+ return """Output format:
+
+ * info
+
+ Pretty-printed info-rich text format suitable for human inspection.
+
+ * mkbootimg
+
+ Output shell-escaped (quoted) argument strings that can be used to
+ reconstruct the boot image. For example:
+
+ $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg |
+ tee mkbootimg_args
+ $ sh -c "mkbootimg $(cat mkbootimg_args) --vendor_boot repacked.img"
+
+ vendor_boot.img and repacked.img would be equivalent.
+
+ If the -0 option is specified, output unescaped null-terminated argument
+ strings that are suitable to be parsed by a shell script (xargs -0 format):
+
+ $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg \\
+ -0 | tee mkbootimg_args
+ $ declare -a MKBOOTIMG_ARGS=()
+ $ while IFS= read -r -d '' ARG; do
+ MKBOOTIMG_ARGS+=("${ARG}")
+ done <mkbootimg_args
+ $ mkbootimg "${MKBOOTIMG_ARGS[@]}" --vendor_boot repacked.img
+"""
def parse_cmdline():
"""parse command line arguments"""
parser = ArgumentParser(
- description='Unpacks boot.img/recovery.img, extracts the kernel,'
- 'ramdisk, second bootloader, recovery dtbo and dtb')
- parser.add_argument(
- '--boot_img',
- help='path to boot image',
- type=FileType('rb'),
- required=True)
- parser.add_argument('--out', help='path to out binaries', default='out')
+ formatter_class=RawDescriptionHelpFormatter,
+ description='Unpacks boot, recovery or vendor_boot image.',
+ epilog=get_unpack_usage(),
+ )
+ parser.add_argument('--boot_img', type=FileType('rb'), required=True,
+ help='path to the boot, recovery or vendor_boot image')
+ parser.add_argument('--out', default='out',
+ help='output directory of the unpacked images')
+ parser.add_argument('--format', choices=['info', 'mkbootimg'],
+ default='info',
+ help='text output format (default: info)')
+ parser.add_argument('-0', '--null', action='store_true',
+ help='output null-terminated argument strings')
return parser.parse_args()
def main():
"""parse arguments and unpack boot image"""
args = parse_cmdline()
- create_out_dir(args.out)
unpack_image(args)