From 91a6f3b4e7f87a52d4dbc69e06c8254034499b21 Mon Sep 17 00:00:00 2001 From: Taylor Braun-Jones Date: Fri, 12 Apr 2024 15:35:10 -0400 Subject: [PATCH 1/3] fix pip install hacking instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8251c02..406d2db 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ python3 -m venv .venv Next install the project in editable mode with development dependencies: ```bash -pip install -m .[dev] +pip install -e .[dev] ``` Finally, to run tests use `unittest`: From cc4cb1006eea0f9650d7375b29b127eff3c9d7ec Mon Sep 17 00:00:00 2001 From: Taylor Braun-Jones Date: Fri, 12 Apr 2024 15:43:18 -0400 Subject: [PATCH 2/3] add `copy --format simg` option for converting to an Android Sparse Image file Android Sparse Image files are supported by the U-Boot `mmc swrite ...` command which greatly speeds up the bootstrapping process of an unprogrammed board. --- src/bmaptool/BmapCopy.py | 256 ++++++++++++++++++++++++++++++++++++++- src/bmaptool/CLI.py | 11 +- 2 files changed, 265 insertions(+), 2 deletions(-) diff --git a/src/bmaptool/BmapCopy.py b/src/bmaptool/BmapCopy.py index 15113a8..3982419 100644 --- a/src/bmaptool/BmapCopy.py +++ b/src/bmaptool/BmapCopy.py @@ -59,6 +59,7 @@ import os import re import stat +import struct import sys import hashlib import logging @@ -259,7 +260,7 @@ def __init__(self, image, dest, bmap=None, image_size=None): self._f_image = image self._image_path = image.name - self._f_dest = dest + self._f_dest: BinaryIO = dest self._dest_path = dest.name st_data = os.fstat(self._f_dest.fileno()) self._dest_is_regfile = stat.S_ISREG(st_data.st_mode) @@ -792,6 +793,259 @@ def sync(self): ) +# See: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libsparse/sparse_format.h +SPARSE_HEADER_MAGIC = 0xed26ff3a +CHUNK_TYPE_RAW = 0xCAC1 +CHUNK_TYPE_FILL = 0xCAC2 +CHUNK_TYPE_DONT_CARE = 0xCAC3 +CHUNK_TYPE_CRC32 = 0xCAC4 + +MAJOR_VERSION = 1 +MINOR_VERSION = 0 +FILE_HEADER_SIZE = 28 # always for version 1.0 +CHUNK_HEADER_SIZE = 12 # always for version 1.0 +CHECKSUM_DONT_CARE = 0 +CHUNK_HEADER_RESERVED1 = 0 + + +class BmapAndroidSparseImageCopy(BmapCopy): + def __init__(self, image, dest, bmap=None, image_size=None): + super().__init__(image, dest, bmap, image_size) + + self._sparse_image_chunk_cnt = 0 + self._batch_bytes = 100 * 2**20 + self._batch_blocks = self._batch_bytes // self.block_size + self._offset = 0 + + def _print_chunk_info(self, header): + blk_sz = self.block_size + showhash = False + + unpacked_header = struct.unpack("<2H2I", header) + chunk_type = unpacked_header[0] + chunk_sz = unpacked_header[2] + total_sz = unpacked_header[3] + data_sz = total_sz - 12 + curhash = "" + curtype = "" + + curpos = self._f_dest.tell() + print("%4u %10u %10u %7u %7u" % (self._sparse_image_chunk_cnt + 1, curpos, data_sz, self._offset, chunk_sz), + end=" ") + + if chunk_type == 0xCAC1: + if data_sz != (chunk_sz * blk_sz): + print("Raw chunk input size (%u) does not match output size (%u)" + % (data_sz, chunk_sz * blk_sz)) + return + else: + curtype = "Raw data" + + elif chunk_type == 0xCAC2: + if data_sz != 4: + print("Fill chunk should have 4 bytes of fill, but this has %u" + % (data_sz)) + return + else: + fill_bin = FH.read(4) + fill = struct.unpack(" (end - start) * self.block_size + + # Synchronize the destination file if we reached the watermark + if self._dest_fsync_watermark: + if blocks_written >= fsync_last + self._dest_fsync_watermark: + fsync_last = blocks_written + self.sync() + + blocks_since_previous_chunk = start - prev_end - 1 + + if blocks_since_previous_chunk < 0: + raise Error("Out-of-order bmap blocks are not supported") + + if blocks_since_previous_chunk > 0: + self._write_dont_care_chunk(blocks_since_previous_chunk) + + self._write_raw_chunk(buf) + + self._batch_queue.task_done() + blocks_written += batch_block_count + bytes_written += len(buf) + + prev_end = end + + self._update_progress(blocks_written) + + if prev_end < self.blocks_cnt: + self._write_dont_care_chunk(self.blocks_cnt - prev_end - 1) + + if not self.image_size: + # The image size was unknown up until now, set it + self._set_image_size(bytes_written) + + # This is just a sanity check - we should have written exactly + # 'mapped_cnt' blocks. + if blocks_written != self.mapped_cnt: + raise Error( + "wrote %u blocks from image '%s' to '%s', but should " + "have %u - bmap file '%s' does not belong to this " + "image" + % ( + blocks_written, + self._image_path, + self._dest_path, + self.mapped_cnt, + self._bmap_path, + ) + ) + + print(f"sparse_image_chunk_cnt: {self._sparse_image_chunk_cnt}") + + file_header = struct.pack(" Date: Fri, 12 Apr 2024 15:48:24 -0400 Subject: [PATCH 3/3] android simg cleanup --- src/bmaptool/BmapCopy.py | 88 ++++------------------------------------ 1 file changed, 9 insertions(+), 79 deletions(-) diff --git a/src/bmaptool/BmapCopy.py b/src/bmaptool/BmapCopy.py index 3982419..1a006dd 100644 --- a/src/bmaptool/BmapCopy.py +++ b/src/bmaptool/BmapCopy.py @@ -260,7 +260,7 @@ def __init__(self, image, dest, bmap=None, image_size=None): self._f_image = image self._image_path = image.name - self._f_dest: BinaryIO = dest + self._f_dest = dest self._dest_path = dest.name st_data = os.fstat(self._f_dest.fileno()) self._dest_is_regfile = stat.S_ISREG(st_data.st_mode) @@ -793,7 +793,6 @@ def sync(self): ) -# See: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libsparse/sparse_format.h SPARSE_HEADER_MAGIC = 0xed26ff3a CHUNK_TYPE_RAW = 0xCAC1 CHUNK_TYPE_FILL = 0xCAC2 @@ -809,82 +808,25 @@ def sync(self): class BmapAndroidSparseImageCopy(BmapCopy): + """ + Copies the source image to an Android Sparse Image file + + See: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libsparse/sparse_format.h + """ def __init__(self, image, dest, bmap=None, image_size=None): super().__init__(image, dest, bmap, image_size) self._sparse_image_chunk_cnt = 0 + + # Use bigger batch sizes to avoid so many raw chunks in the output + # Sparse Image file self._batch_bytes = 100 * 2**20 self._batch_blocks = self._batch_bytes // self.block_size - self._offset = 0 - - def _print_chunk_info(self, header): - blk_sz = self.block_size - showhash = False - - unpacked_header = struct.unpack("<2H2I", header) - chunk_type = unpacked_header[0] - chunk_sz = unpacked_header[2] - total_sz = unpacked_header[3] - data_sz = total_sz - 12 - curhash = "" - curtype = "" - - curpos = self._f_dest.tell() - print("%4u %10u %10u %7u %7u" % (self._sparse_image_chunk_cnt + 1, curpos, data_sz, self._offset, chunk_sz), - end=" ") - - if chunk_type == 0xCAC1: - if data_sz != (chunk_sz * blk_sz): - print("Raw chunk input size (%u) does not match output size (%u)" - % (data_sz, chunk_sz * blk_sz)) - return - else: - curtype = "Raw data" - - elif chunk_type == 0xCAC2: - if data_sz != 4: - print("Fill chunk should have 4 bytes of fill, but this has %u" - % (data_sz)) - return - else: - fill_bin = FH.read(4) - fill = struct.unpack("