Move cos-toolbox@29da3c029f29975bb443aa74dbdc7959e685088d from Github
BUG=b/183723779
Change-Id: Ie639fca149fd6d9715d1d0cf83d169e1cb43623f
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/16470
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Reviewed-by: Sam Kunz <samkunz@google.com>
Reviewed-by: Arnav Kansal <rnv@google.com>
Tested-by: Arnav Kansal <rnv@google.com>
diff --git a/src/cmd/toolbox/Dockerfile b/src/cmd/toolbox/Dockerfile
new file mode 100644
index 0000000..1dd5442
--- /dev/null
+++ b/src/cmd/toolbox/Dockerfile
@@ -0,0 +1,43 @@
+FROM golang:1.11 as gcr-build
+
+RUN go get -u github.com/GoogleCloudPlatform/docker-credential-gcr
+
+# Start from debian:buster-backports base.
+FROM debian:buster-backports
+
+# Prepare the image.
+ENV DEBIAN_FRONTEND noninteractive
+
+COPY --from=gcr-build /go/bin/docker-credential-gcr /usr/bin/
+
+# Google Cloud SDK pre requisites.
+RUN apt-get update && apt-get install -y -qq --no-install-recommends apt-transport-https \
+ ca-certificates gnupg curl
+
+# Install the Google Cloud SDK.
+ENV HOME /
+ENV CLOUDSDK_PYTHON_SITEPACKAGES 1
+RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] \
+ https://packages.cloud.google.com/apt cloud-sdk main" | \
+ tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
+ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
+ apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \
+ && apt-get update && apt-get -y -qq install google-cloud-sdk && apt-get clean
+
+# Various networking and other tools. net-tools installs arp, netstat, etc.
+RUN apt-get install -u -qq vim \
+ net-tools netcat ipset conntrack inetutils-traceroute bridge-utils \
+ ebtables \
+ && apt-get clean
+
+# These packages are required or extracting source tarballs and building the kernel.
+RUN apt-get update && \
+ apt-get install -u -qq \
+ xz-utils make gcc python-minimal bc libelf-dev libssl-dev \
+ crash bison flex dwarves libdw1 && \
+ apt-get clean
+COPY cos-kernel /usr/local/bin
+
+VOLUME ["/.config"]
+
+CMD ["/bin/bash"]
diff --git a/src/cmd/toolbox/README.md b/src/cmd/toolbox/README.md
new file mode 100644
index 0000000..0e7141c
--- /dev/null
+++ b/src/cmd/toolbox/README.md
@@ -0,0 +1,59 @@
+# Toolbox Docker Container for Container-Optimized OS
+
+Note: This is not an official Google product.
+
+## Overview
+
+This is a Docker image used by the
+[CoreOS Toolbox](https://github.com/coreos/toolbox) script on [Container-Optimized
+OS](https://cloud.google.com/container-optimized-os/). This image comes
+pre-installed with common debugging tools that are not pre-installed on the host.
+
+The official toolbox container is available at `gcr.io/cos-cloud/toolbox`.
+
+Starting with tag `20190312-00`, COS toolbox includes a tool called
+`cos-kernel` to make it easy to fetch kernel headers, source, and
+toolchain for COS releases. For example, the following command fetches
+kernel headers, source, and toolchain for the instance it's running on:
+
+```bash
+my-cos-instance ~ $ toolbox
+root@my-cos-instance:~# cos-kernel fetch
+```
+
+By default, `cos-kernel` uses `$HOME` as its install directory but this
+can be changed via the `--instdir` option. `cos-kernel` copies the
+files it fetches in the `fetched-files` directory and extracts them into
+`cos-kernel-headers`, `cos-kernel-src`, and `cos-toolchain`.
+
+```bash
+root@my-cos-instance:~# ls -l
+drwxr-xr-x 4 root root 4096 Mar 12 14:43 cos-kernel-headers
+drwxr-xr-x 4 root root 4096 Mar 12 14:44 cos-kernel-src
+drwxr-xr-x 4 root root 4096 Mar 12 14:43 cos-toolchain
+drwxr-xr-x 4 root root 4096 Mar 12 14:40 fetched-files
+````
+The following command fetches kernel headers, source, and toolchain for
+release `11636.0.0` and builds the kernel:
+
+```bash
+root@my-cos-instance:~# cos-kernel build 11636.0.0
+```
+
+To see the list of available subcommands and their options, type:
+
+```bash
+root@my-cos-instance:~# cos-kernel help
+```
+
+For detailed documentation on how this is used, see
+https://cloud.google.com/container-optimized-os/docs/how-to/toolbox.
+
+
+# Contributor Docs
+
+## Releasing
+
+To release a new version of COS Toolbox, tag the commit you want to release
+with the date in the form of `vYYYYMMDD`. This will trigger a Cloud Build job to
+build and release the container image.
diff --git a/src/cmd/toolbox/cloudbuild.yaml b/src/cmd/toolbox/cloudbuild.yaml
new file mode 100644
index 0000000..171095d
--- /dev/null
+++ b/src/cmd/toolbox/cloudbuild.yaml
@@ -0,0 +1,25 @@
+options:
+ env:
+ - 'DOCKER_CLI_EXPERIMENTAL=enabled'
+steps:
+# Build toolbox image
+# This step is needed to add a new entry to /proc/sys/fs/binfmt_misc. Docker
+# uses QEMU user emulation to run arm64 programs on x86 hosts. A QEMU
+# interpreter needs to be added to /proc/sys/fs/binfmt_misc to run arm64
+# programs.
+- name: 'gcr.io/cloud-builders/docker'
+ args: ['run', '--privileged', 'linuxkit/binfmt:v0.7']
+# The default builder (which appears to be the Docker daemon that implements
+# the old, familiar `docker build` behavior) doesn't support the --platform
+# flag, so we need to create a new builder.
+- name: 'gcr.io/cloud-builders/docker'
+ args: ['buildx', 'create', '--name', 'builder']
+- name: 'gcr.io/cloud-builders/docker'
+ args: ['buildx', 'use', 'builder']
+# Images produced in this way do not appear in the Docker image registry shown
+# by `docker images`, at least by default. We use the --push flag to push the
+# image after building it, because a subsequent `docker push` won't find the
+# image locally.
+- name: 'gcr.io/cloud-builders/docker'
+ args: ['buildx', 'build', '--platform', 'linux/amd64,linux/arm64', '-t', 'gcr.io/${_OUTPUT_PROJECT}/toolbox:latest', '-t', 'gcr.io/${_OUTPUT_PROJECT}/toolbox:${TAG_NAME}', '--push', '.']
+timeout: 1800s
diff --git a/src/cmd/toolbox/cos-kernel b/src/cmd/toolbox/cos-kernel
new file mode 100755
index 0000000..9d42771
--- /dev/null
+++ b/src/cmd/toolbox/cos-kernel
@@ -0,0 +1,824 @@
+#!/bin/bash
+
+#
+# This script fetches $FILES_TO_FETCH (kernel headers, source, toolchain,
+# ...) of a specific COS release and installs them for compiling,
+# debugging, etc. See usage() for details.
+#
+# This script is meant to run in COS toolbox or inside a cos-toolbox
+# container.
+#
+
+set -eu
+set -o pipefail
+
+# Program name and version. Bump the version number if you change
+# this script.
+readonly PROG_NAME="$(basename "${0}")"
+readonly PROG_VERSION="1.3"
+
+# ANSI escape sequences for pretty printing.
+readonly RED_S="\033[00;31m"
+readonly BLUE_S="\033[00;34m"
+readonly PURPLE_S="\033[00;35m"
+readonly ANSI_E="\033[0m"
+
+# Build ID number is passed as an arg or read from $COS_OS_RELEASE.
+BUILD_ID=""
+readonly COS_OS_RELEASE="/media/root/etc/os-release"
+readonly COS_IMAGE_PROJECT="cos-cloud"
+
+# Public GCS bucket of COS to fetch files from.
+readonly COS_GCS_BUCKET="gs://cos-tools"
+
+# For each file to fetch, we have the following properties:
+#
+# Property Type
+# 1. Name in GCS bucket Fixed
+# 2. Install dir name relative to $INSTALL_DIR Fixed
+# 3. Full pathname of install dir Dynamic based on $INSTALL_DIR and $BUILD_ID
+# 4. Installation commnds Dynamic based on $INSTALL_DIR and $BUILS_ID
+# 5. Installation size in MB Fixed
+#
+# 1. Name in GCS bucket.
+readonly KERNEL_HEADERS="kernel-headers.tgz"
+readonly KERNEL_SRC="kernel-src.tar.gz"
+readonly TRUSTED_KEY="trusted_key.pem"
+readonly TOOLCHAIN="toolchain.tar.xz"
+readonly TOOLCHAIN_ENV="toolchain_env"
+# 2. Install dir name relative to $INSTALL_DIR.
+readonly KERNEL_HEADERS_DIRNAME="cos-kernel-headers"
+readonly KERNEL_SRC_DIRNAME="cos-kernel-src"
+readonly TRUSTED_KEY_DIRNAME="${KERNEL_SRC_DIRNAME}"
+readonly TOOLCHAIN_DIRNAME="cos-toolchain"
+readonly TOOLCHAIN_ENV_DIRNAME="cos-toolchain-env"
+# 3. Full pathname of installation directories (see initialize()).
+KERNEL_HEADERS_DIR=""
+KERNEL_SRC_DIR=""
+TRUSTED_KEY_DIR=""
+TOOLCHAIN_DIR=""
+TOOLCHAIN_ENV_DIR=""
+# 4. Installation commnds (see initialize()).
+declare -A INSTALL_CMD
+INSTALL_CMD[${KERNEL_HEADERS}]=""
+INSTALL_CMD[${KERNEL_SRC}]=""
+INSTALL_CMD[${TRUSTED_KEY}]=""
+INSTALL_CMD[${TOOLCHAIN}]=""
+INSTALL_CMD[${TOOLCHAIN_ENV}]=""
+# 5. Installation size in MB.
+declare -A INSTALL_SIZE
+INSTALL_SIZE[${KERNEL_HEADERS}]="120"
+INSTALL_SIZE[${KERNEL_SRC}]="1000"
+INSTALL_SIZE[${TRUSTED_KEY}]="1"
+INSTALL_SIZE[${TOOLCHAIN}]="2200"
+INSTALL_SIZE[${TOOLCHAIN_ENV}]="1"
+
+readonly FILES_TO_FETCH=("${KERNEL_HEADERS}" "${KERNEL_SRC}" "${TRUSTED_KEY}" "${TOOLCHAIN}" "${TOOLCHAIN_ENV}")
+readonly FETCHED_FILES_DIRNAME="fetched-files"
+FETCHED_FILES_DIR=""
+
+# Temporary files created for the list subcommand.
+readonly TMP_IMAGE_LIST="/tmp/image_list"
+readonly TMP_BUILD_ID_LIST="/tmp/build_id_list"
+readonly TMP_BUILD_ID_FILES="/tmp/build_id_files"
+
+# Compilation environment variables.
+CC=""
+CXX=""
+
+SUBCOMMAND=""
+NO_DISK_SPACE=false
+
+# Set the defaults that can be changed by command line flags.
+HELP="" # -h
+INSTALL_DIR="${HOME}" # -i
+ECHO=":" # -v
+GLOBAL_OPTIONS="$(cat <<EOF
+ -h, --help print help message
+ -i, --instdir install directory (default \$HOME: $HOME)
+ -v, --verbose enable verbose mode
+EOF
+)"
+
+ALL="" # -a
+LIST_OPTIONS="$(cat <<EOF
+ -h, --help print help message
+ -a, --all include deprecated builds
+EOF
+)"
+
+EXTRACT=true # -x
+REMOVE=true # -r
+FETCH_OPTIONS="$(cat <<EOF
+ -h, --help print help message
+ -r, --no-remove do not remove fetched files after installation
+ -x, --no-xtract do not extract files from their tarballs
+EOF
+)"
+
+KERNEL_CONFIG="" # -c
+PRINT_CMD="" # -p
+MAKE_VERBOSE="" # -V
+BUILD_OPTIONS="$(cat <<EOF
+ -h, --help print help message
+ -c, --kconf specify path to kernel configuration file
+ -p, --print print commands to build the kernel, but do not execute
+ -V enable make's verbose mode
+EOF
+)"
+
+REMOVE_OPTIONS="$(cat <<EOF
+ -h, --help print help message
+ -a, --all remove all fetched and installed files
+EOF
+)"
+
+
+usage() {
+ local -r exit_code="$1"
+
+ cat <<EOF
+${PROG_NAME} v${PROG_VERSION}
+
+Usage:
+ ${PROG_NAME} [<global-options>] <subcommand> [<subcommand-options>] [<build-id>]
+
+Subcommmands:
+ list list available builds
+ fetch fetch kernel headers, source, and toolchain tarballs
+ build build kernel (implies fetch)
+ remove remove fetched and extracted files
+ help print help message
+
+Global options:
+${GLOBAL_OPTIONS}
+
+list options:
+${LIST_OPTIONS}
+
+fetch options:
+${FETCH_OPTIONS}
+
+build options:
+${BUILD_OPTIONS}
+
+remove options:
+${REMOVE_OPTIONS}
+
+Environment:
+ HOME default installation directory
+EOF
+
+ help_disk_space
+ exit "${exit_code}"
+}
+
+
+main() {
+ check_arch
+
+ local options
+
+ parse_args "${@}"
+
+ # Global help message.
+ if [[ -z "${SUBCOMMAND}" || "${SUBCOMMAND}" == "help" ]]; then
+ usage 0
+ fi
+
+ # Subcommand-specific help message.
+ if [[ -n "${HELP}" ]]; then
+ echo "${SUBCOMMAND}" specific options:
+ options="${SUBCOMMAND^^}_OPTIONS"
+ echo "${!options}"
+ exit 0
+ fi
+
+ # No need to initialize if we're listing available releases.
+ if [[ "${SUBCOMMAND}" != "list" ]]; then
+ initialize
+ fi
+
+ case "${SUBCOMMAND}" in
+ "list") subcmd_list;;
+ "fetch") subcmd_fetch; if "${EXTRACT}"; then extract_files; fi;;
+ "build") subcmd_build;;
+ "remove") subcmd_remove;;
+ *) fatal internal error processing "${SUBCOMMAND}"
+ esac
+}
+
+check_arch() {
+ arch=$(uname -m)
+ if [ $arch == "arm64" ] || [ $arch == "aarch64" ]; then
+ echo "cos-kernel not supported on ARM"
+ exit 1
+ fi
+}
+
+parse_args() {
+ local args
+
+ if ! args=$(getopt \
+ --options "ac:hi:prvVx" \
+ --longoptions "all config: help instdir: print no-remove verbose no-xtract" \
+ -- "$@"); then
+ # getopt has printed an appropriate error message.
+ exit 1
+ fi
+ eval set -- "${args}"
+
+ while [[ "${#}" -gt 0 ]]; do
+ case "$1" in
+ -a|--all)
+ ALL="yes";;
+ -c|--kconf)
+ shift
+ KERNEL_CONFIG="$1";;
+ -h|--help)
+ HELP="yes";;
+ -i|--instdir)
+ shift
+ INSTALL_DIR="$1";;
+ -p|--print)
+ PRINT_CMD="echo";;
+ -r|--no-remove)
+ REMOVE=false;;
+ -v|--verbose)
+ ECHO="info";;
+ -V)
+ MAKE_VERBOSE="V=1";;
+ -x|--no-xtract)
+ EXTRACT=false;;
+ --)
+ ;;
+ *)
+ if [[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ BUILD_ID="$1"
+ shift
+ continue
+ fi
+ if [[ -n "${SUBCOMMAND}" ]]; then
+ fatal specify only one subcommand
+ fi
+ case "$1" in
+ "list") SUBCOMMAND="$1";;
+ "fetch") SUBCOMMAND="$1";;
+ "build") SUBCOMMAND="$1";;
+ "remove") SUBCOMMAND="$1";;
+ "help") SUBCOMMAND="$1";;
+ "--") ;;
+ *) fatal "$1}": invalid build id
+ esac
+ esac
+ shift
+ done
+
+ if [[ -z "${INSTALL_DIR}" ]]; then
+ fatal install directory not specified
+ fi
+}
+
+
+initialize() {
+ if [[ "${SUBCOMMAND}" == "remove" && -n "${ALL}" ]]; then
+ return
+ fi
+
+ # If build ID is not provided as an argument, we assume we're
+ # running on COS and the user wants the current build ID.
+ if [[ -z "${BUILD_ID}" ]]; then
+ if [[ ! -f "${COS_OS_RELEASE}" ]]; then
+ fatal "${COS_OS_RELEASE}" does not exist and build ID not specified
+ fi
+ # shellcheck disable=SC1090
+ source "${COS_OS_RELEASE}"
+ fi
+
+ if [[ ! ${BUILD_ID} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ error "${BUILD_ID}": invalid build id
+ return 1
+ fi
+
+ FETCHED_FILES_DIR="${INSTALL_DIR}/${FETCHED_FILES_DIRNAME}/${BUILD_ID}"
+
+ KERNEL_HEADERS_DIR="${INSTALL_DIR}/${KERNEL_HEADERS_DIRNAME}/${BUILD_ID}"
+ KERNEL_SRC_DIR="${INSTALL_DIR}/${KERNEL_SRC_DIRNAME}/${BUILD_ID}"
+ TRUSTED_KEY_DIR="${INSTALL_DIR}/${TRUSTED_KEY_DIRNAME}/${BUILD_ID}/certs"
+ TOOLCHAIN_DIR="${INSTALL_DIR}/${TOOLCHAIN_DIRNAME}/${BUILD_ID}"
+ TOOLCHAIN_ENV_DIR="${INSTALL_DIR}/${TOOLCHAIN_ENV_DIRNAME}/${BUILD_ID}"
+
+ INSTALL_CMD[${KERNEL_HEADERS}]="tar -C \"${KERNEL_HEADERS_DIR}\" -xf \"${FETCHED_FILES_DIR}/${KERNEL_HEADERS}\""
+ INSTALL_CMD[${KERNEL_SRC}]="tar -C \"${KERNEL_SRC_DIR}\" -xf \"${FETCHED_FILES_DIR}/${KERNEL_SRC}\" && \
+ (cd \"$(dirname "${KERNEL_SRC_DIR}")\" && rm -f kernel && ln -s \"$(basename "${KERNEL_SRC_DIR}")\" kernel)"
+ INSTALL_CMD[${TRUSTED_KEY}]="cp -a \"${FETCHED_FILES_DIR}/${TRUSTED_KEY}\" \"${TRUSTED_KEY_DIR}/${TRUSTED_KEY}\""
+ INSTALL_CMD[${TOOLCHAIN}]="tar -C \"${TOOLCHAIN_DIR}\" -xf \"${FETCHED_FILES_DIR}/${TOOLCHAIN}\""
+ INSTALL_CMD[${TOOLCHAIN_ENV}]="cp \"${FETCHED_FILES_DIR}/${TOOLCHAIN_ENV}\" \"${TOOLCHAIN_ENV_DIR}\""
+
+ info INSTALL_DIR="${INSTALL_DIR}"
+ info BUILD_ID="${BUILD_ID}"
+ echo
+ "${ECHO}" FETCHED_FILES_DIR="${FETCHED_FILES_DIR}"
+ "${ECHO}" KERNEL_HEADERS_DIR="${KERNEL_HEADERS_DIR}"
+ "${ECHO}" KERNEL_SRC_DIR="${KERNEL_SRC_DIR}"
+ "${ECHO}" TRUSTED_KEY_DIR="${TRUSTED_KEY_DIR}"
+ "${ECHO}" TOOLCHAIN_DIR="${TOOLCHAIN_DIR}"
+ "${ECHO}" TOOLCHAIN_ENV_DIR="${TOOLCHAIN_ENV_DIR}"
+ "${ECHO}"
+}
+
+
+subcmd_list() {
+ local header
+ local n
+ local build_id
+ local all_lines
+ local line
+
+ # If we generated the list of images within the past hour, use it.
+ if [[ ! -s "${TMP_IMAGE_LIST}" || -z "$(find "${TMP_IMAGE_LIST}" -cmin -60)" ]]; then
+ info getting the list of images from "${COS_IMAGE_PROJECT}"
+ list_cos_images > "${TMP_IMAGE_LIST}"
+ fi
+
+ # If we generated the list of build IDs within the past hour, use it.
+ if [[ ! -s "${TMP_BUILD_ID_FILES}" || -z "$(find "${TMP_BUILD_ID_FILES}" -cmin -60)" ]]; then
+ info getting the list of builds from "${COS_GCS_BUCKET}"
+ gsutil ls -r "${COS_GCS_BUCKET}" > "${TMP_BUILD_ID_FILES}"
+ fi
+
+ # Get and sort the list of build IDs in $COS_GCS_BUCKET.
+ if [[ -n "${BUILD_ID}" ]]; then
+ echo "${BUILD_ID}" > "${TMP_BUILD_ID_LIST}"
+ # The $BUILD_ID may be deprecated or obsolete, but
+ # becaue it was specified on the command line, we
+ # still want to print it.
+ ALL="yes"
+ else
+ grep '^gs://.*:$' "${TMP_BUILD_ID_FILES}" | \
+ grep -E '[0-9]+\.[0-9]+\.[0-9]+' | \
+ sed -e "s;${COS_GCS_BUCKET}/;;" -e "s;/:;;" | \
+ sort -V > "${TMP_BUILD_ID_LIST}"
+ fi
+
+ # Build and print the header.
+ header="BUILD_ID MS FAMILY"
+ if [[ -n "${ALL}" ]]; then
+ header="${header} STAT"
+ fi
+ header="${header} HDR SRC KEY TLC"
+ echo "${header}"
+
+ n=0
+ while read -r build_id; do
+ # Although we no longer create releases with the exact
+ # same build ID in different image families, there are
+ # still older releases like cos-65-10323-104-0 and
+ # cos-stable-65-10323-104-0 that do have the same
+ # build ID. So, grep can return multiple lines.
+ all_lines=("$(grep "${build_id//./-}" "${TMP_IMAGE_LIST}")")
+ while read -r line; do
+ if [[ ("${line}" == *"DEPRECATED"* || "${line}" == *"OBSOLETE"*) && -z "${ALL}" ]]; then
+ continue
+ fi
+ mapfile -t milestone_family < <(get_milestone_family "${line}")
+ printf "%-14s %2s %6s" "${build_id}" "${milestone_family[0]}" "${milestone_family[1]}"
+ if [[ -n "${ALL}" ]]; then
+ if [[ "${line}" == *"DEPRECATED"* ]]; then
+ echo -n " dep"
+ elif [[ "${line}" == *"OBSOLETE"* ]]; then
+ echo -n " obs"
+ else
+ echo -n " "
+ fi
+ fi
+ echo -n " "
+ for f in "${FILES_TO_FETCH[@]}"; do
+ if grep -q "/${build_id}/${f}\$" "${TMP_BUILD_ID_FILES}"; then
+ echo -n "+++ "
+ else
+ echo -n "--- "
+ fi
+ done
+ echo
+ n=$((n + 1))
+ if [[ "${n}" -gt 25 ]]; then
+ echo
+ echo "${header}"
+ n=0
+ fi
+ done <<< "${all_lines[@]}"
+ done < "${TMP_BUILD_ID_LIST}"
+}
+
+
+subcmd_fetch() {
+ local f # file to fetch
+ local ff # complete URL of the file to fetch
+ local fetched # were any files fetched?
+ local md5 # md5sum checksum file
+ local bytes # size of file to fetch in bytes
+
+ mkdir -p "${FETCHED_FILES_DIR}"
+ fetched=false
+ for f in "${FILES_TO_FETCH[@]}"; do
+ # To save disk space, fetched files are deleted by default (see -r) after being verified and installed.
+ if [[ -f "${FETCHED_FILES_DIR}/${f}.verified" && -f "${FETCHED_FILES_DIR}/${f}.installed" ]]; then
+ "${ECHO}" "${f}": already verified and installed
+ continue
+ fi
+ fetched=true
+
+ if [[ -s "${FETCHED_FILES_DIR}/${f}" ]]; then
+ "${ECHO}" "${f}": already fetched
+ else
+ ff="${COS_GCS_BUCKET}/${BUILD_ID}/${f}"
+ # Does the file to fetch exist in GCS?
+ if ! gsutil -q stat "${ff}" 2> /dev/null; then
+ # A non-existent trusted key, toolchain, or toolchain_env is not fatal
+ # because older releases do not have them.
+ if [[ "${f}" != "${TRUSTED_KEY}" && "${f}" != "${TOOLCHAIN}" && "${f}" != "${TOOLCHAIN_ENV}" ]]; then
+ fatal "${ff}" does not exists
+ fi
+ warn "${ff}" does not exist
+ continue
+ fi
+
+ # How big is the file to fetch?
+ bytes="$(gsutil stat "${ff}" 2> /dev/null | awk '/Content-Length:/ { print $2 }')"
+ if [[ -z "${bytes}" ]]; then
+ fatal cannot determine the size of "${ff}"
+ fi
+ # Do we have enough disk space for the file to fetch?
+ if ! have_disk_space $((bytes / (1024 * 1024))); then
+ fatal not enough disk space to fetch "${ff}"
+ fi
+
+ # Fetch the file.
+ "${ECHO}" fetching "${ff}"
+ if ! fetch_file "${ff}" "${FETCHED_FILES_DIR}/${f}"; then
+ fatal could not fetch "${ff}"
+ rm -f "${FETCHED_FILES_DIR}/${f}"
+ fi
+ # Remember that haven't verified or installed the file that we fetched.
+ rm -f "${FETCHED_FILES_DIR}/${f}.verified" "${FETCHED_FILES_DIR}/${f}.installed"
+ fi
+
+ # See if there's an md5sum file to verify the file we fetched.
+ md5="${f}.md5"
+ if [[ -s "${FETCHED_FILES_DIR}/${md5}" ]]; then
+ "${ECHO}" "${md5}": already fetched
+ else
+ ff="${COS_GCS_BUCKET}/${BUILD_ID}/${md5}"
+ "${ECHO}" fetching "${ff}"
+ # The md5 file is missing for old builds, so we tolerate failure.
+ if ! fetch_file "${ff}" "${FETCHED_FILES_DIR}/${md5}"; then
+ # This error is not fatal because older tarballs do not have
+ # md5sum checksum files.
+ warn could not fetch "${ff}"
+ rm -f "${FETCHED_FILES_DIR}/${md5}"
+ fi
+ fi
+ done
+
+ if "${fetched}"; then
+ verify_fetched_files
+ fi
+}
+
+
+subcmd_build() {
+ subcmd_fetch
+ extract_files
+
+ # We need at least 2.4GB to build the kernel.
+ if ! have_disk_space 2400; then
+ fatal not enough disk space to build the kernel
+ fi
+
+ set_compilation_env
+ ${PRINT_CMD} cd "${KERNEL_SRC_DIR}"
+ ${PRINT_CMD} make ${MAKE_VERBOSE} -j $(($(nproc) * 2)) CC="${CC}" CXX="${CXX}"
+}
+
+
+subcmd_remove() {
+ local f
+
+ if [[ -n "${ALL}" ]]; then
+ for f in "${FETCHED_FILES_DIRNAME}" "${KERNEL_HEADERS_DIRNAME}" "${KERNEL_SRC_DIRNAME}" "${TOOLCHAIN_DIRNAME}" "${TOOLCHAIN_ENV_DIRNAME}"; do
+ info removing "${INSTALL_DIR}/${f}"
+ rm -rf "${INSTALL_DIR:?INSTALL_DIR not set}/${f}"
+ done
+ return
+ fi
+
+ for f in "${FETCHED_FILES_DIR}" "${KERNEL_HEADERS_DIR}" "${KERNEL_SRC_DIR}" "${TOOLCHAIN_DIR}" "${TOOLCHAIN_ENV_DIR}"; do
+ if [[ -n "${f}" ]]; then
+ info removing "${f}"
+ rm -rf "${f}"
+ fi
+ done
+
+ for f in "${TMP_IMAGE_LIST}" "${TMP_BUILD_ID_LIST}" "${TMP_BUILD_ID_FILES}"; do
+ if [[ -f "${f}" ]]; then
+ info removing "${f}"
+ rm -f "${f}"
+ fi
+ done
+}
+
+
+list_cos_images() {
+ gcloud compute images list --project "${COS_IMAGE_PROJECT}" --no-standard-images --show-deprecated
+}
+
+
+get_milestone_family() {
+ local line="$1"
+ local milestone
+ local family
+
+ #cos-65-10323-104-0 cos-cloud cos-65-lts DEPRECATED READY
+ #cos-dev-72-11190-0-0 cos-cloud cos-dev DEPRECATED READY
+ if [[ "${line}" =~ ^cos-[0-9][0-9]* ]]; then
+ # shellcheck disable=SC2001
+ milestone="$(echo "${line}" | sed -e 's/cos-\(.*\)-\(.*\)-\(.*\)-\([0-9][0-9]*\)\( *cos-cloud.*\)/\1/')"
+ family="lts"
+ else
+ # shellcheck disable=SC2001
+ milestone="$(echo "${line}" | sed -e 's/cos-\(.*\)-\(.*\)-\(.*\)-\(.*\)-\([0-9][0-9]*\)\( *cos-cloud.*\)/\2/')"
+ # shellcheck disable=SC2001
+ family="$(echo "${line}" | sed -e 's/cos-\(.*\)-\(.*\)-\(.*\)-\(.*\)-\([0-9][0-9]*\)\( *cos-cloud.*\)/\1/')"
+ fi
+ echo -e "${milestone}\n${family}"
+}
+
+
+fetch_file() {
+ local src="$1"
+ local dst="$2"
+
+ if ! gsutil cp "${src}" "${dst}" 2>/dev/null; then
+ return 1
+ fi
+
+ if ! test -s "${dst}"; then
+ return 1
+ fi
+}
+
+
+verify_fetched_files() {
+ local file
+ local f
+ local checksum
+
+ "${ECHO}"
+ for file in "${FILES_TO_FETCH[@]}"; do
+ f="${FETCHED_FILES_DIR}/${file}"
+ if [[ -f "${f}.verified" ]]; then
+ "${ECHO}" "${file}": already verified
+ continue
+ fi
+ if [[ ! -f "${f}.md5" ]]; then
+ warn "${file}.md5" does not exist, skipping verification
+ continue
+ fi
+ checksum="$(md5sum "${f}" | awk '{ print $1 }')"
+ if [[ "${checksum}" == "$(cat "${f}.md5")" ]]; then
+ "${ECHO}" verified "${file}"
+ touch "${f}.verified"
+ else
+ fatal "${file}" md5sum mismatch: expected "$(cat "${f}.md5")", got "${checksum}"
+ fi
+ done
+}
+
+
+extract_files() {
+ local f
+ local installed=false
+
+ "${ECHO}"
+
+ for f in "${KERNEL_HEADERS}" "${KERNEL_SRC}" "${TOOLCHAIN}" "${TOOLCHAIN_ENV}"; do
+ if install "${f}"; then
+ installed=true
+ fi
+ done
+
+ if setup_kernel_config; then
+ installed=true
+ fi
+
+ setup_trusted_key
+
+ if "${installed}"; then
+ echo
+ fi
+}
+
+
+install() {
+ local f="$1" # file that was fetched
+ local ff
+ local dir
+ local cmd
+
+ ff="${FETCHED_FILES_DIR}/${f}"
+ if [[ -f "${ff}.installed" ]]; then
+ "${ECHO}" "${ff}": already installed
+ return 1
+ fi
+
+ # Do we have enough disk space to install?
+ if ! have_disk_space "${INSTALL_SIZE["${f}"]}"; then
+ fatal not enough disk space to fetch "${ff}"
+ fi
+
+ info installing "${ff}"
+ case "${f}" in
+ "${KERNEL_HEADERS}") dir="${KERNEL_HEADERS_DIR}";;
+ "${KERNEL_SRC}") dir="${KERNEL_SRC_DIR}";;
+ "${TOOLCHAIN}") dir="${TOOLCHAIN_DIR}";;
+ "${TOOLCHAIN_ENV}") dir="${TOOLCHAIN_ENV_DIR}";;
+ *) fatal "don't know where to install ${f}";;
+ esac
+ mkdir -p "${dir}"
+ cmd="${INSTALL_CMD[${f}]:-none}"
+ if [[ "${cmd}" == "none" ]]; then
+ fatal "don't know how to install ${ff}"
+ fi
+ eval "${cmd}"
+ touch "${ff}.installed"
+ if "${REMOVE}"; then
+ rm "${ff}"
+ fi
+ return 0
+}
+
+
+setup_kernel_config() {
+ local kernel_config="${KERNEL_SRC_DIR}/.config"
+ local f
+
+ # Was kernel configuration file specified on the command line?
+ if [[ -n "${KERNEL_CONFIG}" ]]; then
+ info creating kernel config file from "${KERNEL_CONFIG}"
+ if [[ "${KERNEL_CONFIG}" == "/proc/config.gz" ]]; then
+ zcat "${KERNEL_CONFIG}" > "${kernel_config}"
+ else
+ cp -a "${KERNEL_CONFIG}" "${KERNEL_SRC_DIR}"
+ fi
+ return 0
+ fi
+
+ if [[ -s "${kernel_config}" ]]; then
+ "${ECHO}" "${kernel_config}": already exists
+ return 1
+ fi
+
+ info copying kernel config from kernel headers
+ f="$(eval echo "${KERNEL_HEADERS_DIR}"/usr/src/linux-headers-*/.config)"
+ if [[ ! -f "${f}" ]]; then
+ fatal "${f}" does not exist
+ fi
+ cp -a "${f}" "${kernel_config}"
+ return 0
+}
+
+
+setup_trusted_key() {
+ local kernel_config="${KERNEL_SRC_DIR}/.config"
+ local output
+ local cmd
+
+ # Check CONFIG_SYSTEM_TRUSTED_KEYS to see if we have to copy the trusted key
+ # to the kernel source directory.
+ output="$(grep -w "CONFIG_SYSTEM_TRUSTED_KEYS" "${kernel_config}")" || true
+ if [[ -z "${output}" ]]; then
+ warn CONFIG_SYSTEM_TRUSTED_KEYS not in "${kernel_config}"
+ return
+ fi
+ if ! echo "${output}" | grep -qw "certs/${TRUSTED_KEY}"; then
+ return
+ fi
+
+ # Did we fetch the trusted key?
+ if [[ -f "${FETCHED_FILES_DIR}/${TRUSTED_KEY}" ]]; then
+ if [[ -f "${TRUSTED_KEY_DIR}/${TRUSTED_KEY}" ]]; then
+ "${ECHO}" trusted key "${TRUSTED_KEY_DIR}/${TRUSTED_KEY}": already exists
+ return
+ fi
+
+ info copying trusted key to "${TRUSTED_KEY_DIR}/${TRUSTED_KEY}"
+ cmd="${INSTALL_CMD[${TRUSTED_KEY}]:-none}"
+ if [[ "${cmd}" == "none" ]]; then
+ fatal "don't know how to install ${TRUSTED_KEY}"
+ fi
+ eval "${cmd}"
+ else
+ warn "modifying CONFIG_SYSTEM_TRUSTED_KEYS's value because we could not fetch the trusted key"
+ sed -i.bak -e 's/CONFIG_SYSTEM_TRUSTED_KEYS=.*/CONFIG_SYSTEM_TRUSTED_KEYS=""/' "${kernel_config}"
+ echo diff "${kernel_config}" "${kernel_config}.bak"
+ diff "${kernel_config}" "${kernel_config}.bak" || true
+ fi
+}
+
+
+set_compilation_env() {
+ local path
+
+ path="$(realpath "${TOOLCHAIN_DIR}/bin")"
+ ${PRINT_CMD} export PATH="${path}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/google-cloud-sdk/bin"
+
+ if [[ -s "${TOOLCHAIN_ENV_DIR}/${TOOLCHAIN_ENV}" ]]; then
+ # shellcheck disable=SC1090
+ source "${TOOLCHAIN_ENV_DIR}/${TOOLCHAIN_ENV}"
+ else
+ # To support COS build not having toolchain_env file
+ CC="x86_64-cros-linux-gnu-gcc"
+ CXX="x86_64-cros-linux-gnu-g++"
+ fi
+}
+
+
+have_disk_space() {
+ local need="$1"
+ local avail
+
+ avail="$(df -BM --output=avail "${INSTALL_DIR}" | sed -n -e 's/M//p')"
+ if [[ "${avail}" -lt "${need}" ]]; then
+ error need at least "${need}"MB, but have only "${avail}"MB in "${INSTALL_DIR}"
+ NO_DISK_SPACE=true
+ return 1
+ fi
+ return 0
+}
+
+
+help_disk_space() {
+cat <<'END'
+
+NOTE:
+Because by default toolbox uses /var/lib/toolbox as its working directory,
+you can run out of space if your root partition is not big enough.
+
+You can add a second drive to your COS instance and use it as the working
+directory of toolbox. For example, the following code creates a second
+disk, attaches it to the instance, uses cloud-init to mount it on each
+reboot, and assigns it to toolbox:
+
+ # On your desktop:
+ $ gcloud compute disks create <your-disk> --size=200GB
+ $ gcloud compute instances attach-disk <your-instance> --disk <your-disk>
+ $ cat > user_data <<EOF
+ #cloud-config
+
+ bootcmd:
+ - if [ -z "$(sudo blkid /dev/sdb)" ]; then mkfs.ext4 /dev/sdb; fi
+ - fsck.ext4 /dev/sdb
+ - mkdir -p /mnt/disks/sdb
+ - mount -t ext4 /dev/sdb /mnt/disks/sdb
+ EOF
+ $ gcloud compute instances add-metadata <your-instance> --metadata-from-file=user-data=user_data
+
+ # On your COS instance:
+ $ echo TOOLBOX_DIRECTORY="/mnt/disks/sdb" >> $HOME/.toolboxrc
+ $ sudo reboot
+END
+}
+
+
+info() {
+ if [[ -n "${*}" ]]; then
+ echo -e "${BLUE_S}INFO: ${*}${ANSI_E}" >&2
+ else
+ echo
+ fi
+}
+
+
+warn() {
+ if [[ "${ECHO}" != ":" ]]; then
+ echo -e "${PURPLE_S}WARNING: ${*}${ANSI_E}" >&2
+ fi
+}
+
+
+error() {
+ echo -e "${RED_S}ERROR: ${*}${ANSI_E}" >&2
+}
+
+
+fatal() {
+ error "${@}"
+ if ${NO_DISK_SPACE}; then
+ help_disk_space
+ fi
+ exit 1
+}
+
+
+main "${@}"