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