Created
April 24, 2026 09:48
-
-
Save Cryolitia/6a68087c590e5693156a4e5cc3be8aca to your computer and use it in GitHub Desktop.
Convert 512-byte sector disk images to 4K logical-sector images while preserving partition tables, metadata, and partition contents
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /usr/bin/env nix-shell | |
| #! nix-shell -i bash -p bash coreutils util-linux parted gptfdisk jq | |
| # SPDX-FileCopyrightText: 2026 Cryolitia | |
| # SPDX-License-Identifier: MIT | |
| set -euo pipefail | |
| SECTOR_SIZE=4096 | |
| TRANSFER_BLOCK_SIZE=4M | |
| ALIGN_MIB=1 | |
| ALIGN_BYTES=$((ALIGN_MIB * 1024 * 1024)) | |
| declare -a PART_NUMS=() | |
| declare -a PART_START_BYTES=() | |
| declare -a PART_SIZES=() | |
| declare -a PART_END_BYTES=() | |
| declare -a PART_FS_TYPES=() | |
| declare -a NEW_START_BYTES=() | |
| declare -a NEW_END_BYTES=() | |
| declare -a NEW_SIZES=() | |
| declare -a NEW_START_LBAS=() | |
| declare -a NEW_END_LBAS=() | |
| declare -a GPT_NAMES=() | |
| declare -a GPT_TYPE_GUIDS=() | |
| declare -a GPT_UNIQUE_GUIDS=() | |
| declare -a MBR_TYPE_CODES=() | |
| declare -a MBR_BOOTABLES=() | |
| MBR_DISK_ID="" | |
| INPUT_IMG="" | |
| OUTPUT_IMG="" | |
| FORCE=0 | |
| ANALYZE_ONLY=0 | |
| COMPUTED_OUTPUT_SIZE="" | |
| SOURCE_DEV="" | |
| OUTPUT_LOOP="" | |
| ANALYZE_LOOP="" | |
| INPUT_SECTOR_SIZE=512 | |
| cleanup() { | |
| local status=$? | |
| if [[ -n "$OUTPUT_LOOP" ]]; then | |
| losetup -d "$OUTPUT_LOOP" >/dev/null 2>&1 || true | |
| fi | |
| if [[ -n "$ANALYZE_LOOP" ]]; then | |
| losetup -d "$ANALYZE_LOOP" >/dev/null 2>&1 || true | |
| fi | |
| if [[ $status -ne 0 ]]; then | |
| printf 'Error: script failed with status %s\n' "$status" >&2 | |
| fi | |
| } | |
| trap cleanup EXIT | |
| usage() { | |
| cat <<'EOF' | |
| Usage: | |
| convert-sector-img.sh -i INPUT_IMG -o OUTPUT_IMG [--force] | |
| convert-sector-img.sh -i INPUT_IMG --analyze-only | |
| Convert a 512-byte logical-sector raw disk image into a 4K logical-sector raw | |
| disk image while preserving partition contents. | |
| Options: | |
| -i, --input Input raw disk image | |
| -o, --output Output raw disk image | |
| --analyze-only | |
| Read-only analysis of the input image and proposed 4K layout | |
| --force Overwrite output image if it exists | |
| -h, --help Show this help | |
| Notes: | |
| - GPT input stays GPT output and keeps partition name/type GUID/unique GUID. | |
| - MBR input stays MBR output. | |
| - MBR images with extended or logical partitions are rejected. | |
| - Partition contents are copied byte-for-byte and verified with sha256. | |
| - Copy and verification display progress with dd status=progress. | |
| - Conversion requires root privileges for 4K loop device setup. | |
| - Analyze mode auto-detects 512-byte vs 4K logical sector layouts. | |
| EOF | |
| } | |
| require_commands() { | |
| local cmd | |
| for cmd in fdisk parted sfdisk sgdisk jq dd truncate sha256sum stat numfmt losetup; do | |
| command -v "$cmd" >/dev/null 2>&1 || { | |
| printf 'Error: required command not found: %s\n' "$cmd" >&2 | |
| exit 1 | |
| } | |
| done | |
| } | |
| align_up() { | |
| local value=$1 | |
| local alignment=$2 | |
| printf '%s\n' $(( ((value + alignment - 1) / alignment) * alignment )) | |
| } | |
| format_bytes() { | |
| numfmt --to=iec --suffix=B "$1" | |
| } | |
| parse_args() { | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -i|--input) | |
| [[ $# -ge 2 ]] || { printf 'Error: missing value for %s\n' "$1" >&2; exit 1; } | |
| INPUT_IMG=$2 | |
| shift 2 | |
| ;; | |
| -o|--output) | |
| [[ $# -ge 2 ]] || { printf 'Error: missing value for %s\n' "$1" >&2; exit 1; } | |
| OUTPUT_IMG=$2 | |
| shift 2 | |
| ;; | |
| --force) | |
| FORCE=1 | |
| shift | |
| ;; | |
| --analyze-only) | |
| ANALYZE_ONLY=1 | |
| shift | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| printf 'Error: unknown argument: %s\n' "$1" >&2 | |
| usage >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| [[ -n "$INPUT_IMG" ]] || { printf 'Error: input image is required\n' >&2; exit 1; } | |
| [[ -f "$INPUT_IMG" ]] || { printf 'Error: input image not found: %s\n' "$INPUT_IMG" >&2; exit 1; } | |
| if [[ $ANALYZE_ONLY -eq 1 ]]; then | |
| [[ -z "$OUTPUT_IMG" ]] || { printf 'Error: --analyze-only does not accept --output\n' >&2; exit 1; } | |
| else | |
| [[ -n "$OUTPUT_IMG" ]] || { printf 'Error: output image is required\n' >&2; exit 1; } | |
| if [[ "$INPUT_IMG" -ef "$OUTPUT_IMG" ]]; then | |
| printf 'Error: input and output image must be different files\n' >&2 | |
| exit 1 | |
| fi | |
| fi | |
| if [[ $ANALYZE_ONLY -eq 0 && -e "$OUTPUT_IMG" && $FORCE -ne 1 ]]; then | |
| printf 'Error: output image exists, use --force to overwrite: %s\n' "$OUTPUT_IMG" >&2 | |
| exit 1 | |
| fi | |
| } | |
| ensure_privileges() { | |
| if [[ $ANALYZE_ONLY -eq 1 || $EUID -eq 0 ]]; then | |
| return | |
| fi | |
| command -v sudo >/dev/null 2>&1 || { | |
| printf 'Error: conversion requires root privileges; rerun with sudo\n' >&2 | |
| exit 1 | |
| } | |
| printf 'Requesting sudo for 4K loop device operations...\n' >&2 | |
| exec sudo "$0" "$@" | |
| } | |
| reset_partition_state() { | |
| PART_NUMS=() | |
| PART_START_BYTES=() | |
| PART_SIZES=() | |
| PART_END_BYTES=() | |
| PART_FS_TYPES=() | |
| NEW_START_BYTES=() | |
| NEW_END_BYTES=() | |
| NEW_SIZES=() | |
| NEW_START_LBAS=() | |
| NEW_END_LBAS=() | |
| GPT_NAMES=() | |
| GPT_TYPE_GUIDS=() | |
| GPT_UNIQUE_GUIDS=() | |
| MBR_TYPE_CODES=() | |
| MBR_BOOTABLES=() | |
| MBR_DISK_ID="" | |
| COMPUTED_OUTPUT_SIZE="" | |
| } | |
| can_probe_4k_loop() { | |
| if [[ $EUID -eq 0 ]]; then | |
| return 0 | |
| fi | |
| command -v sudo >/dev/null 2>&1 || return 1 | |
| sudo -n true >/dev/null 2>&1 | |
| } | |
| probe_layout_metrics() { | |
| local device=$1 | |
| local label line start_field end_field size_field total_size=0 part_count=0 max_end=0 | |
| label=$(parted -m -s "$device" print 2>/dev/null | awk -F: 'NR==2 { print $6 }') || return 1 | |
| case "$label" in | |
| gpt|msdos) ;; | |
| *) return 1 ;; | |
| esac | |
| while IFS= read -r line; do | |
| [[ $line == [0-9]*:* ]] || continue | |
| IFS=: read -r _ start_field end_field size_field _ <<<"$line" | |
| part_count=$((part_count + 1)) | |
| total_size=$((total_size + ${size_field%B})) | |
| if (( ${end_field%B} > max_end )); then | |
| max_end=${end_field%B} | |
| fi | |
| done < <(parted -m -s "$device" unit B print 2>/dev/null) | |
| (( part_count > 0 )) || return 1 | |
| printf '%s:%s:%s:%s\n' "$label" "$part_count" "$total_size" "$max_end" | |
| } | |
| select_analysis_source() { | |
| local raw_metrics raw_label raw_count raw_total raw_end | |
| local loop_metrics loop_label loop_count loop_total loop_end | |
| SOURCE_DEV=$INPUT_IMG | |
| INPUT_SECTOR_SIZE=512 | |
| raw_metrics=$(probe_layout_metrics "$INPUT_IMG") || { | |
| printf 'Error: failed to read partition layout from input image\n' >&2 | |
| exit 1 | |
| } | |
| IFS=: read -r raw_label raw_count raw_total raw_end <<<"$raw_metrics" | |
| if ! can_probe_4k_loop; then | |
| return | |
| fi | |
| ANALYZE_LOOP=$(losetup --find --show --sector-size "$SECTOR_SIZE" "$INPUT_IMG" 2>/dev/null || true) | |
| [[ -n "$ANALYZE_LOOP" ]] || return 0 | |
| loop_metrics=$(probe_layout_metrics "$ANALYZE_LOOP" 2>/dev/null || true) | |
| [[ -n "$loop_metrics" ]] || return 0 | |
| IFS=: read -r loop_label loop_count loop_total loop_end <<<"$loop_metrics" | |
| if (( loop_total > raw_total || loop_end > raw_end )); then | |
| SOURCE_DEV=$ANALYZE_LOOP | |
| INPUT_SECTOR_SIZE=$SECTOR_SIZE | |
| fi | |
| } | |
| detect_label_type() { | |
| local label | |
| label=$(parted -m -s "$SOURCE_DEV" print | awk -F: 'NR==2 { print $6 }') | |
| case "$label" in | |
| gpt|msdos) | |
| printf '%s\n' "$label" | |
| ;; | |
| *) | |
| printf 'Error: unsupported partition table type: %s\n' "$label" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| collect_common_partitions() { | |
| local line number start_field end_field size_field fs_field start_bytes end_bytes size_bytes | |
| while IFS= read -r line; do | |
| [[ $line == [0-9]*:* ]] || continue | |
| IFS=: read -r number start_field end_field size_field fs_field _ <<<"$line" | |
| start_bytes=${start_field%B} | |
| end_bytes=${end_field%B} | |
| size_bytes=${size_field%B} | |
| PART_NUMS+=("$number") | |
| PART_START_BYTES+=("$start_bytes") | |
| PART_END_BYTES+=("$end_bytes") | |
| PART_SIZES+=("$size_bytes") | |
| PART_FS_TYPES+=("$fs_field") | |
| done < <(parted -m -s "$SOURCE_DEV" unit B print) | |
| [[ ${#PART_NUMS[@]} -gt 0 ]] || { printf 'Error: no partitions found in input image\n' >&2; exit 1; } | |
| } | |
| collect_gpt_metadata() { | |
| local part_num info_line type_guid unique_guid name | |
| for part_num in "${PART_NUMS[@]}"; do | |
| info_line=$(sgdisk -i "$part_num" "$SOURCE_DEV") | |
| type_guid=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition GUID code:/ { split($2, a, " "); print a[1] }') | |
| unique_guid=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition unique GUID:/ { print $2 }') | |
| name=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition name:/ { gsub(/^\047|\047$/, "", $2); print $2 }') | |
| [[ -n "$type_guid" ]] || { printf 'Error: failed to read GPT type GUID for partition %s\n' "$part_num" >&2; exit 1; } | |
| [[ -n "$unique_guid" ]] || { printf 'Error: failed to read GPT unique GUID for partition %s\n' "$part_num" >&2; exit 1; } | |
| GPT_TYPE_GUIDS+=("$type_guid") | |
| GPT_UNIQUE_GUIDS+=("$unique_guid") | |
| GPT_NAMES+=("$name") | |
| done | |
| } | |
| collect_mbr_metadata() { | |
| local json part_count i type start size bootable part_type | |
| json=$(sfdisk --json "$SOURCE_DEV") | |
| part_count=$(printf '%s\n' "$json" | jq '.partitiontable.partitions | length') | |
| MBR_DISK_ID=$(printf '%s\n' "$json" | jq -r '.partitiontable.id // ""') | |
| [[ "$part_count" -eq "${#PART_NUMS[@]}" ]] || { | |
| printf 'Error: sfdisk partition count mismatch\n' >&2 | |
| exit 1 | |
| } | |
| for ((i = 0; i < part_count; i++)); do | |
| type=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].type // \"\"") | |
| start=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].start // 0") | |
| size=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].size // 0") | |
| bootable=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].bootable // false") | |
| part_type=$(printf '%s' "$type" | tr '[:upper:]' '[:lower:]') | |
| case "$part_type" in | |
| 5|0x5|05|0x05|f|0xf|0f|0x0f|85|0x85) | |
| printf 'Error: MBR extended/logical partitions are not supported\n' >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| if [[ "$start" -eq 0 || "$size" -eq 0 ]]; then | |
| printf 'Error: invalid MBR metadata for partition %s\n' "${PART_NUMS[$i]}" >&2 | |
| exit 1 | |
| fi | |
| MBR_TYPE_CODES+=("$type") | |
| MBR_BOOTABLES+=("$bootable") | |
| done | |
| } | |
| compute_new_layout() { | |
| local label_type=$1 | |
| local input_size current_offset new_start new_end new_size i final_size | |
| input_size=$(stat -c '%s' "$INPUT_IMG") | |
| current_offset=$ALIGN_BYTES | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| new_start=$(align_up "$current_offset" "$ALIGN_BYTES") | |
| new_size=$(align_up "${PART_SIZES[$i]}" "$SECTOR_SIZE") | |
| new_end=$((new_start + new_size - 1)) | |
| if (( new_start % SECTOR_SIZE != 0 )); then | |
| printf 'Error: computed partition %s start is not 4K aligned\n' "${PART_NUMS[$i]}" >&2 | |
| exit 1 | |
| fi | |
| NEW_START_BYTES+=("$new_start") | |
| NEW_END_BYTES+=("$new_end") | |
| NEW_SIZES+=("$new_size") | |
| NEW_START_LBAS+=("$((new_start / SECTOR_SIZE))") | |
| NEW_END_LBAS+=("$((new_end / SECTOR_SIZE))") | |
| current_offset=$((new_end + 1)) | |
| done | |
| final_size=$(align_up "$current_offset" "$ALIGN_BYTES") | |
| if [[ "$label_type" == "gpt" ]]; then | |
| final_size=$(align_up $((final_size + 33 * SECTOR_SIZE)) "$ALIGN_BYTES") | |
| fi | |
| if (( final_size < input_size )); then | |
| final_size=$(align_up "$input_size" "$ALIGN_BYTES") | |
| fi | |
| COMPUTED_OUTPUT_SIZE=$final_size | |
| } | |
| create_output_image() { | |
| local output_size=$1 | |
| if [[ -e "$OUTPUT_IMG" ]]; then | |
| rm -f "$OUTPUT_IMG" | |
| fi | |
| truncate -s "$output_size" "$OUTPUT_IMG" | |
| } | |
| attach_output_loop() { | |
| [[ -z "$OUTPUT_LOOP" ]] || return 0 | |
| OUTPUT_LOOP=$(losetup --find --show --sector-size "$SECTOR_SIZE" "$OUTPUT_IMG") | |
| } | |
| run_sfdisk() { | |
| local stderr_file | |
| local output | |
| stderr_file=$(mktemp) | |
| if ! output=$(sfdisk --no-reread "$OUTPUT_LOOP" 2>"$stderr_file"); then | |
| cat "$stderr_file" >&2 | |
| rm -f "$stderr_file" | |
| return 1 | |
| fi | |
| if [[ -s "$stderr_file" ]]; then | |
| if ! grep -q '^重新读取分区表失败' "$stderr_file" && ! grep -q '^Re-reading the partition table failed' "$stderr_file"; then | |
| cat "$stderr_file" >&2 | |
| fi | |
| fi | |
| rm -f "$stderr_file" | |
| printf '%s' "$output" | |
| } | |
| build_gpt() { | |
| local i part_num name type_guid unique_guid start_lba end_lba | |
| sgdisk --clear "$OUTPUT_LOOP" >/dev/null | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| part_num=${PART_NUMS[$i]} | |
| name=${GPT_NAMES[$i]} | |
| type_guid=${GPT_TYPE_GUIDS[$i]} | |
| unique_guid=${GPT_UNIQUE_GUIDS[$i]} | |
| start_lba=${NEW_START_LBAS[$i]} | |
| end_lba=${NEW_END_LBAS[$i]} | |
| sgdisk \ | |
| --new="${part_num}:${start_lba}:${end_lba}" \ | |
| --typecode="${part_num}:${type_guid}" \ | |
| --change-name="${part_num}:${name}" \ | |
| --partition-guid="${part_num}:${unique_guid}" \ | |
| "$OUTPUT_LOOP" >/dev/null | |
| done | |
| sgdisk --verify "$OUTPUT_LOOP" >/dev/null | |
| } | |
| build_mbr() { | |
| local script="label: dos\nunit: sectors\n" | |
| local i start_lba size_lba line type bootable | |
| if [[ -n "$MBR_DISK_ID" && "$MBR_DISK_ID" != "null" ]]; then | |
| script+="label-id: ${MBR_DISK_ID}\n" | |
| fi | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| start_lba=${NEW_START_LBAS[$i]} | |
| size_lba=$((NEW_SIZES[$i] / SECTOR_SIZE)) | |
| type=${MBR_TYPE_CODES[$i]} | |
| bootable=${MBR_BOOTABLES[$i]} | |
| line="start=${start_lba}, size=${size_lba}, type=${type}" | |
| if [[ "$bootable" == "true" ]]; then | |
| line+=" , bootable" | |
| fi | |
| script+="${line}\n" | |
| done | |
| printf '%b' "$script" | run_sfdisk >/dev/null | |
| } | |
| copy_partition_data() { | |
| local i | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| printf 'Copying partition %s/%s\n' "$((i + 1))" "${#PART_NUMS[@]}" | |
| dd \ | |
| if="$INPUT_IMG" \ | |
| of="$OUTPUT_IMG" \ | |
| bs="$TRANSFER_BLOCK_SIZE" \ | |
| iflag=skip_bytes,count_bytes \ | |
| oflag=seek_bytes \ | |
| skip="${PART_START_BYTES[$i]}" \ | |
| count="${PART_SIZES[$i]}" \ | |
| seek="${NEW_START_BYTES[$i]}" \ | |
| conv=notrunc \ | |
| status=progress | |
| done | |
| } | |
| verify_common_metadata() { | |
| local label_type=$1 | |
| local line idx=0 number start_field size_field start_bytes size_bytes fs_field | |
| while IFS= read -r line; do | |
| [[ $line == [0-9]*:* ]] || continue | |
| IFS=: read -r number start_field _ size_field fs_field _ <<<"$line" | |
| start_bytes=${start_field%B} | |
| size_bytes=${size_field%B} | |
| [[ "$number" == "${PART_NUMS[$idx]}" ]] || { printf 'Error: output partition numbering mismatch\n' >&2; exit 1; } | |
| [[ "$start_bytes" == "${NEW_START_BYTES[$idx]}" ]] || { printf 'Error: output partition start mismatch for partition %s\n' "$number" >&2; exit 1; } | |
| [[ "$size_bytes" == "${NEW_SIZES[$idx]}" ]] || { printf 'Error: output partition size mismatch for partition %s\n' "$number" >&2; exit 1; } | |
| if (( start_bytes % SECTOR_SIZE != 0 )); then | |
| printf 'Error: output partition %s start is not 4K aligned\n' "$number" >&2 | |
| exit 1 | |
| fi | |
| ((idx += 1)) | |
| done < <(parted -m -s "$OUTPUT_LOOP" unit B print) | |
| [[ $idx -eq ${#PART_NUMS[@]} ]] || { printf 'Error: output partition count mismatch\n' >&2; exit 1; } | |
| if [[ "$label_type" == "gpt" ]]; then | |
| sgdisk --verify "$OUTPUT_LOOP" >/dev/null | |
| fi | |
| } | |
| verify_gpt_metadata() { | |
| local i part_num info_line type_guid unique_guid name | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| part_num=${PART_NUMS[$i]} | |
| info_line=$(sgdisk -i "$part_num" "$OUTPUT_LOOP") | |
| type_guid=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition GUID code:/ { split($2, a, " "); print a[1] }') | |
| unique_guid=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition unique GUID:/ { print $2 }') | |
| name=$(printf '%s\n' "$info_line" | awk -F': ' '/Partition name:/ { gsub(/^\047|\047$/, "", $2); print $2 }') | |
| [[ "$type_guid" == "${GPT_TYPE_GUIDS[$i]}" ]] || { printf 'Error: GPT type GUID mismatch for partition %s\n' "$part_num" >&2; exit 1; } | |
| [[ "$unique_guid" == "${GPT_UNIQUE_GUIDS[$i]}" ]] || { printf 'Error: GPT unique GUID mismatch for partition %s\n' "$part_num" >&2; exit 1; } | |
| [[ "$name" == "${GPT_NAMES[$i]}" ]] || { printf 'Error: GPT name mismatch for partition %s\n' "$part_num" >&2; exit 1; } | |
| done | |
| } | |
| verify_mbr_metadata() { | |
| local json part_count i type bootable | |
| json=$(sfdisk --json "$OUTPUT_LOOP") | |
| part_count=$(printf '%s\n' "$json" | jq '.partitiontable.partitions | length') | |
| [[ "$part_count" -eq "${#PART_NUMS[@]}" ]] || { printf 'Error: output MBR partition count mismatch\n' >&2; exit 1; } | |
| for ((i = 0; i < part_count; i++)); do | |
| type=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].type // \"\"") | |
| bootable=$(printf '%s\n' "$json" | jq -r ".partitiontable.partitions[$i].bootable // false") | |
| [[ "$type" == "${MBR_TYPE_CODES[$i]}" ]] || { printf 'Error: MBR type mismatch for partition %s\n' "${PART_NUMS[$i]}" >&2; exit 1; } | |
| [[ "$bootable" == "${MBR_BOOTABLES[$i]}" ]] || { printf 'Error: MBR boot flag mismatch for partition %s\n' "${PART_NUMS[$i]}" >&2; exit 1; } | |
| done | |
| } | |
| sha256_partition_region() { | |
| local image=$1 | |
| local start=$2 | |
| local size=$3 | |
| dd if="$image" bs="$TRANSFER_BLOCK_SIZE" iflag=skip_bytes,count_bytes skip="$start" count="$size" status=progress \ | |
| | sha256sum | awk '{print $1}' | |
| } | |
| verify_partition_content() { | |
| local i input_hash output_hash | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| printf 'Verifying partition %s/%s (input)\n' "$((i + 1))" "${#PART_NUMS[@]}" | |
| input_hash=$(sha256_partition_region "$INPUT_IMG" "${PART_START_BYTES[$i]}" "${PART_SIZES[$i]}") | |
| printf 'Verifying partition %s/%s (output)\n' "$((i + 1))" "${#PART_NUMS[@]}" | |
| output_hash=$(sha256_partition_region "$OUTPUT_IMG" "${NEW_START_BYTES[$i]}" "${PART_SIZES[$i]}") | |
| [[ "$input_hash" == "$output_hash" ]] || { | |
| printf 'Error: content hash mismatch for partition %s\n' "${PART_NUMS[$i]}" >&2 | |
| exit 1 | |
| } | |
| done | |
| } | |
| print_partition_layout() { | |
| local i | |
| printf 'Partitions:\n' | |
| for ((i = 0; i < ${#PART_NUMS[@]}; i++)); do | |
| printf ' %s: %s -> %s, size %s' \ | |
| "${PART_NUMS[$i]}" \ | |
| "$(format_bytes "${PART_START_BYTES[$i]}")" \ | |
| "$(format_bytes "${NEW_START_BYTES[$i]}")" \ | |
| "$(format_bytes "${PART_SIZES[$i]}")" | |
| if [[ "${NEW_SIZES[$i]}" != "${PART_SIZES[$i]}" ]]; then | |
| printf ', padded to %s' "$(format_bytes "${NEW_SIZES[$i]}")" | |
| fi | |
| printf '\n' | |
| done | |
| } | |
| print_analysis() { | |
| local label_type=$1 | |
| printf 'Input image: %s\n' "$INPUT_IMG" | |
| printf 'Label type: %s\n' "$label_type" | |
| printf 'Input sector: %s\n' "$INPUT_SECTOR_SIZE" | |
| printf 'Target sector:%s\n' "$SECTOR_SIZE" | |
| printf 'Output size: %s\n' "$(format_bytes "$COMPUTED_OUTPUT_SIZE")" | |
| printf 'Mode: analyze-only\n\n' | |
| print_partition_layout | |
| } | |
| print_summary() { | |
| local label_type=$1 | |
| printf 'Input image: %s\n' "$INPUT_IMG" | |
| printf 'Output image: %s\n' "$OUTPUT_IMG" | |
| printf 'Label type: %s\n' "$label_type" | |
| printf 'Input sector: %s\n' "$INPUT_SECTOR_SIZE" | |
| printf 'Target sector:%s\n' "$SECTOR_SIZE" | |
| printf 'Output size: %s\n\n' "$(format_bytes "$COMPUTED_OUTPUT_SIZE")" | |
| print_partition_layout | |
| printf '\nVerification: OK\n' | |
| } | |
| main() { | |
| local label_type | |
| parse_args "$@" | |
| require_commands | |
| ensure_privileges "$@" | |
| reset_partition_state | |
| SOURCE_DEV=$INPUT_IMG | |
| if [[ $ANALYZE_ONLY -eq 1 ]]; then | |
| select_analysis_source | |
| fi | |
| label_type=$(detect_label_type) | |
| collect_common_partitions | |
| case "$label_type" in | |
| gpt) | |
| collect_gpt_metadata | |
| ;; | |
| msdos) | |
| collect_mbr_metadata | |
| ;; | |
| esac | |
| compute_new_layout "$label_type" | |
| if [[ $ANALYZE_ONLY -eq 1 ]]; then | |
| print_analysis "$label_type" | |
| return | |
| fi | |
| create_output_image "$COMPUTED_OUTPUT_SIZE" | |
| attach_output_loop | |
| case "$label_type" in | |
| gpt) | |
| build_gpt | |
| ;; | |
| msdos) | |
| build_mbr | |
| ;; | |
| esac | |
| copy_partition_data | |
| verify_common_metadata "$label_type" | |
| case "$label_type" in | |
| gpt) | |
| verify_gpt_metadata | |
| ;; | |
| msdos) | |
| verify_mbr_metadata | |
| ;; | |
| esac | |
| verify_partition_content | |
| print_summary "$label_type" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment