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:
73EX_UNSUPPORTED_DELTA=100
74
75warn() {
76  echo "brillo_update_payload: warning: $*" >&2
77}
78
79die() {
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).
86load_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
98load_shflags
99
100HELP_GENERATE="generate: Generate an unsigned update payload."
101HELP_HASH="hash: Generate the hashes of the unsigned payload and metadata used \
102for signing."
103HELP_SIGN="sign: Insert the signatures into the unsigned payload."
104HELP_PROPERTIES="properties: Extract payload properties to a file."
105HELP_VERIFY="verify: Verify a (signed) update payload."
106
107usage() {
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.
120if [[ $# -lt 1 ]]; then
121  echo "Please specify a command [generate|hash|sign|properties]"
122  exit 1
123fi
124
125# Parse command.
126COMMAND="${1:-}"
127shift
128
129case "${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    ;;
155esac
156
157# Flags
158FLAGS_HELP="Usage: $0 ${COMMAND} [flags]
159${FLAGS_HELP}"
160
161if [[ "${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 \
172payload, should be set to a number higher than the build timestamp of the \
173system running on the device, 0 if not specified."
174fi
175if [[ "${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[:...]"
179fi
180if [[ "${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."
185fi
186if [[ "${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: \
191metadata_signature1:metadata_signature2[:...]"
192  DEFINE_string payload_signature_file "" \
193    "The payload signatures in the following format: \
194payload_signature1:payload_signature2[:...]"
195  DEFINE_string metadata_size_file "" \
196    "Optional: Path to output metadata size."
197fi
198if [[ "${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 \
203be used."
204fi
205if [[ "${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 \
212applied to this."
213fi
214
215DEFINE_string work_dir "${TMPDIR:-/tmp}" "Where to dump temporary files."
216
217# Parse command line flag arguments
218FLAGS "$@" || exit 1
219eval set -- "${FLAGS_ARGV}"
220set -e
221
222# Override the TMPDIR with the passed work_dir flags, which anyway defaults to
223# ${TMPDIR}.
224TMPDIR="${FLAGS_work_dir}"
225export 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.
229declare -A SRC_PARTITIONS
230declare -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.
234declare -A SRC_PARTITIONS_MAP
235declare -A DST_PARTITIONS_MAP
236
237# List of partition names in order.
238declare -a PARTITIONS_ORDER
239
240# A list of temporary files to remove during cleanup.
241CLEANUP_FILES=()
242
243# Global options to force the version of the payload.
244FORCE_MAJOR_VERSION=""
245FORCE_MINOR_VERSION=""
246
247# Path to the postinstall config file in target image if exists.
248POSTINSTALL_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|.
255read_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.
273truncate_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.
283create_tempfile() {
284  local pattern="${1:-tempfile.XXXXXX}"
285  mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}"
286}
287
288cleanup() {
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
301cleanup_on_error() {
302  trap - INT TERM ERR EXIT
303  cleanup
304  die "Cleanup success after an error."
305}
306
307cleanup_on_exit() {
308  trap - INT TERM ERR EXIT
309  cleanup
310}
311
312trap cleanup_on_error INT TERM ERR
313trap 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.
322extract_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.
349extract_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.
391extract_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, \
428using 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}. \
440Disabling 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
506validate_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
514cmd_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
590validate_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
605cmd_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
615validate_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
635cmd_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
652validate_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
660cmd_properties() {
661  "${GENERATOR}" \
662      -in_file="${FLAGS_payload}" \
663      -properties_file="${FLAGS_properties_file}"
664}
665
666validate_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
674cmd_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:
752GENERATOR="$(which delta_generator || true)"
753[[ -x "${GENERATOR}" ]] || die "can't find delta_generator"
754
755case "$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          ;;
771esac
772