| #!/bin/sh |
| # Copyright (c) 2010 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. |
| |
| # This is enabled by pack_firmware.py when unified builds is being used. |
| # In that case the variables below are set later because we don't have access |
| # to the configuration until we unpack the archive. |
| UNIBUILD= |
| |
| if [ -z "${UNIBUILD}" ]; then |
| # Version information. Please keep this in head of the updater. |
| TARGET_RO_FWID="REPLACE_RO_FWID" |
| TARGET_FWID="REPLACE_FWID" |
| TARGET_ECID="REPLACE_ECID" |
| TARGET_PDID="REPLACE_PDID" |
| TARGET_PLATFORM="REPLACE_PLATFORM" |
| STABLE_FWID="REPLACE_STABLE_FWID" |
| STABLE_ECID="REPLACE_STABLE_ECID" |
| STABLE_PDID="REPLACE_STABLE_PDID" |
| fi |
| |
| TARGET_SCRIPT="REPLACE_SCRIPT" |
| |
| # Export all version information |
| export TARGET_FWID TARGET_RO_FWID TARGET_ECID TARGET_PDID |
| export TARGET_PLATFORM TARGET_SCRIPT |
| export STABLE_FWID STABLE_ECID STABLE_PDID |
| export UNIBUILD |
| set -e |
| |
| # Global variables |
| SELF="$(readlink -f "$0")" |
| |
| # Set by make_temp function and removed by clean_temp |
| TMP_DIR= |
| # Decides if we need to print debug messages |
| IS_DEBUG= |
| # The default script to be invoked from extracted bundle |
| SCRIPT="./${TARGET_SCRIPT:-updater.sh}" |
| |
| # Tag file to prohibit updater execution |
| TAG_FILE_DISABLED='/root/.leave_firmware_alone' |
| # Set to True to bypass checking TAG_FILE_DISABLED |
| IS_FORCED= |
| |
| # Set to true to prevent printing error alerts by error return value |
| IS_IGNORE_RC= |
| |
| # Prints a message and return an error code ($1). |
| die_as() { |
| local ret="$1" |
| shift |
| echo "ERROR: $@" >&2 |
| exit "$ret" |
| } |
| |
| # Prints a message and return error as 1. |
| die() { |
| die_as 1 "$@" |
| } |
| |
| # Prints messages if $IS_DEBUG is not empty. |
| debug() { |
| [ -z "$IS_DEBUG" ] || echo "$@" >&2 |
| } |
| |
| # Creates a temporary folder |
| make_temp() { |
| TMP_DIR="$(mktemp -d --tmpdir)" || |
| die "Failed to create temporary folder" |
| trap clean_temp EXIT |
| } |
| |
| # Creates a temporary folder with execution permission |
| make_exec_temp() { |
| make_temp |
| if [ "$(id -u)" = "0" ]; then |
| debug "bind and remount for allowing execution ..." |
| (mount --bind "$TMP_DIR" "$TMP_DIR" && mount -o remount,exec "$TMP_DIR") || |
| die "Failed to enable execution permission of folder $TMP_DIR" |
| else |
| debug "Not running as root: assuming we can execute in /tmp" |
| fi |
| } |
| |
| # Cleans temporary folders |
| clean_temp() { |
| debug "clean_temp: started." |
| if [ -d "$TMP_DIR" ]; then |
| umount -f "$TMP_DIR" >/dev/null 2>&1 || true |
| rm -rf "$TMP_DIR" >/dev/null 2>&1 || true |
| TMP_DIR="" |
| fi |
| } |
| |
| # Extracts bundle content to specified location |
| extract_bundle() { |
| local destination="$1" |
| sh "$SELF" --sb_extract "$destination" >/dev/null || |
| die "Cannot extract bundle content to: $destination" |
| } |
| |
| # Executes a script in bundle |
| exec_bundle_script() { |
| local rc=0 |
| make_exec_temp |
| extract_bundle "$TMP_DIR" |
| [ -x "$TMP_DIR/$SCRIPT" ] || die "Missing program in bundle: $SCRIPT" |
| |
| debug "Start running script: $SCRIPT $@" |
| (cd "$TMP_DIR" && "$SCRIPT" "$@") || rc="$?" |
| |
| if [ "$rc" -ne 0 -a -z "$IS_IGNORE_RC" ]; then |
| die_as "$rc" "Execution failed: $SCRIPT (error code = $rc)" |
| fi |
| exit "$rc" |
| } |
| |
| # Prepares for extraction with shar |
| prepare_shar_extract() { |
| local destination="$1" |
| if [ -z "$destination" ]; then |
| make_temp |
| destination="$TMP_DIR" |
| # Don't remove the temporary files |
| TMP_DIR="" |
| fi |
| echo "Extracting to: $destination" |
| cd "$destination" || die "Invalid destination: $destination" |
| exec >/dev/null # Prevent shar messages in stdout |
| set -- "-c" # Force shar to overwrite files |
| } |
| |
| find_version_string() { |
| local filename="$1" |
| local pattern="$2" |
| local section="$3" |
| if [ ! -s "$filename" ]; then |
| return |
| fi |
| |
| # Chrome OS & chroot has dump_fmap, and standard Linux desktop has strings. |
| if type dump_fmap >/dev/null 2>&1; then |
| |
| local tmpdir="$(mktemp -d)" |
| local filepath="$(readlink -f "$filename")" |
| (cd "$tmpdir"; dump_fmap -x "$filepath" "$section") >/dev/null 2>&1 |
| cat "$tmpdir/$section" #2>/dev/null |
| rm -rf "$tmpdir" |
| |
| elif type strings >/dev/null 2>&1; then |
| |
| local versions=$( (strings "$filename" | grep "$pattern") || true) |
| local version="$(echo "$versions" | uniq)" |
| |
| local num_versions="$(echo "$versions" | wc -l)" |
| local num_version="$(echo "$version" | wc -l)" |
| |
| # To deal with firmware having RO != RW, we need to find difference |
| # between RO_FRID, RW_FWID_A and RW_FWID_B; and unfortunately that is not |
| # possible if there's just strings, since we don't know the ordering of |
| # RO/RW sections. The hack here is, if there are three versions with only |
| # 2 different values, we assume the duplicated = RW and unique = RO. |
| if [ "$num_version" -eq 1 ]; then |
| echo "$version" |
| elif [ "$num_version" -eq 2 -a "$num_versions" -eq 3 ]; then |
| case "$section" in |
| RO_FRID) |
| echo "$versions" | uniq -u |
| ;; |
| RW_FWID_*) |
| echo "$versions" | uniq -d |
| ;; |
| *) |
| echo "WARNING: Unknown firmware versions for $filename." >&2 |
| ;; |
| esac |
| else |
| echo "WARNING: cannot identify firmware version for $filename." >&2 |
| fi |
| |
| else |
| |
| (echo "WARNING: 'strings' and 'dump_fmap' are both not available." |
| echo " TARGET_{FW,EC,PD}ID can't be updated." |
| echo " You have to manually change that or repack on a desktop." |
| ) >&2 |
| |
| fi |
| } |
| |
| # Repacks current file ($SELF) by given source folder. |
| perform_shar_repack() { |
| local new_source="$1" |
| local cut_mark="$(sed -n '/^##CUTHERE##/=' "$SELF")" |
| local md5_file="$new_source/VERSION.md5" |
| local ro_fw_ver="$(find_version_string "$new_source/bios.bin" \ |
| '^Google_' RO_FRID)" |
| local fw_ver="$(find_version_string "$new_source/bios.bin" \ |
| '^Google_' RW_FWID_A)" |
| local ec_ver="$(find_version_string "$new_source/ec.bin" \ |
| '^[a-zA-Z0-9]*_v[0-9\.]*-[a-z0-9]*$' RO_FRID)" |
| local pd_ver="$(find_version_string "$new_source/pd.bin" \ |
| '^[a-zA-Z0-9]*_v[0-9\.]*-[a-z0-9]*$' RO_FRID)" |
| |
| # Since mosys r430, trailing spaces reported by mosys is always scrubbed. |
| ec_ver="$(echo "$ec_ver" | sed 's/ *$//')" |
| |
| [ "$cut_mark" -gt 0 ] || die "File corrupted: $SELF" |
| sed -i "$((cut_mark + 1)),\$d" "$SELF" || |
| die "Failed to truncate existing data in $SELF" |
| |
| # Try to update firmware version if available. This doesn't work with |
| # unified builds since the variables are in separate files. |
| if [ -z "${UNIBUILD}" ]; then |
| if [ -n "$fw_ver" ]; then |
| sed -i 's/^\( *TARGET_FWID=\)".*"/\1"'"$fw_ver"'"/' "$SELF" && |
| echo "Changed TARGET_FWID to $fw_ver" |
| sed -i 's/^\( *TARGET_RO_FWID=\)".*"/\1"'"$ro_fw_ver"'"/' \ |
| "$SELF" && |
| echo "Changed TARGET_ROFWID to $ro_fw_ver" |
| if [ -s "$new_source/VERSION" ]; then |
| sed -i "s/^\(BIOS version: *\).*/\1$ro_fw_ver/; |
| s/^\(BIOS .RW. version: *\).*/\1$fw_ver/" "$new_source/VERSION" |
| fi |
| fi |
| if [ -n "$ec_ver" ]; then |
| sed -i 's/^\( *TARGET_ECID=\)".*"/\1"'"$ec_ver"'"/' "$SELF" && |
| echo "Changed TARGET_ECID to $ec_ver" |
| [ -s "$new_source/VERSION" ] && |
| sed -i "s/^\(EC version:*\).*/\1$ec_ver/" "$new_source/VERSION" |
| fi |
| if [ -n "$pd_ver" ]; then |
| sed -i 's/^\( *TARGET_PDID=\)".*"/\1"'"$pd_ver"'"/' "$SELF" && |
| echo "Changed TARGET_PDID to $pd_ver" |
| [ -s "$new_source/VERSION" ] && |
| sed -i "s/^\(PD version:*\).*/\1$pd_ver/" "$new_source/VERSION" |
| fi |
| fi |
| |
| # Update checksum data for every files except VERSION* |
| (cd "$new_source" && |
| echo "Package Content:" && |
| find . -type f '!' -name VERSION.md5 '!' -name VERSION \ |
| -exec md5sum -b '{}' '+' ) >"$md5_file" |
| |
| # Use a standard timestamp for the version files so that we get the same |
| # exact sharball each time. Otherwise the changing timestamps creates small |
| # differences. |
| touch -t 201701010000 "$new_source/VERSION" "$md5_file" |
| |
| # Build shar content with files in sorted order for repeatability. |
| (cd "$new_source" && |
| find . -type f | sort | |
| shar -Q -q -x -m --no-character-count -D --no-i18n -z -g 1 | |
| sed -r 's/^lock_dir=_sh.*/lock_dir=_fwupdate/; |
| s"^begin ([0-9]+) _sh[^/]*"begin \1 _fwupdate" |
| /^# Made on .* by/d; |
| /^# Source directory was /d') >>"$SELF" || |
| die "Failed repacking from $new_source" |
| } |
| |
| # Prints the VAR from '--param VAR' and '--param=VAR' format. |
| get_parameter_variable() { |
| local param="$1" |
| local param_name="${param%%=*}" |
| local param_value="${param#*=}" |
| |
| if [ "$param" = "$param_name" ]; then |
| echo "$2" |
| else |
| echo "$param_value" |
| fi |
| } |
| |
| # Main entry |
| main() { |
| local original_params="$*" |
| |
| case "$1" in |
| --sb_extract | --sb_extract=*) |
| local destination="$(get_parameter_variable "$@")" |
| prepare_shar_extract "$destination" |
| return # Let shar handle the remaining stuff |
| ;; |
| |
| --sb_repack ) |
| local new_source="$(get_parameter_variable "$@")" |
| [ -d "$new_source" ] || die "Invalid source folder: $new_source" |
| echo "Repacking from: $new_source" |
| perform_shar_repack "$new_source" |
| exit 0 |
| ;; |
| |
| -V) |
| # Read information |
| make_temp |
| extract_bundle "$TMP_DIR" |
| cat "$TMP_DIR/VERSION"* |
| exit 0 |
| ;; |
| |
| -h | "-?" | --help) |
| echo " |
| USAGE: $SELF [bundle_option|--] [updater_options] |
| |
| bundle_option (only one option can be selected): |
| -h,--help: Show usage help |
| -V: show version and content of bundle |
| --force: force execution and ignore $TAG_FILE_DISABLED |
| --sb_extract [PATH]: extract bundle content to a temporary folder |
| --sb_repack PATH: update bundle content from given folder |
| |
| updater_options: |
| " |
| # Invoke script with -h for usage help |
| IS_IGNORE_RC=TRUE |
| exec_bundle_script "-h" |
| ;; |
| |
| --force) |
| # Pass this into updaters |
| IS_FORCED=TRUE |
| ;; |
| |
| --debug | --debug | -v) |
| # do not shift here because this needs to be passed into the script |
| IS_DEBUG=TRUE |
| ;; |
| |
| --) |
| shift |
| ;; |
| esac |
| |
| # Do nothing if the OS specifies that. |
| # TODO(hungte) move this flag to kernel command line, or updater bundle |
| # itself. |
| if [ -e "$TAG_FILE_DISABLED" ] && [ -z "$IS_FORCED" ]; then |
| echo "WARNING: $SELF is disabled by $TAG_FILE_DISABLED" |
| echo "To force execution, please prefix --force to your command:" |
| echo " sudo $SELF --force $original_params" |
| exit 0 |
| fi |
| |
| exec_bundle_script "$@" |
| } |
| |
| main "$@" |
| |
| # Below are for shar execution. Don't put any code below main. |
| ##CUTHERE################################################################## |