blob: fd17517601f6e1595396fb7632a5338e114b1fd3 [file] [log] [blame]
#!/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##################################################################