| #!/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 "$@" |