blob: 8b9637d6d2467d491afe000768eb9b837af4746b [file] [log] [blame]
#!/bin/sh
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Script to extract image resources from multiple images and then compress
# and save them into a single output image.
. "$(dirname "$(readlink -f "$0")")/factory_common.sh" || exit 1
# Flags
DEFINE_string save_partitions "1,4,5,6,7,8,12" \
"A list of numbers specifying partitions to be compressed from \
each input image."
DEFINE_string format "ext4" \
"Filesystem format of the output file, ex: ext3, ext4, fat, msdos, etc."
DEFINE_string output "resources.ext4" \
"File name of the output image."
DEFINE_boolean force ${FLAGS_FALSE} "Try to force update." ""
# Parse command line
FLAGS "$@" || exit 1
ORIGINAL_PARAMS="$@"
eval set -- "${FLAGS_ARGV}"
# Temp folder to store the compressed partitions for each images.
INSTALLER_RESOURCES_DIR="$(mktemp -d)"
# Partition number on the output file storing compressed input partitions.
DATA_PART_NUM=1
alert() {
echo "$*" >&2
}
die() {
alert "ERROR: $*"
exit 1
}
on_exit() {
image_clean_temp
rm -rf "${INSTALLER_RESOURCES_DIR}"
}
# Usage: compress_one_partition <image_file> <part_num> <output_dir>
# Args:
# image_file: The image file.
# part_num: The partition number to make filesystem on it.
# output_dir: The output directory to store the compressed partition
compress_one_partition() {
local image_file="$1"
local part_num="$2"
local output_dir="$3"
local part_hash
mkdir -p "${output_dir}" || die "Cannot create directory: ${output_dir}."
alert "Compressing partition ${part_num} of ${image_file}..."
part_hash="$(compress_and_hash_partition \
"${image_file}" "${part_num}" "${output_dir}/${part_num}.gz")"
# The compress_and_hash_partition returns the hash of the compressed
# file but it doesn't guarantee the copy to destination is completed.
# Verifies the hash of the destination file to ensure the copy is
# completed and throws error if the hash verification failed.
actual_hash=$(openssl sha1 -binary "${output_dir}/${part_num}.gz" |
openssl base64)
[ "${part_hash}" = "${actual_hash}" ] ||
die "Failed to generate ${output_dir}/${part_num}.gz."
echo "${part_num}_checksum = ${part_hash}" >> "${output_dir}/config"
}
# Compress partitions of each image to folder ${INSTALLER_RESOURCES_DIR}.
# Usage: compress_partitions <save_partitions> <image1> <image2> ...
# Args:
# save_partitions: a list of number specifying which partitions to compress.
# For example: "1,4,5,6,7"
# imageN: the input images to be compressed
compress_partitions() {
local save_partitions="$1,"
shift
local image next_part partitions_list
local kern_guid
local compressed_size_in_bytes overhead_in_bytes
local filesystem_overhead_fraction=7 # Add extra 1 / 7 size for overhead.
for image in "$@"; do
if [ ! -f "${image}" ]; then
die "Cannot find input file ${image}."
fi
done
for image in "$@"; do
# Use GUID of installer kernel (partition 2) as the directory name to
# store the compressed partitions of the image.
kern_guid="$(cgpt show -u -i 2 "${image}")"
partitions_list="${save_partitions}"
# Compress each partition to the shared folder.
next_part=${partitions_list%%,*}
while [ $((next_part)) -ne 0 ]; do
compress_one_partition "${image}" "$((next_part))" \
"${INSTALLER_RESOURCES_DIR}/${kern_guid}"
partitions_list=${partitions_list#*,}
next_part=${partitions_list%%,*}
done
done
# Count the total size of the compressed files.
# Then plus extra size for filesystem overhead.
compressed_size_in_bytes=$(du -sb "${INSTALLER_RESOURCES_DIR}" | cut -f1)
overhead_in_bytes=$((compressed_size_in_bytes / filesystem_overhead_fraction))
echo "$((compressed_size_in_bytes + overhead_in_bytes))"
}
# Convert a list of partition sizes in bytes to a list of sizes in sectors.
bytes_to_sectors_list() {
local bytes_list="$1,"
local sectors_list=""
local part_bytes part_sectors
local aligned_bytes
# Convert bytes_list to sectors_list
part_bytes=${bytes_list%%,*}
while [ $((part_bytes)) -ne 0 ]; do
aligned_bytes=$(image_alignment "${part_bytes}" "${IMAGE_CGPT_BS}" "")
part_sectors=$((aligned_bytes / IMAGE_CGPT_BS))
sectors_list="${sectors_list} ${part_sectors}"
# Continue on remaining partitions.
bytes_list=${bytes_list#*,}
part_bytes=${bytes_list%%,*}
done
echo "${sectors_list}"
}
# Callback of image_process_geometry. Format a partition by give offset,
# size (sectors), and index.
image_geometry_format_partition() {
local offset="$1"
local sectors="$2"
local index="$3"
local output_file="$4"
local filesystem="$5"
if [ "${offset}" = "0" ]; then
# first entry is CGPT; ignore.
return
fi
cgpt add -b "${offset}" -s "${sectors}" -i "${index}" -t "data" \
-l "DATA" "${output_file}"
part_dev=$(sudo losetup -f --show --offset="$((offset * IMAGE_CGPT_BS))" \
--sizelimit="$((sectors * IMAGE_CGPT_BS))" "${output_file}")
# Format the file.
sudo "mkfs.${filesystem}" "${part_dev}" ||
die "Failed to format file: ${output_file}."
sudo losetup -d "${part_dev}"
}
# Create a cgpt image with partitions
# Usage: build_image_file <part_bytes_list> <output_file> <part_filesystem>
# Args:
# part_bytes_list: a list of numbers specifying the size in bytes of
# different partitions. For example: "10000000,400000000".
# output_file: the output image file.
# part_filesystem: filesystem format for each partition, ex: ext4, ntfs.
build_image_file() {
local part_bytes_list="$1"
local output_file="$2"
local part_filesystem="$3"
local sectors_list=$(bytes_to_sectors_list "${part_bytes_list}")
image_geometry_build_file "${sectors_list}" "${output_file}"
# Format each partition.
image_process_geometry "${IMAGE_CGPT_START_SIZE} ${sectors_list}" \
image_geometry_format_partition \
"${output_file}" "${part_filesystem}"
}
# Usage: copy_dirs_to_partition <src_dir> <image_file> <part_num> <dst_dir>
# Args:
# src_dir: the source directory to copy from.
# image_file: The image file.
# part_num: The partition of the image_file to store the source_dir.
# dst_dir: the destination directory to copy to.
copy_dir_to_partition() {
local src_dir="$1"
local image_file="$2"
local part_num="$3"
local dst_dir="$4"
local data_dir="$(mktemp -d)"
alert "Copying ${src_dir} to partition #${part_num} of ${image_file}..."
image_add_temp "${data_dir}"
image_mount_partition "${image_file}" "${part_num}" "${data_dir}" "rw"
# Add a trailing / on the source to "copy the contents of this directory"
# as opposed to "copy the directory by name".
sudo rsync -Pa "${src_dir}/" "${data_dir}/${dst_dir}"
image_umount_partition "${data_dir}"
}
main() {
local compressed_size
if [ "$#" -lt 1 ]; then
flags_help
exit 1
fi
if [ -f "${FLAGS_output}" ] && [ "${FLAGS_force}" != "${FLAGS_TRUE}" ]; then
die "Output file ${FLAGS_output} already exists." \
"To overwrite the file, add --force."
fi
compressed_size=$(compress_partitions "${FLAGS_save_partitions}" "$@")
build_image_file "${compressed_size}" "${FLAGS_output}" "${FLAGS_format}"
copy_dir_to_partition "${INSTALLER_RESOURCES_DIR}" "${FLAGS_output}" \
"${DATA_PART_NUM}" "installer_resources"
}
set -e
trap on_exit EXIT
main "$@"