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