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