brillo_update_payload revision 89ff9e3221c358977f9c3124930ee6b559853740
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# 15# Generate command arguments: 16# --payload generated unsigned payload output file 17# --source_image if defined, generate a delta payload from the specified 18# image to the target_image 19# --target_image the target image that should be sent to clients 20# 21# Hash command arguments: 22# --unsigned_payload the input unsigned payload to generate the hash from 23# --signature_size signature sizes in bytes in the following format: 24# "size1:size2[:...]" 25# --payload_hash_file if defined, generate a payload hash and output to the 26# specified file 27# --metadata_hash_file if defined, generate a metadata hash and output to the 28# specified file 29# 30# Sign command arguments: 31# --unsigned_payload the input unsigned payload to insert the signatures 32# --payload the output signed payload 33# --signature_size signature sizes in bytes in the following format: 34# "size1:size2[:...]" 35# --payload_signature_file the payload signature files in the following 36# format: 37# "payload_signature1:payload_signature2[:...]" 38# --metadata_signature_file the metadata signature files in the following 39# format: 40# "metadata_signature1:metadata_signature2[:...]" 41# Note that the number of signature sizes and payload signatures have to match. 42 43# Load common CrOS utilities. Inside the chroot this file is installed in 44# /usr/lib/crosutils. This script may also be called from a zipfile, in which 45# case common.sh will be in the current directory. 46find_common_sh() { 47 local thisdir="$(dirname "$(readlink -f "$0")")" 48 local common_paths=(/usr/lib/crosutils "${thisdir}") 49 local path 50 51 SCRIPT_ROOT="${common_paths[0]}" 52 for path in "${common_paths[@]}"; do 53 if [[ -r "${path}/common.sh" ]]; then 54 SCRIPT_ROOT="${path}" 55 break 56 fi 57 done 58 59 # We have to fake GCLIENT_ROOT in case we're running inside 60 # au_zip enviroment. GCLIENT_ROOT detection became fatal. 61 [[ "${SCRIPT_ROOT}" == "${thisdir}" ]] && export GCLIENT_ROOT="." 62} 63 64find_common_sh 65. "${SCRIPT_ROOT}/common.sh" || exit 1 66 67# Check that a command is specified 68if [[ $# -lt 1 ]]; then 69 echo "Please specify a command [generate|hash|sign]" 70 exit 1 71fi 72 73# Parse command 74case "$1" in 75 generate|hash|sign) 76 COMMAND=$1 77 ;; 78 *) 79 echo "Unrecognized command:" $1 80 exit 1 81 ;; 82esac 83 84shift 85 86# Flags 87DEFINE_string payload "" "Path to output the generated payload file." 88DEFINE_string target_image "" \ 89 "Path to the target image that should be sent to clients." 90DEFINE_string source_image "" \ 91 "Optional: Path to a source image. If specified, this makes\ 92 a delta update." 93DEFINE_string unsigned_payload "" "Path to the generated unsigned payload." 94DEFINE_string signature_size "" \ 95 "Signature sizes in bytes in the following format: size1:size2[:...]" 96DEFINE_string payload_hash_file "" "Optional: Path to output payload hash file." 97DEFINE_string metadata_hash_file "" \ 98 "Optional: Path to output metadata hash file." 99DEFINE_string payload_signature_file "" \ 100 "The payload signatures in the following format:\ 101 payload_signature1:payload_signature2[:...]" 102DEFINE_string metadata_signature_file "" \ 103 "The metatada signatures in the following format:\ 104 metadata_signature1:metadata_signature2[:...]" 105DEFINE_string work_dir "/tmp" "Where to dump temporary files." 106 107# Parse command line flag arguments 108FLAGS "$@" || exit 1 109eval set -- "${FLAGS_ARGV}" 110set -e 111 112# Associative arrays from partition name to file in the source and target 113# images. The size of the updated area must be the size of the file. 114declare -A SRC_PARTITIONS 115declare -A DST_PARTITIONS 116 117# A list of temporary files to remove during cleanup. 118CLEANUP_FILES=() 119 120# Create a temporary file in the work_dir with an optional pattern name. 121# Prints the name of the newly created file. 122create_tempfile() { 123 local pattern="${1:-tempfile.XXXXXX}" 124 mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}" 125} 126 127cleanup() { 128 local err="" 129 rm -f "${CLEANUP_FILES[@]}" || err=1 130 131 # If we are cleaning up after an error, or if we got an error during 132 # cleanup (even if we eventually succeeded) return a non-zero exit 133 # code. This triggers additional logging in most environments that call 134 # this script. 135 if [[ -n "${err}" ]]; then 136 die "Cleanup encountered an error." 137 fi 138} 139 140cleanup_on_error() { 141 trap - INT TERM ERR EXIT 142 cleanup 143 die "Cleanup success after an error." 144} 145 146cleanup_on_exit() { 147 trap - INT TERM ERR EXIT 148 cleanup 149} 150 151trap cleanup_on_error INT TERM ERR 152trap cleanup_on_exit EXIT 153 154# extract_image_cros <image.bin> <partitions_array> 155# 156# Extract Chromium OS recovery images into new temporary files. Add the list 157# of partition names and its files to the associative array passed in 158# partitions_array. 159extract_image_cros() { 160 local image="$1" 161 local partitions_array="$2" 162 163 local kernel root 164 kernel=$(create_tempfile "kernel.bin.XXXXXX") 165 CLEANUP_FILES+=("${kernel}") 166 root=$(create_tempfile "root.bin.XXXXXX") 167 CLEANUP_FILES+=("${root}") 168 169 cros_generate_update_payload --extract \ 170 --image "${image}" \ 171 --kern_path "${kernel}" --root_path "${root}" \ 172 --work_dir "${FLAGS_work_dir}" --outside_chroot 173 174 # When generating legacy Chrome OS images, we need to use "kernel" and "root" 175 # for the partition names. 176 eval ${partitions_array}[kernel]=\""${kernel}"\" 177 eval ${partitions_array}[root]=\""${root}"\" 178 179 local part varname 180 for part in root kernel; do 181 varname="${partitions_array}[${part}]" 182 printf "md5sum of %s: " "${varname}" 183 md5sum "${!varname}" 184 done 185} 186 187validate_generate() { 188 [[ -n "${FLAGS_payload}" ]] || 189 die "Error: you must specify an output filename with --payload FILENAME" 190 191 [[ -n "${FLAGS_target_image}" ]] || 192 die "Error: you must specify a target image with --target_image FILENAME" 193} 194 195cmd_generate() { 196 local payload_type="delta" 197 if [[ -z "${FLAGS_source_image}" ]]; then 198 payload_type="full" 199 fi 200 201 echo "Generating ${payload_type} update" 202 203 # TODO(deymo): Detect the format the image and call the right extract_image 204 # function. 205 extract_image_cros "${FLAGS_target_image}" DST_PARTITIONS 206 if [[ "${payload_type}" == "delta" ]]; then 207 extract_image_cros "${FLAGS_source_image}" SRC_PARTITIONS 208 fi 209 210 GENERATOR_ARGS=( 211 # Common payload args: 212 -out_file="${FLAGS_payload}" 213 # Target image args: 214 # TODO(deymo): Pass the list of partitions to the generator. 215 -new_image="${DST_PARTITIONS[root]}" 216 -new_kernel="${DST_PARTITIONS[kernel]}" 217 ) 218 219 if [[ "${payload_type}" == "delta" ]]; then 220 GENERATOR_ARGS+=( 221 # Source image args: 222 -old_image="${SRC_PARTITIONS[root]}" 223 -old_kernel="${SRC_PARTITIONS[kernel]}" 224 ) 225 fi 226 227 echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}" 228 "${GENERATOR}" "${GENERATOR_ARGS[@]}" 229 230 echo "Done generating ${payload_type} update." 231} 232 233validate_hash() { 234 [[ -n "${FLAGS_signature_size}" ]] || 235 die "Error: you must specify signature size with --signature_size SIZES" 236 237 [[ -n "${FLAGS_unsigned_payload}" ]] || 238 die "Error: you must specify the input unsigned payload with \ 239--unsigned_payload FILENAME" 240 241 [[ -n "${FLAGS_metadata_hash_file}" ]] || 242 [[ -n "${FLAGS_payload_hash_file}" ]] || 243 die "Error: you must specify --metadata_hash_file FILENAME \ 244or --payload_hash_file FILENAME" 245} 246 247cmd_hash() { 248 if [[ -n "${FLAGS_metadata_hash_file}" ]]; then 249 "${GENERATOR}" \ 250 -in_file="${FLAGS_unsigned_payload}" \ 251 -signature_size="${FLAGS_signature_size}" \ 252 -out_metadata_hash_file="${FLAGS_metadata_hash_file}" 253 fi 254 255 if [[ -n "${FLAGS_payload_hash_file}" ]]; then 256 "${GENERATOR}" \ 257 -in_file="${FLAGS_unsigned_payload}" \ 258 -signature_size="${FLAGS_signature_size}" \ 259 -out_hash_file="${FLAGS_payload_hash_file}" 260 fi 261 echo "Done generating hash." 262} 263 264validate_sign() { 265 [[ -n "${FLAGS_signature_size}" ]] || 266 die "Error: you must specify signature size with --signature_size SIZES" 267 268 [[ -n "${FLAGS_unsigned_payload}" ]] || 269 die "Error: you must specify the input unsigned payload with \ 270--unsigned_payload FILENAME" 271 272 [[ -n "${FLAGS_payload}" ]] || 273 die "Error: you must specify the output signed payload with \ 274--payload FILENAME" 275 276 [[ -n "${FLAGS_payload_signature_file}" ]] || 277 die "Error: you must specify the payload signature file with \ 278--payload_signature_file SIGNATURES" 279 280 [[ -n "${FLAGS_metadata_signature_file}" ]] || 281 die "Error: you must specify the metadata signature file with \ 282--metadata_signature_file SIGNATURES" 283} 284 285cmd_sign() { 286 "${GENERATOR}" \ 287 -in_file="${FLAGS_unsigned_payload}" \ 288 -signature_size="${FLAGS_signature_size}" \ 289 -signature_file="${FLAGS_payload_signature_file}" \ 290 -metadata_signature_file="${FLAGS_metadata_signature_file}" \ 291 -out_file="${FLAGS_payload}" 292 echo "Done signing payload." 293} 294 295# TODO: Extract the input zip files once the format is finalized 296 297# Sanity check that the real generator exists: 298GENERATOR="$(which delta_generator)" 299[[ -x "${GENERATOR}" ]] || die "can't find delta_generator" 300 301case "$COMMAND" in 302 generate) validate_generate 303 cmd_generate 304 ;; 305 hash) validate_hash 306 cmd_hash 307 ;; 308 sign) validate_sign 309 cmd_sign 310 ;; 311esac 312