blob: 065d3eaa59da036245f1afd5250eb444e15d2821 [file] [log] [blame]
# Copyright 2017 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_ROOT=$(dirname "$(readlink -f "$0")")
. "${SCRIPT_ROOT}/build_library/" || exit 1
. "${SCRIPT_ROOT}/build_library/" || exit 1
assert_inside_chroot "$@"
DEFINE_string arch "amd64" \
"Architecture of the VM image"
DEFINE_string filesystem "ext4" \
"Filesystem for the rootfs image"
DEFINE_string image "" \
"Chromium OS disk image to build the Termina image from"
DEFINE_string output "" \
"Output directory"
DEFINE_boolean upload ${FLAGS_FALSE} \
"Upload resulting image to Google Storage" u
DEFINE_boolean test_image ${FLAGS_FALSE} \
"True if the image is a test image" t
To build a tatl test image, try:
$ ./build_image --board=tatl test
$ ${SCRIPT_NAME} --image=../build/images/tatl/latest/chromiumos_test_image.bin --output=tatl
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
get_version() {
local output_dir="$1"
awk -F'=' -v key='CHROMEOS_RELEASE_VERSION' '$1==key { print $2 }' \
is_test_image() {
local output_dir="$1"
grep -q testimage-channel "${output_dir}"/lsb-release
read_le_int() {
local disk="$1"
local offset="$2"
local size="$3"
case "${size}" in
1 | 2 | 4 | 8)
*) die "${size} is not a valid int size to read"
local raw="$(xxd -g ${size} -e -s ${offset} -l ${size} ${disk} | cut -d' ' -f2)"
local result="$(( 16#${raw} ))"
echo "${result}"
extract_squashfs_partition() {
local src_disk="$1"
local src_part="$2"
local dst_file="$3"
local part_start_blks="$(cgpt show -i "${src_part}" -b "${src_disk}")"
local part_start_bytes="$(( part_start_blks * 512 ))"
local part_size_blks="$(cgpt show -i "${src_part}" -s "${src_disk}")"
local part_size_bytes="$(( part_size_blks * 512 ))"
# To be sure we're extracting a squashfs partition, verify the magic.
# See fs/squashfs/squashfs_fs.h.
local magic="$(read_le_int ${src_disk} ${part_start_bytes} 4)"
if [[ "${magic}" -ne 0x73717368 ]]; then
die "Partition ${src_part} doesn't look like a squashfs partition"
dd if="${src_disk}" of="${dst_file}" skip="${part_start_bytes}c" \
count="${part_size_bytes}c" iflag=skip_bytes,count_bytes
# Repack termina rootfs.
repack_rootfs() {
local output_dir="$1"
local fs_type="$2"
local rootfs_img="${output_dir}/vm_rootfs.img"
local stateful_img="${output_dir}/vm_stateful.img"
# Create image in a temporary directory to avoid the need for extra space
# on the final rootfs.
local rootfs="${output_dir}/rootfs"
local stateful="${output_dir}/stateful"
sudo unsquashfs -d "${rootfs}" "${rootfs_img}"
sudo unsquashfs -d "${stateful}" "${stateful_img}"
# Remove source images.
sudo rm -f "${rootfs_img}" "${stateful_img}"
# Fix up rootfs.
# Remove efi cruft.
sudo rm -rf "${rootfs}/boot"/{efi,syslinux}
# Don't need firmware if you don't have hardware!
sudo rm -rf "${rootfs}/lib/firmware"
# Get rid of stateful, it's not needed on termina.
sudo rm -rf "${rootfs}/mnt/stateful_partition"
# Create container rootfs and private dirs.
sudo mkdir "${rootfs}"/mnt/{container_rootfs,container_private}
# Copy the dev_image into its location at /usr/local.
sudo cp -aT "${stateful}"/dev_image "${rootfs}/usr/local"
# TODO(smbarber): Remove vmlinuz once kvmtool is removed.
cp "${rootfs}/boot/vmlinuz" "${output_dir}/vmlinuz"
"${CHROOT_TRUNK_DIR}"/src/third_party/kernel/v4.4/scripts/extract-vmlinux \
"${output_dir}/vmlinuz" > "${output_dir}/vm_kernel"
# Remove vmlinuz from the rootfs since it's already on the host rootfs.
sudo rm -rf "${rootfs}"/boot/vmlinuz*
sudo cp "${rootfs}/etc/lsb-release" "${output_dir}/lsb-release"
case "${fs_type}" in
sudo mksquashfs "${rootfs}" "${rootfs_img}" -comp lzo
# Start with 300MB, then shrink.
local image_size=300
truncate --size "${image_size}M" "${rootfs_img}"
/sbin/mkfs.ext4 -F -m 0 -i 16384 -b 1024 -O "^has_journal" "${rootfs_img}"
local rootfs_mnt="$(mktemp -d)"
fs_mount "${rootfs_img}" "${rootfs_mnt}" ext4 rw
sudo cp -aT "${rootfs}" "${rootfs_mnt}"
fs_umount "${rootfs_img}" "${rootfs_mnt}" ext4 rw
# Shrink to minimum size.
/sbin/e2fsck -f "${rootfs_img}"
/sbin/resize2fs -M "${rootfs_img}"
rmdir "${rootfs_mnt}"
die_notrace "Unsupported fs type ${fs_type}."
sudo rm -rf "${rootfs}" "${stateful}"
upload() {
local output_dir="$1"
local gspath="gs://chromeos-localmirror/distfiles"
local release="$(get_version "${output_dir}")"
if is_test_image "${output_dir}"; then
local pkg_name="termina-vm-image-test_${FLAGS_arch}"
local pkg_name="termina-vm-image_${FLAGS_arch}"
local target_file="${pkg_name}-${release}.tar.xz"
info "Will upload: '${target_file}'"
if ! prompt_yesno "Are you sure you want to upload this image?"; then
echo "Not uploading image."
info "Generating tarball..."
tar caf "${output_dir}/termina-vm-image.tar.xz" -C "${output_dir}" \
vm_kernel vm_rootfs.img
if ! gsutil cp -a public-read "${output_dir}/termina-vm-image.tar.xz" \
"${gspath}/${target_file}"; then
die_notrace "Couldn't upload '${gspath}/${target_file}'."
info "Uploaded to ${gspath}/${target_file}"
main() {
if [[ -z "${FLAGS_image}" ]]; then
die_notrace "Please provide an image using --image"
elif [[ ! -f "${FLAGS_image}" ]]; then
die_notrace "'${FLAGS_image}' does not exist"
if [[ "${FLAGS_arch}" != "amd64" && "${FLAGS_arch}" != "armv8" ]]; then
die_notrace "Architecture '${FLAGS_arch}' is not valid. Options are 'amd64' and 'armv8'"
case "${FLAGS_filesystem}" in
die_notrace "Filesystem '${FLAGS_filesystem}' is not valid. Options are 'squashfs' and 'ext4'"
if [[ -z "${FLAGS_output}" ]]; then
die_notrace "Output directory was not specified"
elif [[ -e "${FLAGS_output}" ]]; then
die_notrace "${FLAGS_output} already exists"
local output_dir="${FLAGS_output}"
local stateful_img="${output_dir}/vm_stateful.img"
local rootfs_img="${output_dir}/vm_rootfs.img"
local image="${FLAGS_image}"
mkdir -p "${output_dir}"
extract_squashfs_partition "${image}" "3" "${rootfs_img}"
extract_squashfs_partition "${image}" "1" "${stateful_img}"
repack_rootfs "${output_dir}" "${FLAGS_filesystem}"
if [[ "${FLAGS_upload}" -eq "${FLAGS_TRUE}" ]]; then
upload "${output_dir}"
info "Done! The resulting image is in '${output_dir}'"
main "$@"