blob: c50fea8e8d155d98880c1e28a0088dcc21b352fc [file] [log] [blame]
# Copyright 2016 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
assert_inside_chroot "$@"
DEFINE_string board "${DEFAULT_BOARD}" \
"The board to build an image for."
DEFINE_string package "" \
"Package whose bare minimum deps the final container should have."
DEFINE_string name "example" \
"Name of the container."
DEFINE_string extra "" \
"Comma separated extra packages to be included in the final image."
DEFINE_string argv "" \
"The command (and args) to run inside the container."
DEFINE_integer minor_version -1 \
"An extra minor version number to append to the version read from portage."
FLAGS_HELP="USAGE: $(basename "$0") [flags]
The --package and --argv options are required.
To build a container for fastboot, you might want something like:
$ $(basename "$0") --package dev-util/android-tools --argv /usr/bin/fastboot
The output directory will contain the following:
* image.squash: a squashfs image containing the rootfs and config.json
for run_oci
* table: text file containing verity commandline
* imageloader.json: imageloader manifest which contains hashes
of image.squash and table
* imageloader.sig.2: ECDSA signature of imageloader.json
* container.json: container manifest for run_oci and such
If the --minor_version flag is given a negative number, it is ignored.
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# Sign the container!
sign_manifest() {
oci-container "$1" "${VBOOT_DEVKEYS_DIR}" "$1"
# Generate the imageloader.json for this container.
generate_imageloader_manifest() {
local pkg_version="$1"
local output="$2"
local manifest="${output}/imageloader.json"
local image="${output}/image.squash"
local table="${output}/table"
gethash() {
local file="$1"
echo $(sha256sum <"${file}" | awk '{print $1}')
cd "${output}"
printf '{\n'
printf '"manifest-version": 1,\n'
printf '"version": "%s",\n' "${pkg_version}"
printf '"image-sha256-hash": "%s",\n' "$(gethash "${image}")"
printf '"table-sha256-hash": "%s"\n' "$(gethash "${table}")"
printf '}\n'
) >"${manifest}"
# Sanity check the generated manifest.
python -mjson.tool <"${manifest}" >/dev/null
sign_manifest "${output}"
# Generate the manifest for this one container.
generate_container_manifest() {
local container_name="$1"
local pkg_version="$2"
local manifest="${output}/container.json"
cd "${output}"
printf '{\n'
printf '"manifest-version": 1,\n'
printf '"name": "%s",\n' "${container_name}"
printf '"version": "%s"\n' "${pkg_version}"
printf '}\n'
) >"${manifest}"
# Sanity check the generated manifest.
python -mjson.tool <"${manifest}" >/dev/null
# Sign the specified disk image using verity so we can load it with dm-verity
# at runtime. We don't allow algorithm selection -- sha256 should be good
# enough for everyone! :)
# Note: We write the verity command line to the "verity" variable as an output.
sign_disk_image() {
local img="$1"
local hashtree="${img}.hashtree"
# TODO: Add "salt=random" here.
verity=$(verity mode=create alg=sha256 payload="${img}" \
cat "${hashtree}" >>"${img}"
rm "${hashtree}"
# Produce the container image.
build_container_image() {
local pkg_name="$1"
local container_name="$2"
local argv="$3"
# For now we hardcode chronos.
local container_uid="1000"
local container_gid="1000"
local output="${PWD}/${container_name}"
local IMAGEDIR="${output}/image"
local ROOTDIR="${IMAGEDIR}/rootfs"
mkdir -p "${output}"
mkdir -p "${IMAGEDIR}"
info "Fetching package version ... "
local version="$(get_package_version "${pkg_name}")"
install_with_root_deps "${ROOTDIR}" "${pkg_name}" sys-libs/gcc-libs \
${FLAGS_extra//,/ }
info "Installing C library ... "
install_libc "${ROOTDIR}"
info "Cleaning excess files ... "
sudo rm -rf "${ROOTDIR}"/var "${ROOTDIR}"/usr/lib*/gconv/ \
sudo find "${ROOTDIR}"/ -type d -depth -exec rmdir {} + 2>/dev/null || :
info "Creating top level dirs and socket dirs ... "
sudo mkdir -p "${ROOTDIR}"/{dev,proc,root,sys,home/user,run,tmp,var}
info "Adding config.json ... "
sed \
-e "s:@APP_NAME@:${container_name}:g" \
-e "s:@TERMINAL@:true:g" \
-e "s:@USER_UID@:${container_uid}:g" \
-e "s:@USER_GID@:${container_gid}:g" \
-e "s:@ARGV@:${argv// /\", \"}:g" \
generic_container_files/config.json >"${IMAGEDIR}/config.json"
info "Generating squashfs file ... "
local args=(
local img="${output}/image.squash"
# We need to run through sudo because some files might be read-only by root.
sudo mksquashfs "${IMAGEDIR}" "${img}" "${args[@]}"
sudo chown $(id -u):$(id -g) "${img}"
info "Signing squashfs file ... "
local verity
sign_disk_image "${img}"
echo "${verity}" > "${output}/table"
info "Generating imageloader manifest ..."
generate_imageloader_manifest "${version}" "${output}"
info "Generating container manifest ..."
generate_container_manifest "${container_name}" "${version}"
info "Cleaning up ... "
sudo rm -rf "${IMAGEDIR}"
run_emerge() {
emerge-${BOARD} \
--quiet --jobs ${NUM_JOBS} \
--usepkgonly \
# Normal emerge.
install_with_no_deps() {
local root_dir="$1"
info "Installing '$*' with no deps ... "
run_emerge --root="${root_dir}" "$@" --nodeps
# Emerge with root deps.
install_with_root_deps() {
local root_dir="$1"
info "Installing '$*' with root deps ... "
run_emerge --root="${root_dir}" "$@" --root-deps=rdeps
strip_version() {
local full_version="$1"
echo "${full_version}" | sed 's/^\([0-9.]*[0-9]\).*/\1/'
get_package_version() {
local full_version="$(equery-${BOARD} --quiet list -F '$version' "$@")"
local stripped_version="$(strip_version "${full_version}")"
if [ "${FLAGS_minor_version}" -ge 0 ]; then
echo "${stripped_version}.${FLAGS_minor_version}"
echo "${stripped_version}"
main() {
. "${BUILD_LIBRARY_DIR}/" || exit 1
. "${BUILD_LIBRARY_DIR}/" || exit 1
if [[ -z "${FLAGS_argv}" ]]; then
die_notrace "--argv is needed."
if [[ -z "${FLAGS_package}" ]]; then
die_notrace "--package is needed."
local container_name="${FLAGS_name}"
if [[ -d "${container_name}" ]]; then
die_notrace "Output dir already exists : ${container_name}/"
info "Building container '${container_name}' for ${FLAGS_package} ..."
build_container_image "${FLAGS_package}" "${container_name}" "${FLAGS_argv}"
main "$@"