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