blob: c3b5ebcca7458c7f46cd9bd1b9693919075420d6 [file] [log] [blame]
Zdenek Behan24a75572011-02-17 03:44:461#!/bin/bash
2
Darin Petkovc3fd90c2011-05-11 21:23:003# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Zdenek Behan24a75572011-02-17 03:44:464# 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 Chromium OS update for use by the update engine.
8# If a source .bin is specified, the update is assumed to be a delta update.
9
Zdenek Behan24a75572011-02-17 03:44:4610# Load common CrOS utilities. Inside the chroot this file is installed in
David James7c2e2f72012-07-10 17:17:1011# /usr/lib/crosutils. This script may also be called from a zipfile, in which
12# case common.sh will be in the current directory.
Zdenek Behan24a75572011-02-17 03:44:4613find_common_sh() {
Zdenek Behand6952232011-03-01 01:52:4814 local thisdir="$(dirname "$(readlink -f "$0")")"
15 local common_paths=(/usr/lib/crosutils "${thisdir}")
Zdenek Behan24a75572011-02-17 03:44:4616 local path
17
David James7c2e2f72012-07-10 17:17:1018 SCRIPT_ROOT="${common_paths[0]}"
Zdenek Behan24a75572011-02-17 03:44:4619 for path in "${common_paths[@]}"; do
Gabe Black61c68462014-10-11 09:52:5120 if [[ -r "${path}/common.sh" ]]; then
David James7c2e2f72012-07-10 17:17:1021 SCRIPT_ROOT="${path}"
Zdenek Behan24a75572011-02-17 03:44:4622 break
23 fi
24 done
Zdenek Behand6952232011-03-01 01:52:4825
26 # HACK(zbehan): We have to fake GCLIENT_ROOT in case we're running inside
27 # au_zip enviroment. GCLIENT_ROOT detection became fatal...
Gabe Black61c68462014-10-11 09:52:5128 [[ "${SCRIPT_ROOT}" == "${thisdir}" ]] && export GCLIENT_ROOT="."
Zdenek Behan24a75572011-02-17 03:44:4629}
30
31find_common_sh
David James7c2e2f72012-07-10 17:17:1032. "${SCRIPT_ROOT}/common.sh" || exit 1
Zdenek Behan24a75572011-02-17 03:44:4633
Gabe Blackdbadc172014-10-12 09:18:5634DEFINE_string image "" "The image that should be sent to clients."
35DEFINE_string src_image "" "Optional: a source image. If specified, this makes\
36 a delta update."
37DEFINE_string output "" "Output file"
38DEFINE_boolean outside_chroot "${FLAGS_FALSE}" "Running outside of chroot."
39DEFINE_string private_key "" "Path to private key in .pem format."
Sen Jiangf5fa15c2015-10-26 18:29:2440DEFINE_string out_payload_hash_file "" "Path to output payload hash file."
Gabe Blackdbadc172014-10-12 09:18:5641DEFINE_string out_metadata_hash_file "" "Path to output metadata hash file."
42DEFINE_boolean extract "${FLAGS_FALSE}" "If set, extract old/new kernel/rootfs \
43to [old|new]_[kern|root].dat. Useful for debugging (default: false)"
44DEFINE_boolean full_kernel "${FLAGS_FALSE}" "Generate a full kernel update \
45even if generating a delta update (default: false)"
Alex Deymo49b2e1e2015-06-27 01:17:1946DEFINE_string chunk_size "" \
47 "Delta payload chunk size (-1 means whole files)"
Gabe Blackdbadc172014-10-12 09:18:5648
49DEFINE_string src_channel "" "Channel of the src image."
50DEFINE_string src_board "" "Board of the src image."
51DEFINE_string src_version "" "Version of the src image."
52DEFINE_string src_key "" "Key of the src image."
53DEFINE_string src_build_channel "" "Channel of the build of the src image."
54DEFINE_string src_build_version "" "Version of the build of the src image."
55
56DEFINE_string channel "" "Channel of the target image."
57DEFINE_string board "" "Board of the target image."
58DEFINE_string version "" "Version of the target image."
59DEFINE_string key "" "Key of the target image."
60DEFINE_string build_channel "" "Channel of the build of the target image."
61DEFINE_string build_version "" "Version of the build of the target image."
62
63# Because we archive/call old versions of this script, we can't easily remove
64# command line options, even if we ignore this one now.
65DEFINE_boolean patch_kernel "${FLAGS_FALSE}" "Ignored. Present for \
66compatibility."
67
68# Specifying any of the following will cause it to not be cleaned up upon exit.
69DEFINE_string kern_path "" "File path for extracting the kernel partition."
70DEFINE_string root_path "" "File path for extracting the rootfs partition."
71DEFINE_string src_kern_path "" \
72 "File path for extracting the source kernel partition."
73DEFINE_string src_root_path "" \
74 "File path for extracting the source rootfs partition."
75
76DEFINE_string work_dir "" "Where to dump temporary files."
77
78
79# Parse command line
80FLAGS "$@" || exit 1
81eval set -- "${FLAGS_ARGV}"
82
Alex Deymoc3fb0812015-07-06 21:05:4483STATE_PART_NUM=1
Alex Deymo67ed8ea2015-07-06 19:26:3084ROOTFS_PART_NUM=3
85
Gabe Black034cac12014-10-18 01:23:0586SRC_KERNEL=""
87SRC_ROOT=""
88DST_KERNEL=""
89DST_ROOT=""
Don Garrett47804922014-03-25 03:11:4190
Alex Deymo8ab30b12015-07-10 02:52:2991# We include cgpt in the au-generator.zip, so we can call it directly here. We
92# don't use chromeos-common.sh versions because we need to not run them as root.
93
94# Usage: gpt_part_offset <image> <partition_number>
95# Return the start sector number of the partition number |partition_number| in
96# the GPT image |image|.
97gpt_part_offset() {
98 cgpt show -b -i "$2" "$1"
99}
100
101# Usage: gpt_part_size <image> <partition_number>
102# Return the size of the partition number |partition_number| in the GPT image
103#|image|, in number of sectors.
104gpt_part_size() {
105 cgpt show -s -i "$2" "$1"
106}
107
Zdenek Behan24a75572011-02-17 03:44:46108cleanup() {
Don Garrett79f1f902014-03-26 18:14:31109 local err=""
110
Gabe Black034cac12014-10-18 01:23:05111 if [[ -z "${FLAGS_src_kern_path}" ]]; then
112 rm -f "${SRC_KERNEL}" || err=1
Gilad Arnold9337bcf2013-12-27 22:50:15113 fi
Gabe Black034cac12014-10-18 01:23:05114 if [[ -z "${FLAGS_src_root_path}" ]]; then
115 rm -f "${SRC_ROOT}" || err=1
Gilad Arnold9337bcf2013-12-27 22:50:15116 fi
Gabe Black034cac12014-10-18 01:23:05117 if [[ -z "${FLAGS_kern_path}" ]]; then
118 rm -f "${DST_KERNEL}" || err=1
119 fi
120 if [[ -z "${FLAGS_root_path}" ]]; then
121 rm -f "${DST_ROOT}" || err=1
122 fi
Don Garrett79f1f902014-03-26 18:14:31123
124 # If we are cleaning up after an error, or if we got an error during
125 # cleanup (even if we eventually succeeded) return a non-zero exit
126 # code. This triggers additional logging in most environments that call
127 # this script.
Gabe Black61c68462014-10-11 09:52:51128 if [[ -n "${err}" ]]; then
Gabe Blackdbadc172014-10-12 09:18:56129 die "Cleanup encountered an error."
Don Garrett79f1f902014-03-26 18:14:31130 fi
Zdenek Behan24a75572011-02-17 03:44:46131}
132
Gabe Blackdbadc172014-10-12 09:18:56133cleanup_on_error() {
134 trap - INT TERM ERR EXIT
135 cleanup
136 die "Cleanup success after an error."
137}
138
139cleanup_on_exit() {
140 trap - INT TERM ERR EXIT
141 cleanup
142}
143
144trap cleanup_on_error INT TERM ERR
145trap cleanup_on_exit EXIT
146
Gabe Black034cac12014-10-18 01:23:05147extract_partition_to_temp_file() {
148 local filename="$1"
149 local partition="$2"
150 local temp_file="$3"
151 if [[ -z "${temp_file}" ]]; then
152 temp_file=$(mktemp --tmpdir="${FLAGS_work_dir}" \
153 cros_generate_update_payload.XXXXXX)
154 fi
155 echo "${temp_file}"
Gabe Black0b42ad42014-09-26 00:32:15156
Gabe Black034cac12014-10-18 01:23:05157 # Keep `local` decl split from assignment so return code is checked.
158 local offset length
Alex Deymo8ab30b12015-07-10 02:52:29159 offset=$(gpt_part_offset "${filename}" ${partition}) # 512-byte sectors
160 length=$(gpt_part_size "${filename}" ${partition}) # 512-byte sectors
Alex Deymocbc3e332015-07-06 19:37:06161 dd if="${filename}" of="${temp_file}" bs=8M iflag=skip_bytes,count_bytes \
162 count="$(( length * 512 ))" skip="$(( offset * 512 ))" status=none
Gabe Black034cac12014-10-18 01:23:05163}
164
165patch_kernel() {
Alex Deymoc3fb0812015-07-06 21:05:44166 local image="$1"
167 local kern_file="$2"
168 local err=""
Gabe Black034cac12014-10-18 01:23:05169
Alex Deymoc3fb0812015-07-06 21:05:44170 local state_out vblock
171 state_out=$(extract_partition_to_temp_file "${image}" "${STATE_PART_NUM}" "")
172 vblock=$(mktemp --tmpdir="${FLAGS_work_dir}" vmlinuz_hd.vblock.XXXXXX)
173 e2cp "${state_out}:/vmlinuz_hd.vblock" "${vblock}" || err+="e2cp failed."
174 dd if="${vblock}" of="${kern_file}" conv=notrunc status=none ||
175 err+="dd failed."
176 rm -f "${state_out}" "${vblock}"
177
178 if [[ -n "${err}" ]]; then
179 die "Error patching the kernel: ${err}"
180 fi
Gabe Black0b42ad42014-09-26 00:32:15181}
182
183extract_kern() {
Gabe Black034cac12014-10-18 01:23:05184 local bin_file="$1"
Zdenek Behan24a75572011-02-17 03:44:46185 local kern_out="$2"
Zdenek Behan24a75572011-02-17 03:44:46186
Gabe Black034cac12014-10-18 01:23:05187 kern_out=$(extract_partition_to_temp_file "${bin_file}" 4 "${kern_out}")
Alex Deymob4d749e2015-11-05 22:37:12188 if cmp /dev/zero "${kern_out}" -n 65536 -s; then
Gabe Black0b42ad42014-09-26 00:32:15189 warn "${bin_file}: Kernel B is empty, patching kernel A."
Gabe Black034cac12014-10-18 01:23:05190 extract_partition_to_temp_file "${bin_file}" 2 "${kern_out}" > /dev/null
191 patch_kernel "${bin_file}" "${kern_out}" >&2
Zdenek Behan24a75572011-02-17 03:44:46192 fi
Gabe Black0b42ad42014-09-26 00:32:15193 echo "${kern_out}"
194}
Zdenek Behan24a75572011-02-17 03:44:46195
Alex Deymo9323cc62015-09-29 04:19:12196# ext2fs_size <rootfs>
197# Prints the size in bytes of the ext2 filesystem passed in |rootfs|. In case of
198# error it returns 1 and doesn't print any output.
199ext2fs_size() {
200 local rootfs="$1"
201
202 # dumpe2fs is normally installed in /sbin but doesn't require root.
203 local PATH="${PATH}:/sbin"
204 dumpe2fs "${rootfs}" >/dev/null 2>&1 || return 1
205 local fs_blocs fs_blocksize
206 fs_blocks=$(dumpe2fs -h "${rootfs}" 2>/dev/null | \
207 grep "^Block count:" | cut -f 2 -d :)
208 fs_blocksize=$(dumpe2fs -h "${rootfs}" 2>/dev/null | \
209 grep "^Block size:" | cut -f 2 -d :)
210 echo $(( fs_blocks * fs_blocksize ))
211}
212
213# extract_root <bin_file> <root_out>
214# Extract the rootfs partition from the gpt image |bin_file| and store it in
215# |root_out|. If |root_out| is empty, a new temp file will be used. Prints the
216# filename where the rootfs was stored.
Gabe Black0b42ad42014-09-26 00:32:15217extract_root() {
Gabe Black034cac12014-10-18 01:23:05218 local bin_file="$1"
Gabe Black0b42ad42014-09-26 00:32:15219 local root_out="$2"
220
Alex Deymo9323cc62015-09-29 04:19:12221 root_out=$(extract_partition_to_temp_file "${bin_file}" "${ROOTFS_PART_NUM}" \
222 "${root_out}")
223
224 # We only update the filesystem part of the partition, which is stored in the
225 # gpt script.
226 local root_out_size
227 if root_out_size=$(ext2fs_size "${root_out}"); then
228 truncate --size="${root_out_size}" "${root_out}"
229 echo "Truncated root to ${root_out_size} bytes." >&2
230 else
231 die "Error truncating the rootfs to the filesystem size."
232 fi
233
234 echo "${root_out}"
Zdenek Behan24a75572011-02-17 03:44:46235}
236
Gabe Black61c68462014-10-11 09:52:51237if [[ -n "${FLAGS_src_image}" ]] && \
238 [[ "${FLAGS_outside_chroot}" -eq "${FLAGS_FALSE}" ]]; then
Zdenek Behan24a75572011-02-17 03:44:46239 # We need to be in the chroot for generating delta images.
240 # by specifying --outside_chroot you can choose not to assert
241 # this will allow us to run this script outside chroot.
242 # Running this script outside chroot requires copying delta_generator binary
243 # and also copying few shared libraries with it.
244 assert_inside_chroot
245fi
246
Gabe Black61c68462014-10-11 09:52:51247if [[ "${FLAGS_extract}" -eq "${FLAGS_TRUE}" ]]; then
248 if [[ -n "${FLAGS_src_image}" ]]; then
Gabe Black034cac12014-10-18 01:23:05249 SRC_KERN_PATH="${FLAGS_src_kern_path:-old_kern.dat}"
250 SRC_ROOT_PATH="${FLAGS_src_root_path:-old_root.dat}"
251 extract_kern "${FLAGS_src_image}" "${SRC_KERN_PATH}"
252 extract_root "${FLAGS_src_image}" "${SRC_ROOT_PATH}"
Zdenek Behan24a75572011-02-17 03:44:46253 fi
Gabe Black61c68462014-10-11 09:52:51254 if [[ -n "${FLAGS_image}" ]]; then
Gabe Black034cac12014-10-18 01:23:05255 KERN_PATH="${FLAGS_kern_path:-new_kern.dat}"
256 ROOT_PATH="${FLAGS_root_path:-new_root.dat}"
257 extract_kern "${FLAGS_image}" "${KERN_PATH}"
258 extract_root "${FLAGS_image}" "${ROOT_PATH}"
Zdenek Behan24a75572011-02-17 03:44:46259 fi
260 echo Done extracting kernel/root
261 exit 0
262fi
263
Gabe Black61c68462014-10-11 09:52:51264[[ -n "${FLAGS_output}" ]] ||
Darin Petkovc3fd90c2011-05-11 21:23:00265 die "Error: you must specify an output filename with --output FILENAME"
Zdenek Behan24a75572011-02-17 03:44:46266
Gabe Black61c68462014-10-11 09:52:51267DELTA="${FLAGS_TRUE}"
Gilad Arnold16bbf362013-12-27 23:27:37268PAYLOAD_TYPE="delta"
Gabe Black61c68462014-10-11 09:52:51269if [[ -z "${FLAGS_src_image}" ]]; then
270 DELTA="${FLAGS_FALSE}"
Gilad Arnold16bbf362013-12-27 23:27:37271 PAYLOAD_TYPE="full"
272fi
Darin Petkovc3fd90c2011-05-11 21:23:00273
Gabe Black61c68462014-10-11 09:52:51274echo "Generating ${PAYLOAD_TYPE} update"
Darin Petkovc3fd90c2011-05-11 21:23:00275
276# Sanity check that the real generator exists:
277GENERATOR="$(which delta_generator)"
Gabe Black61c68462014-10-11 09:52:51278[[ -x "${GENERATOR}" ]] || die "can't find delta_generator"
Darin Petkovc3fd90c2011-05-11 21:23:00279
Gabe Black61c68462014-10-11 09:52:51280if [[ "${DELTA}" -eq "${FLAGS_TRUE}" ]]; then
281 if [[ "${FLAGS_full_kernel}" -eq "${FLAGS_FALSE}" ]]; then
Gabe Black034cac12014-10-18 01:23:05282 SRC_KERNEL=$(extract_kern "${FLAGS_src_image}" "${FLAGS_src_kern_path}")
Gabe Black16527e02014-10-18 01:22:35283 echo md5sum of src kernel:
284 md5sum "${SRC_KERNEL}"
Darin Petkovc3fd90c2011-05-11 21:23:00285 else
286 echo "Generating a full kernel update."
287 fi
Gabe Black034cac12014-10-18 01:23:05288 SRC_ROOT=$(extract_root "${FLAGS_src_image}" "${FLAGS_src_root_path}")
Gabe Black16527e02014-10-18 01:22:35289 echo md5sum of src root:
290 md5sum "${SRC_ROOT}"
Zdenek Behan24a75572011-02-17 03:44:46291fi
292
Gabe Black034cac12014-10-18 01:23:05293DST_KERNEL=$(extract_kern "${FLAGS_image}" "${FLAGS_kern_path}")
294DST_ROOT=$(extract_root "${FLAGS_image}" "${FLAGS_root_path}")
Zdenek Behan24a75572011-02-17 03:44:46295
Alex Deymo49b2e1e2015-06-27 01:17:19296GENERATOR_ARGS=(
297 # Common payload args:
Alex Deymob5df1342015-10-14 21:49:33298 -major_version=1
Alex Deymo49b2e1e2015-06-27 01:17:19299 -out_file="${FLAGS_output}"
300 -private_key="${FLAGS_private_key}"
301 # Target image args:
302 -new_image="${DST_ROOT}"
303 -new_kernel="${DST_KERNEL}"
304 -new_channel="${FLAGS_channel}"
305 -new_board="${FLAGS_board}"
306 -new_version="${FLAGS_version}"
307 -new_key="${FLAGS_key}"
308 -new_build_channel="${FLAGS_build_channel}"
309 -new_build_version="${FLAGS_build_version}"
310)
Chris Sosac9c657e2013-04-26 22:37:10311
Gabe Black61c68462014-10-11 09:52:51312if [[ "${DELTA}" -eq "${FLAGS_TRUE}" ]]; then
Alex Deymo49b2e1e2015-06-27 01:17:19313 GENERATOR_ARGS+=(
314 # Source image args:
315 -old_image="${SRC_ROOT}"
316 -old_kernel="${SRC_KERNEL}"
317 -old_channel="${FLAGS_src_channel}"
318 -old_board="${FLAGS_src_board}"
319 -old_version="${FLAGS_src_version}"
320 -old_key="${FLAGS_src_key}"
321 -old_build_channel="${FLAGS_src_build_channel}"
322 -old_build_version="${FLAGS_src_build_version}"
Alex Deymo49b2e1e2015-06-27 01:17:19323 )
324
325 # The passed chunk_size is only used for delta payload. Use delta_generator's
326 # default if no value is provided.
327 if [[ -n "${FLAGS_chunk_size}" ]]; then
328 echo "Forcing chunk_size to ${FLAGS_chunk_size}";
329 GENERATOR_ARGS+=( -chunk_size="${FLAGS_chunk_size}" )
330 fi
331fi
332
Alex Deymo67ed8ea2015-07-06 19:26:30333# Add partition size. Only *required* for minor_version=1.
Alex Deymo8ab30b12015-07-10 02:52:29334DST_ROOT_PARTITION_SECTORS=$(gpt_part_size "${FLAGS_image}" \
335 "${ROOTFS_PART_NUM}")
Alex Deymo67ed8ea2015-07-06 19:26:30336if [[ -n "${DST_ROOT_PARTITION_SECTORS}" ]]; then
337 DST_ROOT_PARTITION_SIZE=$(( DST_ROOT_PARTITION_SECTORS * 512 ))
338 echo "Using rootfs partition size: ${DST_ROOT_PARTITION_SIZE}";
339 GENERATOR_ARGS+=( -rootfs_partition_size="${DST_ROOT_PARTITION_SIZE}" )
Alex Deymo49b2e1e2015-06-27 01:17:19340else
341 echo "Using the default partition size."
342fi
343
344echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}"
Alex Deymo67ed8ea2015-07-06 19:26:30345"${GENERATOR}" "${GENERATOR_ARGS[@]}"
Zdenek Behan24a75572011-02-17 03:44:46346
Sen Jiangf5fa15c2015-10-26 18:29:24347if [[ -n "${FLAGS_out_payload_hash_file}" ||
348 -n "${FLAGS_out_metadata_hash_file}" ]]; then
349 # The out_metadata_hash_file flag requires out_hash_file flag to be set in
350 # delta_generator, if the caller doesn't provide it, we set it to /dev/null.
351 OUT_PAYLOAD_HASH_FILE="${FLAGS_out_payload_hash_file}"
352 if [[ -z "${OUT_PAYLOAD_HASH_FILE}" ]]; then
353 OUT_PAYLOAD_HASH_FILE="/dev/null"
354 fi
David Zeuthen52ccd012013-10-31 19:58:26355 # The manifest - unfortunately - contain two fields called
356 # signature_offset and signature_size with data about the how the
357 # manifest is signed. This means we have to pass the signature
358 # size used. The value 256 is the number of bytes the SHA-256 hash
359 # value of the manifest signed with a 2048-bit RSA key occupies.
Gabe Black61c68462014-10-11 09:52:51360 "${GENERATOR}" \
Steve Fung61394a22014-10-14 09:13:07361 -in_file="${FLAGS_output}" \
362 -signature_size=256 \
Sen Jiangf5fa15c2015-10-26 18:29:24363 -out_hash_file="${OUT_PAYLOAD_HASH_FILE}" \
Steve Fung61394a22014-10-14 09:13:07364 -out_metadata_hash_file="${FLAGS_out_metadata_hash_file}"
David Zeuthen52ccd012013-10-31 19:58:26365fi
366
Gabe Black61c68462014-10-11 09:52:51367echo "Done generating ${PAYLOAD_TYPE} update."