1#!/bin/bash 2 3# 4# Copyright (C) 2015 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19# Script to generate a Brillo update for use by the update engine. 20# 21# usage: brillo_update_payload COMMAND [ARGS] 22# The following commands are supported: 23# generate generate an unsigned payload 24# hash generate a payload or metadata hash 25# sign generate a signed payload 26# properties generate a properties file from a payload 27# verify verify a payload by recreating a target image. 28# check verify a payload using paycheck (static testing) 29# 30# Generate command arguments: 31# --payload generated unsigned payload output file 32# --source_image if defined, generate a delta payload from the 33# specified image to the target_image 34# --target_image the target image that should be sent to clients 35# --metadata_size_file if defined, generate a file containing the size 36# of the ayload metadata in bytes to the specified 37# file 38# --disable_fec_computation Disable the on device fec data computation for 39# incremental update. This feature is enabled by 40# default 41# --force_minor_version Override the minor version used for delta 42# generation. 43# 44# Hash command arguments: 45# --unsigned_payload the input unsigned payload to generate the hash from 46# --signature_size signature sizes in bytes in the following format: 47# "size1:size2[:...]" 48# --payload_hash_file if defined, generate a payload hash and output to the 49# specified file 50# --metadata_hash_file if defined, generate a metadata hash and output to the 51# specified file 52# 53# Sign command arguments: 54# --unsigned_payload the input unsigned payload to insert the signatures 55# --payload the output signed payload 56# --signature_size signature sizes in bytes in the following format: 57# "size1:size2[:...]" 58# --payload_signature_file the payload signature files in the following 59# format: 60# "payload_signature1:payload_signature2[:...]" 61# --metadata_signature_file the metadata signature files in the following 62# format: 63# "metadata_signature1:metadata_signature2[:...]" 64# --metadata_size_file if defined, generate a file containing the size of 65# the signed payload metadata in bytes to the 66# specified file 67# Note that the number of signature sizes and payload signatures have to match. 68# 69# Properties command arguments: 70# --payload the input signed or unsigned payload 71# --properties_file the output path where to write the properties, or 72# '-' for stdout. 73# Verify command arguments: 74# --payload payload input file 75# --source_image verify payload to the specified source image. 76# --target_image the target image to verify upon. 77# 78# Check command arguments: 79# Symmetrical with the verify command. 80 81 82# Exit codes: 83EX_UNSUPPORTED_DELTA=100 84 85warn() { 86 echo "brillo_update_payload: warning: $*" >&2 87} 88 89die() { 90 echo "brillo_update_payload: error: $*" >&2 91 exit 1 92} 93 94# Loads shflags. We first look at the default install location; then our own 95# directory; finally the parent directory. 96load_shflags() { 97 local my_dir="$(dirname "$(readlink -f "$0")")" 98 local path 99 for path in /usr/share/misc \ 100 "${my_dir}"/lib/shflags \ 101 "${my_dir}"/../lib/shflags; do 102 if [[ -r "${path}/shflags" ]]; then 103 . "${path}/shflags" || die "Could not load ${path}/shflags." 104 return 105 fi 106 done 107 die "Could not find shflags." 108} 109 110load_shflags 111 112HELP_GENERATE="generate: Generate an unsigned update payload." 113HELP_HASH="hash: Generate the hashes of the unsigned payload and metadata used \ 114for signing." 115HELP_SIGN="sign: Insert the signatures into the unsigned payload." 116HELP_PROPERTIES="properties: Extract payload properties to a file." 117HELP_VERIFY="verify: Verify a (signed) update payload using delta_generator." 118HELP_CHECK="check: Check a (signed) update payload using paycheck (static \ 119testing)." 120 121usage() { 122 echo "Supported commands:" 123 echo 124 echo "${HELP_GENERATE}" 125 echo "${HELP_HASH}" 126 echo "${HELP_SIGN}" 127 echo "${HELP_PROPERTIES}" 128 echo "${HELP_VERIFY}" 129 echo "${HELP_CHECK}" 130 echo 131 echo "Use: \"$0 <command> --help\" for more options." 132} 133 134# Check that a command is specified. 135if [[ $# -lt 1 ]]; then 136 echo "Please specify a command [generate|hash|sign|properties|verify|check]" 137 exit 1 138fi 139 140# Parse command. 141COMMAND="${1:-}" 142shift 143 144case "${COMMAND}" in 145 generate) 146 FLAGS_HELP="${HELP_GENERATE}" 147 ;; 148 149 hash) 150 FLAGS_HELP="${HELP_HASH}" 151 ;; 152 153 sign) 154 FLAGS_HELP="${HELP_SIGN}" 155 ;; 156 157 properties) 158 FLAGS_HELP="${HELP_PROPERTIES}" 159 ;; 160 161 verify) 162 FLAGS_HELP="${HELP_VERIFY}" 163 ;; 164 165 check) 166 FLAGS_HELP="${HELP_CHECK}" 167 ;; 168 169 *) 170 echo "Unrecognized command: \"${COMMAND}\"" >&2 171 usage >&2 172 exit 1 173 ;; 174esac 175 176# Flags 177FLAGS_HELP="Usage: $0 ${COMMAND} [flags] 178${FLAGS_HELP}" 179 180if [[ "${COMMAND}" == "generate" ]]; then 181 DEFINE_string payload "" \ 182 "Path to output the generated unsigned payload file." 183 DEFINE_string target_image "" \ 184 "Path to the target image that should be sent to clients." 185 DEFINE_string source_image "" \ 186 "Optional: Path to a source image. If specified, this makes a delta update." 187 DEFINE_string metadata_size_file "" \ 188 "Optional: Path to output metadata size." 189 DEFINE_string max_timestamp "" \ 190 "Optional: The maximum unix timestamp of the OS allowed to apply this \ 191payload, should be set to a number higher than the build timestamp of the \ 192system running on the device, 0 if not specified." 193 DEFINE_string partition_timestamps "" \ 194 "Optional: Per-partition maximum unix timestamp of the OS allowed to \ 195apply this payload. Should be a comma separated key value pairs. e.x.\ 196system:1234,vendor:456" 197 DEFINE_string disable_fec_computation "" \ 198 "Optional: Disables the on device fec data computation for incremental \ 199update. This feature is enabled by default." 200 DEFINE_string disable_verity_computation "" \ 201 "Optional: Disables the on device verity computation for incremental \ 202update. This feature is enabled by default." 203 DEFINE_string is_partial_update "" \ 204 "Optional: True if the payload is for partial update. i.e. it only updates \ 205a subset of partitions on device." 206 DEFINE_string full_boot "" "Will include full boot image" 207 DEFINE_string disable_vabc "" \ 208 "Optional: Disables Virtual AB Compression when installing the OTA" 209 DEFINE_string enable_vabc_xor "" \ 210 "Optional: Enable the use of Virtual AB Compression XOR feature" 211 DEFINE_string force_minor_version "" \ 212 "Optional: Override the minor version for the delta generation." 213 DEFINE_string compressor_types "" \ 214 "Optional: allowed compressor types. Colon separated, allowe values are bz2 and brotli" 215 DEFINE_string enable_zucchini "" \ 216 "Optional: Whether to enable zucchini diffing" 217 DEFINE_string enable_lz4diff "" \ 218 "Optional: Whether to enable lz4 diffing for EROFS" 219 DEFINE_string liblz4_path "" \ 220 "Required if --enabled_lz4diff true is passed. Path to liblz4.so. delta_generator will use this copy of liblz4.so for compression. It is important that this copy of liblz4.so is the same as the one on source build." 221 DEFINE_string erofs_compression_param "" \ 222 "Compression parameter passed to mkfs.erofs's -z option." 223fi 224if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then 225 DEFINE_string unsigned_payload "" "Path to the input unsigned payload." 226 DEFINE_string signature_size "" \ 227 "Signature sizes in bytes in the following format: size1:size2[:...]" 228fi 229if [[ "${COMMAND}" == "hash" ]]; then 230 DEFINE_string metadata_hash_file "" \ 231 "Optional: Path to output metadata hash file." 232 DEFINE_string payload_hash_file "" \ 233 "Optional: Path to output payload hash file." 234fi 235if [[ "${COMMAND}" == "sign" ]]; then 236 DEFINE_string payload "" \ 237 "Path to output the generated unsigned payload file." 238 DEFINE_string metadata_signature_file "" \ 239 "The metatada signatures in the following format: \ 240metadata_signature1:metadata_signature2[:...]" 241 DEFINE_string payload_signature_file "" \ 242 "The payload signatures in the following format: \ 243payload_signature1:payload_signature2[:...]" 244 DEFINE_string metadata_size_file "" \ 245 "Optional: Path to output metadata size." 246fi 247if [[ "${COMMAND}" == "properties" ]]; then 248 DEFINE_string payload "" \ 249 "Path to the input signed or unsigned payload file." 250 DEFINE_string properties_file "-" \ 251 "Path to output the extracted property files. If '-' is passed stdout will \ 252be used." 253fi 254if [[ "${COMMAND}" == "verify" || "${COMMAND}" == "check" ]]; then 255 DEFINE_string payload "" \ 256 "Path to the input payload file." 257 DEFINE_string target_image "" \ 258 "Path to the target image to verify upon." 259 DEFINE_string source_image "" \ 260 "Optional: Path to a source image. If specified, the delta update is \ 261applied to this." 262fi 263 264DEFINE_string work_dir "${TMPDIR:-/tmp}" "Where to dump temporary files." 265 266# Parse command line flag arguments 267FLAGS "$@" || exit 1 268eval set -- "${FLAGS_ARGV}" 269set -e 270 271# Override the TMPDIR with the passed work_dir flags, which anyway defaults to 272# ${TMPDIR}. 273TMPDIR="${FLAGS_work_dir}" 274export TMPDIR 275 276# Associative arrays from partition name to file in the source and target 277# images. The size of the updated area must be the size of the file. 278declare -A SRC_PARTITIONS 279declare -A DST_PARTITIONS 280 281# Associative arrays for the .map files associated with each src/dst partition 282# file in SRC_PARTITIONS and DST_PARTITIONS. 283declare -A SRC_PARTITIONS_MAP 284declare -A DST_PARTITIONS_MAP 285 286# List of partition names in order. 287declare -a PARTITIONS_ORDER 288 289# A list of PIDs of the extract_image workers. 290EXTRACT_IMAGE_PIDS=() 291 292# A list of temporary files to remove during cleanup. 293CLEANUP_FILES=() 294 295# Global options to force the version of the payload. 296FORCE_MAJOR_VERSION="" 297FORCE_MINOR_VERSION="" 298 299# Path to the postinstall config file in target image if exists. 300POSTINSTALL_CONFIG_FILE="" 301 302# Path to the dynamic partition info file in target image if exists. 303DYNAMIC_PARTITION_INFO_FILE="" 304 305# Path to the META/apex_info.pb found in target build 306APEX_INFO_FILE="" 307 308# read_option_int <file.txt> <option_key> [default_value] 309# 310# Reads the unsigned integer value associated with |option_key| in a key=value 311# file |file.txt|. Prints the read value if found and valid, otherwise prints 312# the |default_value|. 313read_option_uint() { 314 local file_txt="$1" 315 local option_key="$2" 316 local default_value="${3:-}" 317 local value 318 if value=$(grep "^${option_key}=" "${file_txt}" | tail -n 1); then 319 if value=$(echo "${value}" | cut -f 2- -d "=" | grep -E "^[0-9]+$"); then 320 echo "${value}" 321 return 322 fi 323 fi 324 echo "${default_value}" 325} 326 327# truncate_file <file_path> <file_size> 328# 329# Truncate the given |file_path| to |file_size| using python. 330# The truncate binary might not be available. 331truncate_file() { 332 local file_path="$1" 333 local file_size="$2" 334 python -c "open(\"${file_path}\", 'a').truncate(${file_size})" 335} 336 337# Create a temporary file in the work_dir with an optional pattern name. 338# Prints the name of the newly created file. 339create_tempfile() { 340 local pattern="${1:-tempfile.XXXXXX}" 341 mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}" 342} 343 344cleanup() { 345 local err="" 346 rm -f "${CLEANUP_FILES[@]}" || err=1 347 348 # If we are cleaning up after an error, or if we got an error during 349 # cleanup (even if we eventually succeeded) return a non-zero exit 350 # code. This triggers additional logging in most environments that call 351 # this script. 352 if [[ -n "${err}" ]]; then 353 die "Cleanup encountered an error." 354 fi 355} 356 357cleanup_on_error() { 358 trap - INT TERM ERR EXIT 359 cleanup 360 die "Cleanup success after an error." 361} 362 363cleanup_on_exit() { 364 trap - INT TERM ERR EXIT 365 cleanup 366} 367 368trap cleanup_on_error INT TERM ERR 369trap cleanup_on_exit EXIT 370 371# extract_file <zip_file> <entry_name> <destination> 372# 373# Extracts |entry_name| from |zip_file| to |destination|. 374extract_file() { 375 local zip_file="$1" 376 local entry_name="$2" 377 local destination="$3" 378 379 # unzip -p won't report error upon ENOSPC. Therefore, create a temp directory 380 # as the destination of the unzip, and move the file to the intended 381 # destination. 382 local output_directory=$( 383 mktemp --directory --tmpdir="${FLAGS_work_dir}" "TEMP.XXXXXX") 384 unzip "${zip_file}" "${entry_name}" -d "${output_directory}" || 385 { rm -rf "${output_directory}"; die "Failed to extract ${entry_name}"; } 386 387 mv "${output_directory}/${entry_name}" "${destination}" 388 rm -rf "${output_directory}" 389} 390 391# extract_image <image> <partitions_array> [partitions_order] 392# 393# Detect the format of the |image| file and extract its updatable partitions 394# into new temporary files. Add the list of partition names and its files to the 395# associative array passed in |partitions_array|. If |partitions_order| is 396# passed, set it to list of partition names in order. 397extract_image() { 398 local image="$1" 399 400 # Brillo images are zip files. We detect the 4-byte magic header of the zip 401 # file. 402 local magic=$(xxd -p -l4 "${image}") 403 if [[ "${magic}" == "504b0304" ]]; then 404 echo "Detected .zip file, extracting Brillo image." 405 extract_image_brillo "$@" 406 return 407 fi 408 409 # Chrome OS images are GPT partitioned disks. We should have the cgpt binary 410 # bundled here and we will use it to extract the partitions, so the GPT 411 # headers must be valid. 412 if cgpt show -q -n "${image}" >/dev/null; then 413 echo "Detected GPT image, extracting Chrome OS image." 414 extract_image_cros "$@" 415 return 416 fi 417 418 die "Couldn't detect the image format of ${image}" 419} 420 421# extract_image_cros <image.bin> <partitions_array> [partitions_order] 422# 423# Extract Chromium OS recovery images into new temporary files. 424extract_image_cros() { 425 local image="$1" 426 local partitions_array="$2" 427 local partitions_order="${3:-}" 428 429 local kernel root 430 kernel=$(create_tempfile "kernel.bin.XXXXXX") 431 CLEANUP_FILES+=("${kernel}") 432 root=$(create_tempfile "root.bin.XXXXXX") 433 CLEANUP_FILES+=("${root}") 434 435 cros_generate_update_payload --extract \ 436 --image "${image}" \ 437 --kern_path "${kernel}" --root_path "${root}" 438 439 # Chrome OS now uses major_version 2 payloads for all boards. 440 # See crbug.com/794404 for more information. 441 FORCE_MAJOR_VERSION="2" 442 443 eval ${partitions_array}[kernel]=\""${kernel}"\" 444 eval ${partitions_array}[root]=\""${root}"\" 445 446 if [[ -n "${partitions_order}" ]]; then 447 eval "${partitions_order}=( \"root\" \"kernel\" )" 448 fi 449 450 local part varname 451 for part in kernel root; do 452 varname="${partitions_array}[${part}]" 453 printf "md5sum of %s: " "${varname}" 454 md5sum "${!varname}" 455 done 456} 457 458# extract_partition_brillo <target_files.zip> <partitions_array> <partition> 459# <part_file> <part_map_file> 460# 461# Extract the <partition> from target_files zip file into <part_file> and its 462# map file into <part_map_file>. 463extract_partition_brillo() { 464 local image="$1" 465 local partitions_array="$2" 466 local part="$3" 467 local part_file="$4" 468 local part_map_file="$5" 469 470 # For each partition, we in turn look for its image file under IMAGES/ and 471 # RADIO/ in the given target_files zip file. 472 local path path_in_zip 473 for path in IMAGES RADIO; do 474 if unzip -l "${image}" "${path}/${part}.img" >/dev/null; then 475 path_in_zip="${path}" 476 break 477 fi 478 done 479 [[ -n "${path_in_zip}" ]] || die "Failed to find ${part}.img" 480 extract_file "${image}" "${path_in_zip}/${part}.img" "${part_file}" 481 482 # If the partition is stored as an Android sparse image file, we need to 483 # convert them to a raw image for the update. 484 local magic=$(xxd -p -l4 "${part_file}") 485 if [[ "${magic}" == "3aff26ed" ]]; then 486 local temp_sparse=$(create_tempfile "${part}.sparse.XXXXXX") 487 echo "Converting Android sparse image ${part}.img to RAW." 488 mv "${part_file}" "${temp_sparse}" 489 simg2img "${temp_sparse}" "${part_file}" 490 rm -f "${temp_sparse}" 491 fi 492 493 # Extract the .map file (if one is available). 494 if unzip -l "${image}" "${path_in_zip}/${part}.map" > /dev/null; then 495 extract_file "${image}" "${path_in_zip}/${part}.map" "${part_map_file}" 496 fi 497 498 # delta_generator only supports images multiple of 4 KiB. For target images 499 # we pad the data with zeros if needed, but for source images we truncate 500 # down the data since the last block of the old image could be padded on 501 # disk with unknown data. 502 local filesize=$(stat -c%s "${part_file}") 503 if [[ $(( filesize % 4096 )) -ne 0 ]]; then 504 if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then 505 echo "Rounding DOWN partition ${part}.img to a multiple of 4 KiB." 506 : $(( filesize = filesize & -4096 )) 507 else 508 echo "Rounding UP partition ${part}.img to a multiple of 4 KiB." 509 : $(( filesize = (filesize + 4095) & -4096 )) 510 fi 511 truncate_file "${part_file}" "${filesize}" 512 fi 513 514 echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes" 515} 516 517# extract_image_brillo <target_files.zip> <partitions_array> [partitions_order] 518# 519# Extract the A/B updated partitions from a Brillo target_files zip file into 520# new temporary files. 521extract_image_brillo() { 522 local image="$1" 523 local partitions_array="$2" 524 local partitions_order="${3:-}" 525 526 local partitions=( "boot" "system" ) 527 local ab_partitions_list 528 ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX") 529 CLEANUP_FILES+=("${ab_partitions_list}") 530 if unzip -l "${image}" "META/ab_partitions.txt" > /dev/null; then 531 extract_file "${image}" "META/ab_partitions.txt" "${ab_partitions_list}" 532 if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then 533 die "Invalid partition names found in the partition list." 534 fi 535 # Get partition list without duplicates. 536 partitions=($(awk '!seen[$0]++' "${ab_partitions_list}")) 537 if [[ ${#partitions[@]} -eq 0 ]]; then 538 die "The list of partitions is empty. Can't generate a payload." 539 fi 540 else 541 warn "No ab_partitions.txt found. Using default." 542 fi 543 echo "List of A/B partitions for ${partitions_array}: ${partitions[@]}" 544 545 if [[ -n "${partitions_order}" ]]; then 546 eval "${partitions_order}=(${partitions[@]})" 547 fi 548 549 # All Brillo updaters support major version 2. 550 FORCE_MAJOR_VERSION="2" 551 552 if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then 553 # Source image 554 local ue_config=$(create_tempfile "ue_config.XXXXXX") 555 CLEANUP_FILES+=("${ue_config}") 556 if unzip -l "${image}" "META/update_engine_config.txt" > /dev/null; then 557 extract_file "${image}" "META/update_engine_config.txt" "${ue_config}" 558 else 559 warn "No update_engine_config.txt found. Assuming pre-release image, \ 560using payload minor version 2" 561 fi 562 # For delta payloads, we use the major and minor version supported by the 563 # old updater. 564 FORCE_MINOR_VERSION=$(read_option_uint "${ue_config}" \ 565 "PAYLOAD_MINOR_VERSION" 2) 566 if [[ -n "${FLAGS_force_minor_version}" ]]; then 567 FORCE_MINOR_VERSION="${FLAGS_force_minor_version}" 568 fi 569 FORCE_MAJOR_VERSION=$(read_option_uint "${ue_config}" \ 570 "PAYLOAD_MAJOR_VERSION" 2) 571 572 # Brillo support for deltas started with minor version 3. 573 if [[ "${FORCE_MINOR_VERSION}" -le 2 ]]; then 574 warn "No delta support from minor version ${FORCE_MINOR_VERSION}. \ 575Disabling deltas for this source version." 576 exit ${EX_UNSUPPORTED_DELTA} 577 fi 578 else 579 # Target image 580 local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX") 581 CLEANUP_FILES+=("${postinstall_config}") 582 if unzip -l "${image}" "META/postinstall_config.txt" > /dev/null; then 583 extract_file "${image}" "META/postinstall_config.txt" \ 584 "${postinstall_config}" 585 POSTINSTALL_CONFIG_FILE="${postinstall_config}" 586 fi 587 local dynamic_partitions_info=$(create_tempfile "dynamic_partitions_info.XXXXXX") 588 CLEANUP_FILES+=("${dynamic_partitions_info}") 589 if unzip -l "${image}" "META/dynamic_partitions_info.txt" > /dev/null; then 590 extract_file "${image}" "META/dynamic_partitions_info.txt" \ 591 "${dynamic_partitions_info}" 592 DYNAMIC_PARTITION_INFO_FILE="${dynamic_partitions_info}" 593 fi 594 local apex_info=$(create_tempfile "apex_info.XXXXXX") 595 CLEANUP_FILES+=("${apex_info}") 596 if unzip -l "${image}" "META/apex_info.pb" > /dev/null; then 597 extract_file "${image}" "META/apex_info.pb" \ 598 "${apex_info}" 599 APEX_INFO_FILE="${apex_info}" 600 fi 601 fi 602 603 local part 604 for part in "${partitions[@]}"; do 605 local part_file=$(create_tempfile "${part}.img.XXXXXX") 606 local part_map_file=$(create_tempfile "${part}.map.XXXXXX") 607 CLEANUP_FILES+=("${part_file}" "${part_map_file}") 608 # Extract partitions in background. 609 extract_partition_brillo "${image}" "${partitions_array}" "${part}" \ 610 "${part_file}" "${part_map_file}" & 611 EXTRACT_IMAGE_PIDS+=("$!") 612 eval "${partitions_array}[\"${part}\"]=\"${part_file}\"" 613 eval "${partitions_array}_MAP[\"${part}\"]=\"${part_map_file}\"" 614 done 615} 616 617# cleanup_partition_array <partitions_array> 618# 619# Remove all empty files in <partitions_array>. 620cleanup_partition_array() { 621 local partitions_array="$1" 622 # Have to use eval to iterate over associative array keys with variable array 623 # names, we should change it to use nameref once bash 4.3 is available 624 # everywhere. 625 for part in $(eval "echo \${!${partitions_array}[@]}"); do 626 local path="${partitions_array}[$part]" 627 if [[ ! -s "${!path}" ]]; then 628 eval "unset ${partitions_array}[${part}]" 629 fi 630 done 631} 632 633extract_payload_images() { 634 local payload_type=$1 635 echo "Extracting images for ${payload_type} update." 636 637 if [[ "${payload_type}" == "delta" ]]; then 638 extract_image "${FLAGS_source_image}" SRC_PARTITIONS 639 fi 640 extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER 641 # Wait for all subprocesses to finish. Not using `wait` since it doesn't die 642 # on non-zero subprocess exit code. Not using `wait ${EXTRACT_IMAGE_PIDS[@]}` 643 # as it gives the status of the last process it has waited for. 644 for pid in ${EXTRACT_IMAGE_PIDS[@]}; do 645 wait ${pid} 646 done 647 cleanup_partition_array SRC_PARTITIONS 648 cleanup_partition_array SRC_PARTITIONS_MAP 649 cleanup_partition_array DST_PARTITIONS 650 cleanup_partition_array DST_PARTITIONS_MAP 651} 652 653get_payload_type() { 654 if [[ -z "${FLAGS_source_image}" ]]; then 655 echo "full" 656 else 657 echo "delta" 658 fi 659} 660 661validate_generate() { 662 [[ -n "${FLAGS_payload}" ]] || 663 die "You must specify an output filename with --payload FILENAME" 664 665 [[ -n "${FLAGS_target_image}" ]] || 666 die "You must specify a target image with --target_image FILENAME" 667} 668 669cmd_generate() { 670 local payload_type=$(get_payload_type) 671 extract_payload_images ${payload_type} 672 673 echo "Generating ${payload_type} update." 674 # Common payload args: 675 GENERATOR_ARGS=( --out_file="${FLAGS_payload}" ) 676 677 local part old_partitions="" new_partitions="" partition_names="" 678 local old_mapfiles="" new_mapfiles="" 679 for part in "${PARTITIONS_ORDER[@]}"; do 680 if [[ -n "${partition_names}" ]]; then 681 partition_names+=":" 682 new_partitions+=":" 683 old_partitions+=":" 684 new_mapfiles+=":" 685 old_mapfiles+=":" 686 fi 687 partition_names+="${part}" 688 new_partitions+="${DST_PARTITIONS[${part}]}" 689 if [ "${FLAGS_full_boot}" == "true" ] && [ "${part}" == "boot" ]; then 690 # Skip boot partition. 691 old_partitions+="" 692 else 693 old_partitions+="${SRC_PARTITIONS[${part}]:-}" 694 fi 695 new_mapfiles+="${DST_PARTITIONS_MAP[${part}]:-}" 696 old_mapfiles+="${SRC_PARTITIONS_MAP[${part}]:-}" 697 done 698 699 # Target image args: 700 GENERATOR_ARGS+=( 701 --partition_names="${partition_names}" 702 --new_partitions="${new_partitions}" 703 --new_mapfiles="${new_mapfiles}" 704 ) 705 706 if [[ "${FLAGS_is_partial_update}" == "true" ]]; then 707 GENERATOR_ARGS+=( --is_partial_update="true" ) 708 # Need at least minor version 7 for partial update, so generate with minor 709 # version 7 if we don't have a source image. Let the delta_generator to fail 710 # the other incompatiable minor versions. 711 if [[ -z "${FORCE_MINOR_VERSION}" ]]; then 712 FORCE_MINOR_VERSION="7" 713 fi 714 fi 715 716 if [[ "${payload_type}" == "delta" ]]; then 717 # Source image args: 718 GENERATOR_ARGS+=( 719 --old_partitions="${old_partitions}" 720 --old_mapfiles="${old_mapfiles}" 721 ) 722 if [[ -n "${FLAGS_disable_fec_computation}" ]]; then 723 GENERATOR_ARGS+=( 724 --disable_fec_computation="${FLAGS_disable_fec_computation}" ) 725 fi 726 if [[ -n "${FLAGS_disable_verity_computation}" ]]; then 727 GENERATOR_ARGS+=( 728 --disable_verity_computation="${FLAGS_disable_verity_computation}" ) 729 fi 730 if [[ -n "${FLAGS_compressor_types}" ]]; then 731 GENERATOR_ARGS+=( 732 --compressor_types="${FLAGS_compressor_types}" ) 733 fi 734 if [[ -n "${FLAGS_enable_zucchini}" ]]; then 735 GENERATOR_ARGS+=( 736 --enable_zucchini="${FLAGS_enable_zucchini}" ) 737 fi 738 if [[ -n "${FLAGS_enable_lz4diff}" ]]; then 739 if [[ "${FLAGS_enable_lz4diff}" == "true" && -z "${FLAGS_liblz4_path}" ]]; then 740 echo "--liblz4_path is required when enable_lz4diff is set to true." 741 exit 1 742 fi 743 GENERATOR_ARGS+=( 744 --enable_lz4diff="${FLAGS_enable_lz4diff}" ) 745 fi 746 if [[ -n "${FLAGS_erofs_compression_param}" ]]; then 747 GENERATOR_ARGS+=( 748 --erofs_compression_param="${FLAGS_erofs_compression_param}" ) 749 fi 750 fi 751 752 if [[ -n "${FLAGS_enable_vabc_xor}" ]]; then 753 GENERATOR_ARGS+=( 754 --enable_vabc_xor="${FLAGS_enable_vabc_xor}" ) 755 fi 756 757 if [[ -n "${FLAGS_disable_vabc}" ]]; then 758 GENERATOR_ARGS+=( 759 --disable_vabc="${FLAGS_disable_vabc}" ) 760 fi 761 762 # minor version is set only for delta or partial payload. 763 if [[ -n "${FORCE_MINOR_VERSION}" ]]; then 764 GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) 765 fi 766 767 if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then 768 GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) 769 fi 770 771 if [[ -n "${FLAGS_metadata_size_file}" ]]; then 772 GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) 773 fi 774 775 if [[ -n "${FLAGS_max_timestamp}" ]]; then 776 GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" ) 777 fi 778 779 if [[ -n "${FLAGS_partition_timestamps}" ]]; then 780 GENERATOR_ARGS+=( --partition_timestamps="${FLAGS_partition_timestamps}" ) 781 fi 782 783 if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then 784 GENERATOR_ARGS+=( 785 --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}" 786 ) 787 fi 788 789 if [[ -n "{DYNAMIC_PARTITION_INFO_FILE}" ]]; then 790 GENERATOR_ARGS+=( 791 --dynamic_partition_info_file="${DYNAMIC_PARTITION_INFO_FILE}" 792 ) 793 fi 794 if [[ -n "{APEX_INFO_FILE}" ]]; then 795 GENERATOR_ARGS+=( 796 --apex_info_file="${APEX_INFO_FILE}" 797 ) 798 fi 799 800 echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}" 801 if [[ -n "${FLAGS_enable_lz4diff}" ]]; then 802 LD_PRELOAD=${LD_PRELOAD}:${FLAGS_liblz4_path} "${GENERATOR}" "${GENERATOR_ARGS[@]}" 803 else 804 "${GENERATOR}" "${GENERATOR_ARGS[@]}" 805 fi 806 807 echo "Done generating ${payload_type} update." 808} 809 810validate_hash() { 811 [[ -n "${FLAGS_signature_size}" ]] || 812 die "You must specify signature size with --signature_size SIZES" 813 814 [[ -n "${FLAGS_unsigned_payload}" ]] || 815 die "You must specify the input unsigned payload with \ 816--unsigned_payload FILENAME" 817 818 [[ -n "${FLAGS_payload_hash_file}" ]] || 819 die "You must specify --payload_hash_file FILENAME" 820 821 [[ -n "${FLAGS_metadata_hash_file}" ]] || 822 die "You must specify --metadata_hash_file FILENAME" 823} 824 825cmd_hash() { 826 "${GENERATOR}" \ 827 --in_file="${FLAGS_unsigned_payload}" \ 828 --signature_size="${FLAGS_signature_size}" \ 829 --out_hash_file="${FLAGS_payload_hash_file}" \ 830 --out_metadata_hash_file="${FLAGS_metadata_hash_file}" 831 832 echo "Done generating hash." 833} 834 835validate_sign() { 836 [[ -n "${FLAGS_signature_size}" ]] || 837 die "You must specify signature size with --signature_size SIZES" 838 839 [[ -n "${FLAGS_unsigned_payload}" ]] || 840 die "You must specify the input unsigned payload with \ 841--unsigned_payload FILENAME" 842 843 [[ -n "${FLAGS_payload}" ]] || 844 die "You must specify the output signed payload with --payload FILENAME" 845 846 [[ -n "${FLAGS_payload_signature_file}" ]] || 847 die "You must specify the payload signature file with \ 848--payload_signature_file SIGNATURES" 849 850 [[ -n "${FLAGS_metadata_signature_file}" ]] || 851 die "You must specify the metadata signature file with \ 852--metadata_signature_file SIGNATURES" 853} 854 855cmd_sign() { 856 GENERATOR_ARGS=( 857 --in_file="${FLAGS_unsigned_payload}" 858 --signature_size="${FLAGS_signature_size}" 859 --payload_signature_file="${FLAGS_payload_signature_file}" 860 --metadata_signature_file="${FLAGS_metadata_signature_file}" 861 --out_file="${FLAGS_payload}" 862 ) 863 864 if [[ -n "${FLAGS_metadata_size_file}" ]]; then 865 GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) 866 fi 867 868 "${GENERATOR}" "${GENERATOR_ARGS[@]}" 869 echo "Done signing payload." 870} 871 872validate_properties() { 873 [[ -n "${FLAGS_payload}" ]] || 874 die "You must specify the payload file with --payload FILENAME" 875 876 [[ -n "${FLAGS_properties_file}" ]] || 877 die "You must specify a non empty --properties_file FILENAME" 878} 879 880cmd_properties() { 881 "${GENERATOR}" \ 882 --in_file="${FLAGS_payload}" \ 883 --properties_file="${FLAGS_properties_file}" 884} 885 886validate_verify_and_check() { 887 [[ -n "${FLAGS_payload}" ]] || 888 die "Error: you must specify an input filename with --payload FILENAME" 889 890 [[ -n "${FLAGS_target_image}" ]] || 891 die "Error: you must specify a target image with --target_image FILENAME" 892} 893 894cmd_verify() { 895 local payload_type=$(get_payload_type) 896 extract_payload_images ${payload_type} 897 898 declare -A TMP_PARTITIONS 899 for part in "${PARTITIONS_ORDER[@]}"; do 900 local tmp_part=$(create_tempfile "tmp_part.bin.XXXXXX") 901 echo "Creating temporary target partition ${tmp_part} for ${part}" 902 CLEANUP_FILES+=("${tmp_part}") 903 TMP_PARTITIONS[${part}]=${tmp_part} 904 local FILESIZE=$(stat -c%s "${DST_PARTITIONS[${part}]}") 905 echo "Truncating ${TMP_PARTITIONS[${part}]} to ${FILESIZE}" 906 truncate_file "${TMP_PARTITIONS[${part}]}" "${FILESIZE}" 907 done 908 909 echo "Verifying ${payload_type} update." 910 # Common payload args: 911 GENERATOR_ARGS=( --in_file="${FLAGS_payload}" ) 912 913 local part old_partitions="" new_partitions="" partition_names="" 914 for part in "${PARTITIONS_ORDER[@]}"; do 915 if [[ -n "${partition_names}" ]]; then 916 partition_names+=":" 917 new_partitions+=":" 918 old_partitions+=":" 919 fi 920 partition_names+="${part}" 921 new_partitions+="${TMP_PARTITIONS[${part}]}" 922 old_partitions+="${SRC_PARTITIONS[${part}]:-}" 923 done 924 925 # Target image args: 926 GENERATOR_ARGS+=( 927 --partition_names="${partition_names}" 928 --new_partitions="${new_partitions}" 929 ) 930 931 if [[ "${payload_type}" == "delta" ]]; then 932 # Source image args: 933 GENERATOR_ARGS+=( 934 --old_partitions="${old_partitions}" 935 ) 936 fi 937 938 if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then 939 GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) 940 fi 941 942 echo "Running delta_generator to verify ${payload_type} payload with args: \ 943${GENERATOR_ARGS[@]}" 944 "${GENERATOR}" "${GENERATOR_ARGS[@]}" || true 945 946 echo "Done applying ${payload_type} update." 947 echo "Checking the newly generated partitions against the target partitions" 948 local need_pause=false 949 for part in "${PARTITIONS_ORDER[@]}"; do 950 local not_str="" 951 if ! cmp "${TMP_PARTITIONS[${part}]}" "${DST_PARTITIONS[${part}]}"; then 952 not_str="in" 953 need_pause=true 954 fi 955 echo "The new partition (${part}) is ${not_str}valid." 956 done 957 # All images will be cleaned up when script exits, pause here to give a chance 958 # to inspect the images. 959 if [[ "$need_pause" == true ]]; then 960 read -n1 -r -s -p "Paused to investigate invalid partitions, \ 961press any key to exit." 962 fi 963} 964 965cmd_check() { 966 local payload_type=$(get_payload_type) 967 extract_payload_images ${payload_type} 968 969 local part dst_partitions="" src_partitions="" 970 for part in "${PARTITIONS_ORDER[@]}"; do 971 if [[ -n "${dst_partitions}" ]]; then 972 dst_partitions+=" " 973 src_partitions+=" " 974 fi 975 dst_partitions+="${DST_PARTITIONS[${part}]}" 976 src_partitions+="${SRC_PARTITIONS[${part}]:-}" 977 done 978 979 # Common payload args: 980 PAYCHECK_ARGS=( "${FLAGS_payload}" --type ${payload_type} \ 981 --part_names ${PARTITIONS_ORDER[@]} \ 982 --dst_part_paths ${dst_partitions} ) 983 984 if [[ ! -z "${SRC_PARTITIONS[@]}" ]]; then 985 PAYCHECK_ARGS+=( --src_part_paths ${src_partitions} ) 986 fi 987 988 echo "Checking ${payload_type} update." 989 check_update_payload ${PAYCHECK_ARGS[@]} --check 990} 991 992# Check that the real generator exists: 993[[ -x "${GENERATOR}" ]] || GENERATOR="$(which delta_generator || true)" 994[[ -x "${GENERATOR}" ]] || die "can't find delta_generator" 995 996case "$COMMAND" in 997 generate) validate_generate 998 cmd_generate 999 ;; 1000 hash) validate_hash 1001 cmd_hash 1002 ;; 1003 sign) validate_sign 1004 cmd_sign 1005 ;; 1006 properties) validate_properties 1007 cmd_properties 1008 ;; 1009 verify) validate_verify_and_check 1010 cmd_verify 1011 ;; 1012 check) validate_verify_and_check 1013 cmd_check 1014 ;; 1015esac 1016