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