123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- #!/bin/sh
- #
- # Copyright 2011 The ChromiumOS Authors
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- #
- # Note: This file must be written in dash compatible way as scripts that use
- # this may run in the Chrome OS client enviornment.
- # shellcheck disable=SC2039,SC2059,SC2155
- # Determine script directory
- SCRIPT_DIR=$(dirname "$0")
- PROG=$(basename "$0")
- : "${GPT:=cgpt}"
- : "${FUTILITY:=futility}"
- # The tag when the rootfs is changed.
- TAG_NEEDS_TO_BE_SIGNED="/root/.need_to_be_signed"
- # List of Temporary files and mount points.
- TEMP_FILE_LIST=$(mktemp)
- TEMP_DIR_LIST=$(mktemp)
- # Finds and loads the 'shflags' library, or return as failed.
- load_shflags() {
- # Load shflags
- if [ -f /usr/share/misc/shflags ]; then
- # shellcheck disable=SC1090,SC1091
- . /usr/share/misc/shflags
- elif [ -f "${SCRIPT_DIR}/lib/shflags/shflags" ]; then
- # shellcheck disable=SC1090
- . "${SCRIPT_DIR}/lib/shflags/shflags"
- else
- echo "ERROR: Cannot find the required shflags library."
- return 1
- fi
- # Add debug option for debug output below
- DEFINE_boolean debug $FLAGS_FALSE "Provide debug messages" "d"
- }
- # Functions for debug output
- # ----------------------------------------------------------------------------
- # These helpers are for runtime systems. For scripts using common.sh,
- # they'll get better definitions that will clobber these ones.
- info() {
- echo "${PROG}: INFO: $*" >&2
- }
- warn() {
- echo "${PROG}: WARN: $*" >&2
- }
- error() {
- echo "${PROG}: ERROR: $*" >&2
- }
- # Reports error message and exit(1)
- # Args: error message
- die() {
- error "$@"
- exit 1
- }
- # Returns true if we're running in debug mode.
- #
- # Note that if you don't set up shflags by calling load_shflags(), you
- # must set $FLAGS_debug and $FLAGS_TRUE yourself. The default
- # behavior is that debug will be off if you define neither $FLAGS_TRUE
- # nor $FLAGS_debug.
- is_debug_mode() {
- [ "${FLAGS_debug:-not$FLAGS_TRUE}" = "$FLAGS_TRUE" ]
- }
- # Prints messages (in parameters) in debug mode
- # Args: debug message
- debug_msg() {
- if is_debug_mode; then
- echo "DEBUG: $*" 1>&2
- fi
- }
- # Functions for temporary files and directories
- # ----------------------------------------------------------------------------
- # Create a new temporary file and return its name.
- # File is automatically cleaned when cleanup_temps_and_mounts() is called.
- make_temp_file() {
- local tempfile="$(mktemp)"
- echo "$tempfile" >> "$TEMP_FILE_LIST"
- echo "$tempfile"
- }
- # Create a new temporary directory and return its name.
- # Directory is automatically deleted and any filesystem mounted on it unmounted
- # when cleanup_temps_and_mounts() is called.
- make_temp_dir() {
- local tempdir=$(mktemp -d)
- echo "$tempdir" >> "$TEMP_DIR_LIST"
- echo "$tempdir"
- }
- cleanup_temps_and_mounts() {
- while read -r line; do
- rm -f "$line"
- done < "$TEMP_FILE_LIST"
- set +e # umount may fail for unmounted directories
- while read -r line; do
- if [ -n "$line" ]; then
- if has_needs_to_be_resigned_tag "$line"; then
- echo "Warning: image may be modified. Please resign image."
- fi
- sudo umount "$line" 2>/dev/null
- rm -rf "$line"
- fi
- done < "$TEMP_DIR_LIST"
- set -e
- rm -rf "$TEMP_DIR_LIST" "$TEMP_FILE_LIST"
- }
- trap "cleanup_temps_and_mounts" EXIT
- # Functions for partition management
- # ----------------------------------------------------------------------------
- # Construct a partition device name from a whole disk block device and a
- # partition number.
- # This works for [/dev/sda, 3] (-> /dev/sda3) as well as [/dev/mmcblk0, 2]
- # (-> /dev/mmcblk0p2).
- make_partition_dev() {
- local block="$1"
- local num="$2"
- # If the disk block device ends with a number, we add a 'p' before the
- # partition number.
- if [ "${block%[0-9]}" = "${block}" ]; then
- echo "${block}${num}"
- else
- echo "${block}p${num}"
- fi
- }
- # Find the block size of a device in bytes
- # Args: DEVICE (e.g. /dev/sda)
- # Return: block size in bytes
- blocksize() {
- local output=''
- local path="$1"
- if [ -b "${path}" ]; then
- local dev="${path##*/}"
- local sys="/sys/block/${dev}/queue/logical_block_size"
- output="$(cat "${sys}" 2>/dev/null)"
- fi
- echo "${output:-512}"
- }
- # Read GPT table to find the starting location of a specific partition.
- # Args: DEVICE PARTNUM
- # Returns: offset (in sectors) of partition PARTNUM
- partoffset() {
- sudo "$GPT" show -b -i "$2" "$1"
- }
- # Read GPT table to find the size of a specific partition.
- # Args: DEVICE PARTNUM
- # Returns: size (in sectors) of partition PARTNUM
- partsize() {
- sudo "$GPT" show -s -i "$2" "$1"
- }
- # Tags a file system as "needs to be resigned".
- # Args: MOUNTDIRECTORY
- tag_as_needs_to_be_resigned() {
- local mount_dir="$1"
- sudo touch "$mount_dir/$TAG_NEEDS_TO_BE_SIGNED"
- }
- # Determines if the target file system has the tag for resign
- # Args: MOUNTDIRECTORY
- # Returns: true if the tag is there otherwise false
- has_needs_to_be_resigned_tag() {
- local mount_dir="$1"
- [ -f "$mount_dir/$TAG_NEEDS_TO_BE_SIGNED" ]
- }
- # Determines if the target file system is a Chrome OS root fs
- # Args: MOUNTDIRECTORY
- # Returns: true if MOUNTDIRECTORY looks like root fs, otherwise false
- is_rootfs_partition() {
- local mount_dir="$1"
- [ -f "$mount_dir/$(dirname "$TAG_NEEDS_TO_BE_SIGNED")" ]
- }
- # If the kernel is buggy and is unable to loop+mount quickly,
- # retry the operation a few times.
- # Args: IMAGE PARTNUM MOUNTDIRECTORY [ro]
- #
- # This function does not check whether the partition is allowed to be mounted as
- # RW. Callers must ensure the partition can be mounted as RW before calling
- # this function without |ro| argument.
- _mount_image_partition_retry() {
- local image=$1
- local partnum=$2
- local mount_dir=$3
- local ro=$4
- local bs="$(blocksize "${image}")"
- local offset=$(( $(partoffset "${image}" "${partnum}") * bs ))
- local out try
- # shellcheck disable=SC2086
- set -- sudo LC_ALL=C mount -o loop,offset=${offset},${ro} \
- "${image}" "${mount_dir}"
- try=1
- while [ ${try} -le 5 ]; do
- if ! out=$("$@" 2>&1); then
- if [ "${out}" = "mount: you must specify the filesystem type" ]; then
- printf 'WARNING: mounting %s at %s failed (try %i); retrying\n' \
- "${image}" "${mount_dir}" "${try}"
- # Try to "quiet" the disks and sleep a little to reduce contention.
- sync
- sleep $(( try * 5 ))
- else
- # Failed for a different reason; abort!
- break
- fi
- else
- # It worked!
- return 0
- fi
- : $(( try += 1 ))
- done
- echo "ERROR: mounting ${image} at ${mount_dir} failed:"
- echo "${out}"
- # We don't preserve the exact exit code of `mount`, but since
- # no one in this code base seems to check it, it's a moot point.
- return 1
- }
- # If called without 'ro', make sure the partition is allowed to be mounted as
- # 'rw' before actually mounting it.
- # Args: IMAGE PARTNUM MOUNTDIRECTORY [ro]
- _mount_image_partition() {
- local image=$1
- local partnum=$2
- local mount_dir=$3
- local ro=$4
- local bs="$(blocksize "${image}")"
- local offset=$(( $(partoffset "${image}" "${partnum}") * bs ))
- if [ "$ro" != "ro" ]; then
- # Forcibly call enable_rw_mount. It should fail on unsupported
- # filesystems and be idempotent on ext*.
- enable_rw_mount "${image}" ${offset} 2> /dev/null
- fi
- _mount_image_partition_retry "$@"
- }
- # If called without 'ro', make sure the partition is allowed to be mounted as
- # 'rw' before actually mounting it.
- # Args: LOOPDEV PARTNUM MOUNTDIRECTORY [ro]
- _mount_loop_image_partition() {
- local loopdev=$1
- local partnum=$2
- local mount_dir=$3
- local ro=$4
- local loop_rootfs="${loopdev}p${partnum}"
- if [ "$ro" != "ro" ]; then
- # Forcibly call enable_rw_mount. It should fail on unsupported
- # filesystems and be idempotent on ext*.
- enable_rw_mount "${loop_rootfs}" 2>/dev/null
- fi
- sudo mount -o "${ro}" "${loop_rootfs}" "${mount_dir}"
- }
- # Mount a partition read-only from an image into a local directory
- # Args: IMAGE PARTNUM MOUNTDIRECTORY
- mount_image_partition_ro() {
- _mount_image_partition "$@" "ro"
- }
- # Mount a partition read-only from an image into a local directory
- # Args: LOOPDEV PARTNUM MOUNTDIRECTORY
- mount_loop_image_partition_ro() {
- _mount_loop_image_partition "$@" "ro"
- }
- # Mount a partition from an image into a local directory
- # Args: IMAGE PARTNUM MOUNTDIRECTORY
- mount_image_partition() {
- local mount_dir=$3
- _mount_image_partition "$@"
- if is_rootfs_partition "${mount_dir}"; then
- tag_as_needs_to_be_resigned "${mount_dir}"
- fi
- }
- # Mount a partition from an image into a local directory
- # Args: LOOPDEV PARTNUM MOUNTDIRECTORY
- mount_loop_image_partition() {
- local mount_dir=$3
- _mount_loop_image_partition "$@"
- if is_rootfs_partition "${mount_dir}"; then
- tag_as_needs_to_be_resigned "${mount_dir}"
- fi
- }
- # Mount the image's ESP (EFI System Partition) on a newly created temporary
- # directory.
- # Prints out the newly created temporary directory path if succeeded.
- # If the image doens't have an ESP partition, returns 0 without print anything.
- # Args: LOOPDEV
- # Returns: 0 if succeeded, 1 otherwise.
- mount_image_esp() {
- local loopdev="$1"
- local ESP_PARTNUM=12
- local loop_esp="${loopdev}p${ESP_PARTNUM}"
- local esp_offset=$(( $(partoffset "${loopdev}" "${ESP_PARTNUM}") ))
- # Check if the image has an ESP partition.
- if [[ "${esp_offset}" == "0" ]]; then
- return 0
- fi
- local esp_dir="$(make_temp_dir)"
- if ! sudo mount -o "${ro}" "${loop_esp}" "${esp_dir}"; then
- return 1
- fi
- echo "${esp_dir}"
- return 0
- }
- # Extract a partition to a file
- # Args: IMAGE PARTNUM OUTPUTFILE
- extract_image_partition() {
- local image=$1
- local partnum=$2
- local output_file=$3
- local offset=$(partoffset "$image" "$partnum")
- local size=$(partsize "$image" "$partnum")
- # shellcheck disable=SC2086
- dd if="$image" of="$output_file" bs=512 skip=$offset count=$size \
- conv=notrunc 2>/dev/null
- }
- # Replace a partition in an image from file
- # Args: IMAGE PARTNUM INPUTFILE
- replace_image_partition() {
- local image=$1
- local partnum=$2
- local input_file=$3
- local offset=$(partoffset "$image" "$partnum")
- local size=$(partsize "$image" "$partnum")
- # shellcheck disable=SC2086
- dd if="$input_file" of="$image" bs=512 seek=$offset count=$size \
- conv=notrunc 2>/dev/null
- }
- # For details, see crosutils.git/common.sh
- enable_rw_mount() {
- local rootfs="$1"
- local offset="${2-0}"
- # Make sure we're checking an ext2 image
- # shellcheck disable=SC2086
- if ! is_ext2 "$rootfs" $offset; then
- echo "enable_rw_mount called on non-ext2 filesystem: $rootfs $offset" 1>&2
- return 1
- fi
- local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte
- # Dash can't do echo -ne, but it can do printf "\NNN"
- # We could use /dev/zero here, but this matches what would be
- # needed for disable_rw_mount (printf '\377').
- printf '\000' |
- sudo dd of="$rootfs" seek=$((offset + ro_compat_offset)) \
- conv=notrunc count=1 bs=1 2>/dev/null
- }
- # For details, see crosutils.git/common.sh
- is_ext2() {
- local rootfs="$1"
- local offset="${2-0}"
- # Make sure we're checking an ext2 image
- local sb_magic_offset=$((0x438))
- local sb_value=$(sudo dd if="$rootfs" skip=$((offset + sb_magic_offset)) \
- count=2 bs=1 2>/dev/null)
- local expected_sb_value=$(printf '\123\357')
- if [ "$sb_value" = "$expected_sb_value" ]; then
- return 0
- fi
- return 1
- }
- disable_rw_mount() {
- local rootfs="$1"
- local offset="${2-0}"
- # Make sure we're checking an ext2 image
- # shellcheck disable=SC2086
- if ! is_ext2 "$rootfs" $offset; then
- echo "disable_rw_mount called on non-ext2 filesystem: $rootfs $offset" 1>&2
- return 1
- fi
- local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte
- # Dash can't do echo -ne, but it can do printf "\NNN"
- # We could use /dev/zero here, but this matches what would be
- # needed for disable_rw_mount (printf '\377').
- printf '\377' |
- sudo dd of="$rootfs" seek=$((offset + ro_compat_offset)) \
- conv=notrunc count=1 bs=1 2>/dev/null
- }
- rw_mount_disabled() {
- local rootfs="$1"
- local offset="${2-0}"
- # Make sure we're checking an ext2 image
- # shellcheck disable=SC2086
- if ! is_ext2 "$rootfs" $offset; then
- return 2
- fi
- local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte
- local ro_value=$(sudo dd if="$rootfs" skip=$((offset + ro_compat_offset)) \
- count=1 bs=1 2>/dev/null)
- local expected_ro_value=$(printf '\377')
- if [ "$ro_value" = "$expected_ro_value" ]; then
- return 0
- fi
- return 1
- }
- # Functions for CBFS management
- # ----------------------------------------------------------------------------
- # Get the compression algorithm used for the given CBFS file.
- # Args: INPUT_CBFS_IMAGE CBFS_FILE_NAME
- get_cbfs_compression() {
- cbfstool "$1" print -r "FW_MAIN_A" | awk -vname="$2" '$1 == name {print $5}'
- }
- # Store a file in CBFS.
- # Args: INPUT_CBFS_IMAGE INPUT_FILE CBFS_FILE_NAME
- store_file_in_cbfs() {
- local image="$1"
- local file="$2"
- local name="$3"
- local compression=$(get_cbfs_compression "$1" "${name}")
- # Don't re-add a file to a section if it's unchanged. Otherwise this seems
- # to break signature of existing contents. https://crbug.com/889716
- if cbfstool "${image}" extract -r "FW_MAIN_A,FW_MAIN_B" \
- -f "${file}.orig" -n "${name}"; then
- if cmp -s "${file}" "${file}.orig"; then
- rm -f "${file}.orig"
- return
- fi
- rm -f "${file}.orig"
- fi
- cbfstool "${image}" remove -r "FW_MAIN_A,FW_MAIN_B" -n "${name}" || return
- # This add can fail if
- # 1. Size of a signature after compression is larger
- # 2. CBFS is full
- # These conditions extremely unlikely become true at the same time.
- cbfstool "${image}" add -r "FW_MAIN_A,FW_MAIN_B" -t "raw" \
- -c "${compression}" -f "${file}" -n "${name}" || return
- }
- # Misc functions
- # ----------------------------------------------------------------------------
- # Parses the version file containing key=value lines
- # Args: key file
- # Returns: value
- get_version() {
- local key="$1"
- local file="$2"
- awk -F= -vkey="${key}" '$1 == key { print $NF }' "${file}"
- }
- # Returns true if all files in parameters exist.
- # Args: List of files
- ensure_files_exist() {
- local filename return_value=0
- for filename in "$@"; do
- if [ ! -f "$filename" ] && [ ! -b "$filename" ]; then
- echo "ERROR: Cannot find required file: $filename"
- return_value=1
- fi
- done
- return $return_value
- }
- # Check if the 'chronos' user already has a password
- # Args: rootfs
- no_chronos_password() {
- local rootfs=$1
- # Make sure the chronos user actually exists.
- if grep -qs '^chronos:' "${rootfs}/etc/passwd"; then
- sudo grep -q '^chronos:\*:' "${rootfs}/etc/shadow"
- fi
- }
- # Returns true if given ec.bin is signed or false if not.
- is_ec_rw_signed() {
- ${FUTILITY} dump_fmap "$1" | grep -q KEY_RO
- }
|