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." 223 DEFINE_string security_patch_level "" \ 224 "Optional: security patch level of this OTA" 225 DEFINE_string max_threads "" \ 226 "Optional: specifies max_threads used to generate OTA" 227fi 228if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then 229 DEFINE_string unsigned_payload "" "Path to the input unsigned payload." 230 DEFINE_string signature_size "" \ 231 "Signature sizes in bytes in the following format: size1:size2[:...]" 232fi 233if [[ "${COMMAND}" == "hash" ]]; then 234 DEFINE_string metadata_hash_file "" \ 235 "Optional: Path to output metadata hash file." 236 DEFINE_string payload_hash_file "" \ 237 "Optional: Path to output payload hash file." 238fi 239if [[ "${COMMAND}" == "sign" ]]; then 240 DEFINE_string payload "" \ 241 "Path to output the generated unsigned payload file." 242 DEFINE_string metadata_signature_file "" \ 243 "The metatada signatures in the following format: \ 244metadata_signature1:metadata_signature2[:...]" 245 DEFINE_string payload_signature_file "" \ 246 "The payload signatures in the following format: \ 247payload_signature1:payload_signature2[:...]" 248 DEFINE_string metadata_size_file "" \ 249 "Optional: Path to output metadata size." 250fi 251if [[ "${COMMAND}" == "properties" ]]; then 252 DEFINE_string payload "" \ 253 "Path to the input signed or unsigned payload file." 254 DEFINE_string properties_file "-" \ 255 "Path to output the extracted property files. If '-' is passed stdout will \ 256be used." 257fi 258if [[ "${COMMAND}" == "verify" || "${COMMAND}" == "check" ]]; then 259 DEFINE_string payload "" \ 260 "Path to the input payload file." 261 DEFINE_string target_image "" \ 262 "Path to the target image to verify upon." 263 DEFINE_string source_image "" \ 264 "Optional: Path to a source image. If specified, the delta update is \ 265applied to this." 266fi 267 268DEFINE_string work_dir "${TMPDIR:-/tmp}" "Where to dump temporary files." 269 270# Parse command line flag arguments 271FLAGS "$@" || exit 1 272eval set -- "${FLAGS_ARGV}" 273set -e 274 275# Override the TMPDIR with the passed work_dir flags, which anyway defaults to 276# ${TMPDIR}. 277TMPDIR="${FLAGS_work_dir}" 278export TMPDIR 279 280# Associative arrays from partition name to file in the source and target 281# images. The size of the updated area must be the size of the file. 282declare -A SRC_PARTITIONS 283declare -A DST_PARTITIONS 284 285# Associative arrays for the .map files associated with each src/dst partition 286# file in SRC_PARTITIONS and DST_PARTITIONS. 287declare -A SRC_PARTITIONS_MAP 288declare -A DST_PARTITIONS_MAP 289 290# List of partition names in order. 291declare -a PARTITIONS_ORDER 292 293# A list of PIDs of the extract_image workers. 294EXTRACT_IMAGE_PIDS=() 295 296# A list of temporary files to remove during cleanup. 297CLEANUP_FILES=() 298 299# Global options to force the version of the payload. 300FORCE_MAJOR_VERSION="" 301FORCE_MINOR_VERSION="" 302 303# Path to the postinstall config file in target image if exists. 304POSTINSTALL_CONFIG_FILE="" 305 306# Path to the dynamic partition info file in target image if exists. 307DYNAMIC_PARTITION_INFO_FILE="" 308 309# Path to the META/apex_info.pb found in target build 310APEX_INFO_FILE="" 311 312# read_option_int <file.txt> <option_key> [default_value] 313# 314# Reads the unsigned integer value associated with |option_key| in a key=value 315# file |file.txt|. Prints the read value if found and valid, otherwise prints 316# the |default_value|. 317read_option_uint() { 318 local file_txt="$1" 319 local option_key="$2" 320 local default_value="${3:-}" 321 local value 322 if value=$(grep "^${option_key}=" "${file_txt}" | tail -n 1); then 323 if value=$(echo "${value}" | cut -f 2- -d "=" | grep -E "^[0-9]+$"); then 324 echo "${value}" 325 return 326 fi 327 fi 328 echo "${default_value}" 329} 330 331# truncate_file <file_path> <file_size> 332# 333# Truncate the given |file_path| to |file_size| using python. 334# The truncate binary might not be available. 335truncate_file() { 336 local file_path="$1" 337 local file_size="$2" 338 python -c "open(\"${file_path}\", 'a').truncate(${file_size})" 339} 340 341# Create a temporary file in the work_dir with an optional pattern name. 342# Prints the name of the newly created file. 343create_tempfile() { 344 local pattern="${1:-tempfile.XXXXXX}" 345 mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}" 346} 347 348cleanup() { 349 local err="" 350 rm -f "${CLEANUP_FILES[@]}" || err=1 351 352 # If we are cleaning up after an error, or if we got an error during 353 # cleanup (even if we eventually succeeded) return a non-zero exit 354 # code. This triggers additional logging in most environments that call 355 # this script. 356 if [[ -n "${err}" ]]; then 357 die "Cleanup encountered an error." 358 fi 359} 360 361cleanup_on_error() { 362 trap - INT TERM ERR EXIT 363 cleanup 364 die "Cleanup success after an error." 365} 366 367cleanup_on_exit() { 368 trap - INT TERM ERR EXIT 369 cleanup 370} 371 372trap cleanup_on_error INT TERM ERR 373trap cleanup_on_exit EXIT 374 375# extract_file <zip_file> <entry_name> <destination> 376# 377# Extracts |entry_name| from |zip_file| to |destination|. 378extract_file() { 379 local zip_file="$1" 380 local entry_name="$2" 381 local destination="$3" 382 383 # unzip -p won't report error upon ENOSPC. Therefore, create a temp directory 384 # as the destination of the unzip, and move the file to the intended 385 # destination. 386 local output_directory=$( 387 mktemp --directory --tmpdir="${FLAGS_work_dir}" "TEMP.XXXXXX") 388 unzip "${zip_file}" "${entry_name}" -d "${output_directory}" || 389 { rm -rf "${output_directory}"; die "Failed to extract ${entry_name}"; } 390 391 mv "${output_directory}/${entry_name}" "${destination}" 392 rm -rf "${output_directory}" 393} 394 395# extract_image <image> <partitions_array> [partitions_order] 396# 397# Detect the format of the |image| file and extract its updatable partitions 398# into new temporary files. Add the list of partition names and its files to the 399# associative array passed in |partitions_array|. If |partitions_order| is 400# passed, set it to list of partition names in order. 401extract_image() { 402 local image="$1" 403 404 # Brillo images are zip files. We detect the 4-byte magic header of the zip 405 # file. 406 local magic=$(xxd -p -l4 "${image}") 407 if [[ "${magic}" == "504b0304" ]]; then 408 echo "Detected .zip file, extracting Brillo image." 409 extract_image_brillo "$@" 410 return 411 fi 412 413 # Chrome OS images are GPT partitioned disks. We should have the cgpt binary 414 # bundled here and we will use it to extract the partitions, so the GPT 415 # headers must be valid. 416 if cgpt show -q -n "${image}" >/dev/null; then 417 echo "Detected GPT image, extracting Chrome OS image." 418 extract_image_cros "$@" 419 return 420 fi 421 422 die "Couldn't detect the image format of ${image}" 423} 424 425# extract_image_cros <image.bin> <partitions_array> [partitions_order] 426# 427# Extract Chromium OS recovery images into new temporary files. 428extract_image_cros() { 429 local image="$1" 430 local partitions_array="$2" 431 local partitions_order="${3:-}" 432 433 local kernel root 434 kernel=$(create_tempfile "kernel.bin.XXXXXX") 435 CLEANUP_FILES+=("${kernel}") 436 root=$(create_tempfile "root.bin.XXXXXX") 437 CLEANUP_FILES+=("${root}") 438 439 cros_generate_update_payload --extract \ 440 --image "${image}" \ 441 --kern_path "${kernel}" --root_path "${root}" 442 443 # Chrome OS now uses major_version 2 payloads for all boards. 444 # See crbug.com/794404 for more information. 445 FORCE_MAJOR_VERSION="2" 446 447 eval ${partitions_array}[kernel]=\""${kernel}"\" 448 eval ${partitions_array}[root]=\""${root}"\" 449 450 if [[ -n "${partitions_order}" ]]; then 451 eval "${partitions_order}=( \"root\" \"kernel\" )" 452 fi 453 454 local part varname 455 for part in kernel root; do 456 varname="${partitions_array}[${part}]" 457 printf "md5sum of %s: " "${varname}" 458 md5sum "${!varname}" 459 done 460} 461 462# extract_partition_brillo <target_files.zip> <partitions_array> <partition> 463# <part_file> <part_map_file> 464# 465# Extract the <partition> from target_files zip file into <part_file> and its 466# map file into <part_map_file>. 467extract_partition_brillo() { 468 local image="$1" 469 local partitions_array="$2" 470 local part="$3" 471 local part_file="$4" 472 local part_map_file="$5" 473 474 # For each partition, we in turn look for its image file under IMAGES/ and 475 # RADIO/ in the given target_files zip file. 476 local path path_in_zip 477 for path in IMAGES RADIO; do 478 if unzip -l "${image}" "${path}/${part}.img" >/dev/null; then 479 path_in_zip="${path}" 480 break 481 fi 482 done 483 [[ -n "${path_in_zip}" ]] || die "Failed to find ${part}.img" 484 extract_file "${image}" "${path_in_zip}/${part}.img" "${part_file}" 485 486 # If the partition is stored as an Android sparse image file, we need to 487 # convert them to a raw image for the update. 488 local magic=$(xxd -p -l4 "${part_file}") 489 if [[ "${magic}" == "3aff26ed" ]]; then 490 local temp_sparse=$(create_tempfile "${part}.sparse.XXXXXX") 491 echo "Converting Android sparse image ${part}.img to RAW." 492 mv "${part_file}" "${temp_sparse}" 493 simg2img "${temp_sparse}" "${part_file}" 494 rm -f "${temp_sparse}" 495 fi 496 497 # Extract the .map file (if one is available). 498 if unzip -l "${image}" "${path_in_zip}/${part}.map" > /dev/null; then 499 extract_file "${image}" "${path_in_zip}/${part}.map" "${part_map_file}" 500 fi 501 502 # delta_generator only supports images multiple of 4 KiB. For target images 503 # we pad the data with zeros if needed, but for source images we truncate 504 # down the data since the last block of the old image could be padded on 505 # disk with unknown data. 506 local filesize=$(stat -c%s "${part_file}") 507 if [[ $(( filesize % 4096 )) -ne 0 ]]; then 508 if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then 509 echo "Rounding DOWN partition ${part}.img to a multiple of 4 KiB." 510 : $(( filesize = filesize & -4096 )) 511 else 512 echo "Rounding UP partition ${part}.img to a multiple of 4 KiB." 513 : $(( filesize = (filesize + 4095) & -4096 )) 514 fi 515 truncate_file "${part_file}" "${filesize}" 516 fi 517 518 echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes" 519} 520 521# extract_image_brillo <target_files.zip> <partitions_array> [partitions_order] 522# 523# Extract the A/B updated partitions from a Brillo target_files zip file into 524# new temporary files. 525extract_image_brillo() { 526 local image="$1" 527 local partitions_array="$2" 528 local partitions_order="${3:-}" 529 530 local partitions=( "boot" "system" ) 531 local ab_partitions_list 532 ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX") 533 CLEANUP_FILES+=("${ab_partitions_list}") 534 if unzip -l "${image}" "META/ab_partitions.txt" > /dev/null; then 535 extract_file "${image}" "META/ab_partitions.txt" "${ab_partitions_list}" 536 if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then 537 die "Invalid partition names found in the partition list." 538 fi 539 # Get partition list without duplicates. 540 partitions=($(awk '!seen[$0]++' "${ab_partitions_list}")) 541 if [[ ${#partitions[@]} -eq 0 ]]; then 542 die "The list of partitions is empty. Can't generate a payload." 543 fi 544 else 545 warn "No ab_partitions.txt found. Using default." 546 fi 547 echo "List of A/B partitions for ${partitions_array}: ${partitions[@]}" 548 549 if [[ -n "${partitions_order}" ]]; then 550 eval "${partitions_order}=(${partitions[@]})" 551 fi 552 553 # All Brillo updaters support major version 2. 554 FORCE_MAJOR_VERSION="2" 555 556 if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then 557 # Source image 558 local ue_config=$(create_tempfile "ue_config.XXXXXX") 559 CLEANUP_FILES+=("${ue_config}") 560 if unzip -l "${image}" "META/update_engine_config.txt" > /dev/null; then 561 extract_file "${image}" "META/update_engine_config.txt" "${ue_config}" 562 else 563 warn "No update_engine_config.txt found. Assuming pre-release image, \ 564using payload minor version 2" 565 fi 566 # For delta payloads, we use the major and minor version supported by the 567 # old updater. 568 FORCE_MINOR_VERSION=$(read_option_uint "${ue_config}" \ 569 "PAYLOAD_MINOR_VERSION" 2) 570 if [[ -n "${FLAGS_force_minor_version}" ]]; then 571 FORCE_MINOR_VERSION="${FLAGS_force_minor_version}" 572 fi 573 FORCE_MAJOR_VERSION=$(read_option_uint "${ue_config}" \ 574 "PAYLOAD_MAJOR_VERSION" 2) 575 576 # Brillo support for deltas started with minor version 3. 577 if [[ "${FORCE_MINOR_VERSION}" -le 2 ]]; then 578 warn "No delta support from minor version ${FORCE_MINOR_VERSION}. \ 579Disabling deltas for this source version." 580 exit ${EX_UNSUPPORTED_DELTA} 581 fi 582 else 583 # Target image 584 local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX") 585 CLEANUP_FILES+=("${postinstall_config}") 586 if unzip -l "${image}" "META/postinstall_config.txt" > /dev/null; then 587 extract_file "${image}" "META/postinstall_config.txt" \ 588 "${postinstall_config}" 589 POSTINSTALL_CONFIG_FILE="${postinstall_config}" 590 fi 591 local dynamic_partitions_info=$(create_tempfile "dynamic_partitions_info.XXXXXX") 592 CLEANUP_FILES+=("${dynamic_partitions_info}") 593 if unzip -l "${image}" "META/dynamic_partitions_info.txt" > /dev/null; then 594 extract_file "${image}" "META/dynamic_partitions_info.txt" \ 595 "${dynamic_partitions_info}" 596 DYNAMIC_PARTITION_INFO_FILE="${dynamic_partitions_info}" 597 fi 598 local apex_info=$(create_tempfile "apex_info.XXXXXX") 599 CLEANUP_FILES+=("${apex_info}") 600 if unzip -l "${image}" "META/apex_info.pb" > /dev/null; then 601 extract_file "${image}" "META/apex_info.pb" \ 602 "${apex_info}" 603 APEX_INFO_FILE="${apex_info}" 604 fi 605 fi 606 607 local part 608 for part in "${partitions[@]}"; do 609 local part_file=$(create_tempfile "${part}.img.XXXXXX") 610 local part_map_file=$(create_tempfile "${part}.map.XXXXXX") 611 CLEANUP_FILES+=("${part_file}" "${part_map_file}") 612 # Extract partitions in background. 613 extract_partition_brillo "${image}" "${partitions_array}" "${part}" \ 614 "${part_file}" "${part_map_file}" & 615 EXTRACT_IMAGE_PIDS+=("$!") 616 eval "${partitions_array}[\"${part}\"]=\"${part_file}\"" 617 eval "${partitions_array}_MAP[\"${part}\"]=\"${part_map_file}\"" 618 done 619} 620 621# cleanup_partition_array <partitions_array> 622# 623# Remove all empty files in <partitions_array>. 624cleanup_partition_array() { 625 local partitions_array="$1" 626 # Have to use eval to iterate over associative array keys with variable array 627 # names, we should change it to use nameref once bash 4.3 is available 628 # everywhere. 629 for part in $(eval "echo \${!${partitions_array}[@]}"); do 630 local path="${partitions_array}[$part]" 631 if [[ ! -s "${!path}" ]]; then 632 eval "unset ${partitions_array}[${part}]" 633 fi 634 done 635} 636 637extract_payload_images() { 638 local payload_type=$1 639 echo "Extracting images for ${payload_type} update." 640 641 if [[ "${payload_type}" == "delta" ]]; then 642 extract_image "${FLAGS_source_image}" SRC_PARTITIONS 643 fi 644 extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER 645 # Wait for all subprocesses to finish. Not using `wait` since it doesn't die 646 # on non-zero subprocess exit code. Not using `wait ${EXTRACT_IMAGE_PIDS[@]}` 647 # as it gives the status of the last process it has waited for. 648 for pid in ${EXTRACT_IMAGE_PIDS[@]}; do 649 wait ${pid} 650 done 651 cleanup_partition_array SRC_PARTITIONS 652 cleanup_partition_array SRC_PARTITIONS_MAP 653 cleanup_partition_array DST_PARTITIONS 654 cleanup_partition_array DST_PARTITIONS_MAP 655} 656 657get_payload_type() { 658 if [[ -z "${FLAGS_source_image}" ]]; then 659 echo "full" 660 else 661 echo "delta" 662 fi 663} 664 665validate_generate() { 666 [[ -n "${FLAGS_payload}" ]] || 667 die "You must specify an output filename with --payload FILENAME" 668 669 [[ -n "${FLAGS_target_image}" ]] || 670 die "You must specify a target image with --target_image FILENAME" 671} 672 673cmd_generate() { 674 local payload_type=$(get_payload_type) 675 extract_payload_images ${payload_type} 676 677 echo "Generating ${payload_type} update." 678 # Common payload args: 679 GENERATOR_ARGS=( --out_file="${FLAGS_payload}" ) 680 681 local part old_partitions="" new_partitions="" partition_names="" 682 local old_mapfiles="" new_mapfiles="" 683 for part in "${PARTITIONS_ORDER[@]}"; do 684 if [[ -n "${partition_names}" ]]; then 685 partition_names+=":" 686 new_partitions+=":" 687 old_partitions+=":" 688 new_mapfiles+=":" 689 old_mapfiles+=":" 690 fi 691 partition_names+="${part}" 692 new_partitions+="${DST_PARTITIONS[${part}]}" 693 if [ "${FLAGS_full_boot}" == "true" ] && [ "${part}" == "boot" ]; then 694 # Skip boot partition. 695 old_partitions+="" 696 else 697 old_partitions+="${SRC_PARTITIONS[${part}]:-}" 698 fi 699 new_mapfiles+="${DST_PARTITIONS_MAP[${part}]:-}" 700 old_mapfiles+="${SRC_PARTITIONS_MAP[${part}]:-}" 701 done 702 703 # Target image args: 704 GENERATOR_ARGS+=( 705 --partition_names="${partition_names}" 706 --new_partitions="${new_partitions}" 707 --new_mapfiles="${new_mapfiles}" 708 ) 709 710 if [[ "${FLAGS_is_partial_update}" == "true" ]]; then 711 GENERATOR_ARGS+=( --is_partial_update="true" ) 712 # Need at least minor version 7 for partial update, so generate with minor 713 # version 7 if we don't have a source image. Let the delta_generator to fail 714 # the other incompatiable minor versions. 715 if [[ -z "${FORCE_MINOR_VERSION}" ]]; then 716 FORCE_MINOR_VERSION="7" 717 fi 718 fi 719 720 if [[ "${payload_type}" == "delta" ]]; then 721 # Source image args: 722 GENERATOR_ARGS+=( 723 --old_partitions="${old_partitions}" 724 --old_mapfiles="${old_mapfiles}" 725 ) 726 if [[ -n "${FLAGS_disable_fec_computation}" ]]; then 727 GENERATOR_ARGS+=( 728 --disable_fec_computation="${FLAGS_disable_fec_computation}" ) 729 fi 730 if [[ -n "${FLAGS_disable_verity_computation}" ]]; then 731 GENERATOR_ARGS+=( 732 --disable_verity_computation="${FLAGS_disable_verity_computation}" ) 733 fi 734 if [[ -n "${FLAGS_compressor_types}" ]]; then 735 GENERATOR_ARGS+=( 736 --compressor_types="${FLAGS_compressor_types}" ) 737 fi 738 if [[ -n "${FLAGS_enable_zucchini}" ]]; then 739 GENERATOR_ARGS+=( 740 --enable_zucchini="${FLAGS_enable_zucchini}" ) 741 fi 742 if [[ -n "${FLAGS_enable_lz4diff}" ]]; then 743 if [[ "${FLAGS_enable_lz4diff}" == "true" && -z "${FLAGS_liblz4_path}" ]]; then 744 echo "--liblz4_path is required when enable_lz4diff is set to true." 745 exit 1 746 fi 747 GENERATOR_ARGS+=( 748 --enable_lz4diff="${FLAGS_enable_lz4diff}" ) 749 fi 750 if [[ -n "${FLAGS_erofs_compression_param}" ]]; then 751 GENERATOR_ARGS+=( 752 --erofs_compression_param="${FLAGS_erofs_compression_param}" ) 753 fi 754 fi 755 756 if [[ -n "${FLAGS_enable_vabc_xor}" ]]; then 757 GENERATOR_ARGS+=( 758 --enable_vabc_xor="${FLAGS_enable_vabc_xor}" ) 759 fi 760 761 if [[ -n "${FLAGS_disable_vabc}" ]]; then 762 GENERATOR_ARGS+=( 763 --disable_vabc="${FLAGS_disable_vabc}" ) 764 fi 765 766 if [[ -n "${FLAGS_max_threads}" ]]; then 767 GENERATOR_ARGS+=( 768 --max_threads="${FLAGS_max_threads}" ) 769 fi 770 771 # minor version is set only for delta or partial payload. 772 if [[ -n "${FORCE_MINOR_VERSION}" ]]; then 773 GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) 774 fi 775 776 if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then 777 GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) 778 fi 779 780 if [[ -n "${FLAGS_metadata_size_file}" ]]; then 781 GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) 782 fi 783 784 if [[ -n "${FLAGS_max_timestamp}" ]]; then 785 GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" ) 786 fi 787 788 if [[ -n "${FLAGS_security_patch_level}" ]]; then 789 GENERATOR_ARGS+=( --security_patch_level="${FLAGS_security_patch_level}" ) 790 fi 791 792 if [[ -n "${FLAGS_partition_timestamps}" ]]; then 793 GENERATOR_ARGS+=( --partition_timestamps="${FLAGS_partition_timestamps}" ) 794 fi 795 796 if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then 797 GENERATOR_ARGS+=( 798 --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}" 799 ) 800 fi 801 802 if [[ -n "{DYNAMIC_PARTITION_INFO_FILE}" ]]; then 803 GENERATOR_ARGS+=( 804 --dynamic_partition_info_file="${DYNAMIC_PARTITION_INFO_FILE}" 805 ) 806 fi 807 if [[ -n "{APEX_INFO_FILE}" ]]; then 808 GENERATOR_ARGS+=( 809 --apex_info_file="${APEX_INFO_FILE}" 810 ) 811 fi 812 813 echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}" 814 if [[ -n "${FLAGS_enable_lz4diff}" ]]; then 815 LD_PRELOAD=${LD_PRELOAD}:${FLAGS_liblz4_path} "${GENERATOR}" "${GENERATOR_ARGS[@]}" 816 else 817 "${GENERATOR}" "${GENERATOR_ARGS[@]}" 818 fi 819 820 echo "Done generating ${payload_type} update." 821} 822 823validate_hash() { 824 [[ -n "${FLAGS_signature_size}" ]] || 825 die "You must specify signature size with --signature_size SIZES" 826 827 [[ -n "${FLAGS_unsigned_payload}" ]] || 828 die "You must specify the input unsigned payload with \ 829--unsigned_payload FILENAME" 830 831 [[ -n "${FLAGS_payload_hash_file}" ]] || 832 die "You must specify --payload_hash_file FILENAME" 833 834 [[ -n "${FLAGS_metadata_hash_file}" ]] || 835 die "You must specify --metadata_hash_file FILENAME" 836} 837 838cmd_hash() { 839 "${GENERATOR}" \ 840 --in_file="${FLAGS_unsigned_payload}" \ 841 --signature_size="${FLAGS_signature_size}" \ 842 --out_hash_file="${FLAGS_payload_hash_file}" \ 843 --out_metadata_hash_file="${FLAGS_metadata_hash_file}" 844 845 echo "Done generating hash." 846} 847 848validate_sign() { 849 [[ -n "${FLAGS_signature_size}" ]] || 850 die "You must specify signature size with --signature_size SIZES" 851 852 [[ -n "${FLAGS_unsigned_payload}" ]] || 853 die "You must specify the input unsigned payload with \ 854--unsigned_payload FILENAME" 855 856 [[ -n "${FLAGS_payload}" ]] || 857 die "You must specify the output signed payload with --payload FILENAME" 858 859 [[ -n "${FLAGS_payload_signature_file}" ]] || 860 die "You must specify the payload signature file with \ 861--payload_signature_file SIGNATURES" 862 863 [[ -n "${FLAGS_metadata_signature_file}" ]] || 864 die "You must specify the metadata signature file with \ 865--metadata_signature_file SIGNATURES" 866} 867 868cmd_sign() { 869 GENERATOR_ARGS=( 870 --in_file="${FLAGS_unsigned_payload}" 871 --signature_size="${FLAGS_signature_size}" 872 --payload_signature_file="${FLAGS_payload_signature_file}" 873 --metadata_signature_file="${FLAGS_metadata_signature_file}" 874 --out_file="${FLAGS_payload}" 875 ) 876 877 if [[ -n "${FLAGS_metadata_size_file}" ]]; then 878 GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) 879 fi 880 881 "${GENERATOR}" "${GENERATOR_ARGS[@]}" 882 echo "Done signing payload." 883} 884 885validate_properties() { 886 [[ -n "${FLAGS_payload}" ]] || 887 die "You must specify the payload file with --payload FILENAME" 888 889 [[ -n "${FLAGS_properties_file}" ]] || 890 die "You must specify a non empty --properties_file FILENAME" 891} 892 893cmd_properties() { 894 "${GENERATOR}" \ 895 --in_file="${FLAGS_payload}" \ 896 --properties_file="${FLAGS_properties_file}" 897} 898 899validate_verify_and_check() { 900 [[ -n "${FLAGS_payload}" ]] || 901 die "Error: you must specify an input filename with --payload FILENAME" 902 903 [[ -n "${FLAGS_target_image}" ]] || 904 die "Error: you must specify a target image with --target_image FILENAME" 905} 906 907cmd_verify() { 908 local payload_type=$(get_payload_type) 909 extract_payload_images ${payload_type} 910 911 declare -A TMP_PARTITIONS 912 for part in "${PARTITIONS_ORDER[@]}"; do 913 local tmp_part=$(create_tempfile "tmp_part.bin.XXXXXX") 914 echo "Creating temporary target partition ${tmp_part} for ${part}" 915 CLEANUP_FILES+=("${tmp_part}") 916 TMP_PARTITIONS[${part}]=${tmp_part} 917 local FILESIZE=$(stat -c%s "${DST_PARTITIONS[${part}]}") 918 echo "Truncating ${TMP_PARTITIONS[${part}]} to ${FILESIZE}" 919 truncate_file "${TMP_PARTITIONS[${part}]}" "${FILESIZE}" 920 done 921 922 echo "Verifying ${payload_type} update." 923 # Common payload args: 924 GENERATOR_ARGS=( --in_file="${FLAGS_payload}" ) 925 926 local part old_partitions="" new_partitions="" partition_names="" 927 for part in "${PARTITIONS_ORDER[@]}"; do 928 if [[ -n "${partition_names}" ]]; then 929 partition_names+=":" 930 new_partitions+=":" 931 old_partitions+=":" 932 fi 933 partition_names+="${part}" 934 new_partitions+="${TMP_PARTITIONS[${part}]}" 935 old_partitions+="${SRC_PARTITIONS[${part}]:-}" 936 done 937 938 # Target image args: 939 GENERATOR_ARGS+=( 940 --partition_names="${partition_names}" 941 --new_partitions="${new_partitions}" 942 ) 943 944 if [[ "${payload_type}" == "delta" ]]; then 945 # Source image args: 946 GENERATOR_ARGS+=( 947 --old_partitions="${old_partitions}" 948 ) 949 fi 950 951 if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then 952 GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) 953 fi 954 955 echo "Running delta_generator to verify ${payload_type} payload with args: \ 956${GENERATOR_ARGS[@]}" 957 "${GENERATOR}" "${GENERATOR_ARGS[@]}" || true 958 959 echo "Done applying ${payload_type} update." 960 echo "Checking the newly generated partitions against the target partitions" 961 local need_pause=false 962 for part in "${PARTITIONS_ORDER[@]}"; do 963 local not_str="" 964 if ! cmp "${TMP_PARTITIONS[${part}]}" "${DST_PARTITIONS[${part}]}"; then 965 not_str="in" 966 need_pause=true 967 fi 968 echo "The new partition (${part}) is ${not_str}valid." 969 done 970 # All images will be cleaned up when script exits, pause here to give a chance 971 # to inspect the images. 972 if [[ "$need_pause" == true ]]; then 973 read -n1 -r -s -p "Paused to investigate invalid partitions, \ 974press any key to exit." 975 fi 976} 977 978cmd_check() { 979 local payload_type=$(get_payload_type) 980 extract_payload_images ${payload_type} 981 982 local part dst_partitions="" src_partitions="" 983 for part in "${PARTITIONS_ORDER[@]}"; do 984 if [[ -n "${dst_partitions}" ]]; then 985 dst_partitions+=" " 986 src_partitions+=" " 987 fi 988 dst_partitions+="${DST_PARTITIONS[${part}]}" 989 src_partitions+="${SRC_PARTITIONS[${part}]:-}" 990 done 991 992 # Common payload args: 993 PAYCHECK_ARGS=( "${FLAGS_payload}" --type ${payload_type} \ 994 --part_names ${PARTITIONS_ORDER[@]} \ 995 --dst_part_paths ${dst_partitions} ) 996 997 if [[ ! -z "${SRC_PARTITIONS[@]}" ]]; then 998 PAYCHECK_ARGS+=( --src_part_paths ${src_partitions} ) 999 fi 1000 1001 echo "Checking ${payload_type} update." 1002 check_update_payload ${PAYCHECK_ARGS[@]} --check 1003} 1004 1005# Check that the real generator exists: 1006[[ -x "${GENERATOR}" ]] || GENERATOR="$(which delta_generator || true)" 1007[[ -x "${GENERATOR}" ]] || die "can't find delta_generator" 1008 1009case "$COMMAND" in 1010 generate) validate_generate 1011 cmd_generate 1012 ;; 1013 hash) validate_hash 1014 cmd_hash 1015 ;; 1016 sign) validate_sign 1017 cmd_sign 1018 ;; 1019 properties) validate_properties 1020 cmd_properties 1021 ;; 1022 verify) validate_verify_and_check 1023 cmd_verify 1024 ;; 1025 check) validate_verify_and_check 1026 cmd_check 1027 ;; 1028esac 1029