blob: df96b563679ce47afc6973163523fb28f1dabaaa [file] [log] [blame]
#!/bin/dash
#
# Copyright (c) 2012 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.
#
# Helper function to dump VPD RO/RW content into /var/vpd_2.0.txt.
#
# Used in:
# + OOBE reads this log file for the default locale setting.
# + chrome://system reads filtered file.
#
# Load shflags, define script flags.
. /usr/share/misc/shflags
DEFINE_boolean "clean" ${FLAGS_FALSE} \
"Clean VPD cache and output files, then quit."
DEFINE_boolean "force" ${FLAGS_FALSE} \
"Force regeneration of VPD cache and output files."
DEFINE_boolean "full" ${FLAGS_FALSE} \
"Generate full output, without filtering."
DEFINE_boolean "stdout" ${FLAGS_FALSE} \
"Dump VPD to standard output, instead of a file."
DEFINE_boolean "debug" ${FLAGS_FALSE} \
"Debug mode."
# Parse arguments.
FLAGS "$@"
eval set -- "${FLAGS_ARGV}"
set -e
#
# Make file access-able by root only.
# $1: name of file to change ownership/permissions to.
#
set_conservative_perm() {
chown root:root "$1"
chmod go-stwx "$1"
}
#
# Change file permissions so it is world readable.
# $1: name of file to change ownership/permissions to.
#
set_world_readable() {
set_conservative_perm "$1"
chmod ugo+r "$1"
}
#
# Set a directory as world enterable.
# $1: directory path to set world enterable.
#
set_world_enterable() {
local dir="$1"
set_conservative_perm "${dir}"
chmod ugo+x "${dir}"
}
# Generate a sed filter for VPD output depending on the provided flag.
#
# $1, $2, ..: Each parameter is one whitelisted key.
generate_sed_filter() {
local output=''
for field in $*; do
output="${output}"'/^"'"${field}"'"=".*"$/p;'
done
output="${output}"'/^.*/d;'
echo "${output}"
}
# Perform an atomic file move that is also safe on unclean shutdown. To
# accomplish this, the source file is synced to disk. This avoids the problem
# of the meta data for the rename being visible on disk while the data blocks
# have not or not entirely been flushed to disk due to a crash.
atomic_move() {
local source="$1"
local dest="$2"
dd if=/dev/null of="${source}" conv=notrunc,fdatasync
mv -f "${source}" "${dest}"
}
generate_cache_file() {
if [ -f "${CACHE_FILE}" ]; then
return
fi
# A temporary file used for caching the results of flashrom across subsequent
# invocations of the vpd utility within this script.
local bios_tmp="$(mktemp)"
add_temp_files "${bios_tmp}"
# The temporary file is under same folder as ${CACHE_FILE} to ensure that
# renaming (mv) is atomic.
local cache_tmp="$(mktemp --tmpdir="$(dirname "${CACHE_FILE}")" \
full-v2.txt.tmp.XXXXXXXXXX)"
add_temp_files "${cache_tmp}"
# If the file exists, but was not regular.
rm -f "${CACHE_FILE}"
args_partial="-p host -i FMAP -i RO_VPD -i RW_VPD -r ${bios_tmp}"
args_whole="-p host -r ${bios_tmp}"
if [ -n "${debug_log}" ]; then
echo "-------------------" "$(date)" >>"${debug_log}"
flashrom ${args_partial} -V -V -V >>"${debug_log}" 2>&1 ||
flashrom ${args_whole} -V -V -V >>"${debug_log}" 2>&1 ||
exit 1
else
# flashrom may print messages on stdout (for example, "Reading flash...
# SUCCESS") so we do want to prevent that for --stdout.
flashrom ${args_partial} 1>&2 ||
flashrom ${args_whole} 1>&2 ||
exit 1
fi
generate_full_text "${bios_tmp}" "RO_VPD" "${cache_tmp}"
echo '"___ro_rw_delimiter___"="___RW_VPD_below___"' >>"${cache_tmp}"
generate_full_text "${bios_tmp}" "RW_VPD" "${cache_tmp}"
atomic_move "${cache_tmp}" "${CACHE_FILE}"
# Remove existing filtered output file, forcing it to be regenerated.
rm -f "${FILTERED_FILE}"
}
# Generate the coupon code file containing cached VPD ECHO attributes.
generate_echo_codes() {
local echo_code_keys="
ubind_attribute
gbind_attribute"
if [ -f "${ECHO_COUPON_FILE}" ]; then
return
fi
local coupon_dir="$(dirname ${ECHO_COUPON_FILE})"
local link_dir="$(dirname ${ECHO_COUPON_LINK})"
mkdir -p "${coupon_dir}" "${link_dir}"
# The temporary file is under same folder as ${ECHO_COUPON_FILE} to ensure
# that renaming (mv) is atomic.
local tmpfile="$(mktemp --tmpdir="${coupon_dir}" vpd_echo.txt.tmp.XXXXXXXXXX)"
# If the file exists but isn't regular, it will be removed first.
rm -f "${ECHO_COUPON_FILE}"
rm -f "${ECHO_COUPON_LINK}"
sed -e "$(generate_sed_filter ${echo_code_keys})" "${CACHE_FILE}" \
>"${tmpfile}"
atomic_move "${tmpfile}" "${ECHO_COUPON_FILE}"
set_conservative_perm "${ECHO_COUPON_FILE}"
# Since chrome needs access to this, the file is readable by group chronos.
# Note: It should NOT be world readable.
# TODO(gauravsh): Broker this via debugd. http://crosbug.com/28285
chown -R root:chronos "${coupon_dir}" "${link_dir}"
chmod -R g+rx "${coupon_dir}" "${link_dir}"
chmod g-x "${ECHO_COUPON_FILE}"
}
# Generate the filtered file.
generate_filtered_file() {
local whitelist="
ActivateDate
block_devmode
check_enrollment
customization_id
initial_locale
initial_timezone
keyboard_layout
model_name
panel_backlight_max_nits
Product_S\/N
region
rlz_brand_code
rlz_embargo_end_date
serial_number
should_send_rlz_ping
sku_number"
if [ -f "${FILTERED_FILE}" ]; then
return
fi
# Files for temporary and final storage of filtered VPD data. Note that the
# temporary file is under same folder with ${FILTERED_FILE} to ensure that
# renaming (mv) is atomic.
local tmpfile="$(mktemp --tmpdir="$(dirname "${FILTERED_FILE}")" \
filtered.txt.tmp.XXXXXXXXXX)"
add_temp_files "${tmpfile}"
# If the file exists, but was not regular.
rm -f "${FILTERED_FILE}"
sed -e "$(generate_sed_filter ${whitelist})" "${CACHE_FILE}" >"${tmpfile}"
set_world_readable "${tmpfile}"
atomic_move "${tmpfile}" "${FILTERED_FILE}"
}
# Invoke the VPD utility for generating full VPD content.
#
# $1: BIOS filename
# $2: partition name
# $3: file name to append output
generate_full_text() {
(vpd -f "$1" -i "$2" -l || echo "# $2 execute error.") >>"$3"
}
# Migrate the legacy file under encrypted partition to be a symlink pointing to
# the target file under unencrypted partition.
#
# After call, the legacy file becomes symlink (either be pointed to target, or
# be moved to target).
migrate() {
local legacy="$1"
local target="$2"
if [ -L "${legacy}" ]; then
return
elif [ ! -e "${legacy}" ]; then
echo -n # create symlink at end of function.
elif [ -f "${legacy}" ]; then
if [ -e "${target}" ]; then
rm -f "${legacy}"
else
# To get an atomic move, the legacy file is first copied to a temporary
# file on the destination file system which is then moved into place.
mkdir -p "$(dirname ${target})"
local target_tmp=$(mktemp --tmpdir="$(dirname ${target})" \
"$(basename ${target}).tmp.XXXXXXXXXX")
mv -f "${legacy}" "${target_tmp}"
atomic_move "${target_tmp}" "${target}"
fi
else
echo "# The type of legacy file ${legacy} cannot be migrated."
exit 1
fi
mkdir -p "$(dirname ${legacy})"
ln -sf "${target}" "${legacy}"
}
# Add a file to temporary file list to be deleted on exit (cleanup).
add_temp_files() {
TEMP_FILES="${TEMP_FILES} $*"
}
# Removes all temporary files.
cleanup() {
if [ -n "${TEMP_FILES}" ]; then
# TEMP_FILES is a list so we don't quote it.
rm -rf ${TEMP_FILES}
TEMP_FILES=""
fi
}
#
# main()
#
main() {
umask 0077 # Conservative strategy. Allow root-only first. Open later.
# A list of temporary files to delete.
TEMP_FILES=""
# A flag to indicate if we are caching VPD data in /tmp. This is used for
# scripts calling dump_vpd_log very early before stateful partition was
# mounted (so we can't really cache data on disk).
IS_CACHE_IN_TMP="${FLAGS_FALSE}"
# Remove the temp files.
trap cleanup EXIT
# The unencrypted directory to store VPD cache files. VPD may run when
# encrypted partition is unavailable. Also, accessing unencrypted partition is
# faster.
CACHE_DIR="/mnt/stateful_partition/unencrypted/cache/vpd"
# mktemp depends on existence of ${CACHE_DIR}.
if ! mkdir -p "${CACHE_DIR}"; then
if [ "${FLAGS_stdout}" -eq "${FLAGS_TRUE}" ]; then
echo "Warning: ${CACHE_DIR} not available, VPD won't be cached." >&2
CACHE_DIR="$(mktemp -d)"
IS_CACHE_IN_TMP="${FLAGS_TRUE}"
add_temp_files "${CACHE_DIR}"
else
echo "ERROR: ${CACHE_DIR} not available."
exit 1
fi
fi
set_world_enterable "${CACHE_DIR}"
# Files for final cache of full VPD data.
CACHE_FILE="${CACHE_DIR}/full-v2.txt"
CACHE_LINK="/var/cache/vpd/full-v2.txt"
# Location for storing cached ECHO coupon codes.
ECHO_COUPON_FILE="${CACHE_DIR}/echo/vpd_echo.txt"
ECHO_COUPON_LINK="/var/cache/echo/vpd_echo.txt"
# A space delimited list of old VPD cache files, which will be removed as a
# cleanup measure. Please be sure to update this list as the cache filename
# changes between versions of this script!
OLD_CACHE_FILES="/var/cache/vpd/full.cache /var/cache/offers/vpd_echo.txt \
/var/cache/vpd/full-v2.cache"
# Location for storing filtered VPD data.
FILTERED_FILE="${CACHE_DIR}/filtered.txt"
FILTERED_LINK="/var/log/vpd_2.0.txt"
if [ "${FLAGS_debug}" -eq ${FLAGS_TRUE} ]; then
debug_log="/tmp/dump_vpd_log.debug"
fi
# Cleanup: remove old versions of the VPD cache file; this ensures that we
# don't have unused VPD data lying around, which takes unnecessary space,
# might lead to stale VPD log extraction (in case of a script version
# rollback), and is perceived as a potential security breach.
for f in ${OLD_CACHE_FILES}; do
rm -f "${f}"
done
# Remove output files if --clean or --force flagged.
if [ "${FLAGS_clean}" -eq ${FLAGS_TRUE} -o \
"${FLAGS_force}" -eq ${FLAGS_TRUE} ]; then
rm -f "${FILTERED_FILE}" "${CACHE_FILE}" "${ECHO_COUPON_FILE}" \
"${FILTERED_LINK}" "${CACHE_LINK}"
# If --clean was flagged, we're done.
if [ "${FLAGS_clean}" -eq ${FLAGS_TRUE} ]; then
exit 0
fi
fi
if [ "${FLAGS_stdout}" -eq ${FLAGS_FALSE} ] &&
[ "${FLAGS_full}" -eq ${FLAGS_TRUE} ]; then
# --full must only be used with --stdout, to prevent accidental dumping of
# sensitive VPD info into a world-readable file. To be used as follows:
#
# dump_vpd_log --full --stdout >a_root_readable_file
#
echo "You specified --full without --stdout, aborting."
exit 1
fi
# Generate missing files.
generate_cache_file
generate_filtered_file
# Print to stdout if needed.
if [ "${FLAGS_stdout}" -eq ${FLAGS_TRUE} ]; then
if [ "${FLAGS_full}" -eq ${FLAGS_TRUE} ]; then
cat "${CACHE_FILE}"
else
cat "${FILTERED_FILE}"
fi
fi
if [ "${IS_CACHE_IN_TMP}" -eq "${FLAGS_TRUE}" ]; then
exit 0
fi
# generate_echo_codes need to setup link folder with different permissions so
# it has to be invoked only if we are not caching in /tmp.
generate_echo_codes
# Create symlinks if needed.
migrate "${FILTERED_LINK}" "${FILTERED_FILE}"
migrate "${CACHE_LINK}" "${CACHE_FILE}"
migrate "${ECHO_COUPON_LINK}" "${ECHO_COUPON_FILE}"
}
main "$@"