| #!/bin/sh -u |
| # 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. |
| # |
| # A script to install from removable media to hard disk. |
| |
| # If we're not running as root, restart as root. |
| if [ "${UID:-$(id -u)}" -ne 0 ]; then |
| exec sudo "$0" "$@" |
| fi |
| |
| # Load functions and constants for chromeos-install. |
| . /usr/share/misc/chromeos-common.sh || exit 1 |
| . /usr/share/misc/shflags || exit 1 |
| . /usr/share/misc/lvm-utils.sh || exit 1 |
| |
| # Source blocksize |
| SRC_BLKSIZE=512 |
| |
| # Copy the rootfs in chunks to optimize disk cache usage. |
| NUM_ROOTFS_CHUNKS=4 |
| |
| # Helpful constants. |
| HARDWARE_DIAGNOSTICS_PATH=/tmp/hardware_diagnostics.log |
| TMPMNT=/tmp/install-mount-point |
| # This is defined later once we have mounted the ROOT. |
| STATEFUL_FORMAT= |
| # Partition numbers that have assumptions about them. This list should be kept |
| # to a minimal. Check copy_partition for most special casing. |
| |
| # TODO(installer): Clean up all these flags. There are way too many flags in |
| # this script. |
| |
| DEFINE_string dst "" "Destination device" |
| DEFINE_boolean skip_src_removable ${FLAGS_FALSE} \ |
| "Skip check to ensure source is removable" |
| DEFINE_boolean skip_dst_removable ${FLAGS_FALSE} \ |
| "Skip check to ensure destination is not removable" |
| DEFINE_boolean skip_rootfs ${FLAGS_FALSE} \ |
| "Skip installing the rootfs; Only set up partition table" |
| DEFINE_boolean yes ${FLAGS_FALSE} \ |
| "Answer yes to everything" |
| DEFINE_boolean preserve_stateful ${FLAGS_FALSE} \ |
| "Don't create a new filesystem for the stateful partition. Be careful \ |
| using this option as this may make the stateful partition not mountable." |
| DEFINE_string payload_image "" "Path to a Chromium OS image to install onto \ |
| the device's hard drive" |
| DEFINE_string pmbr_code "" "Path to PMBR code to be installed" |
| DEFINE_string target_bios "" "Bios type to boot with (see postinst --bios)" |
| DEFINE_boolean mtd_layout ${FLAGS_FALSE} "This system uses MTD for \ |
| partitioning rather than GPT" |
| DEFINE_boolean debug ${FLAGS_FALSE} "Show debug output" |
| DEFINE_boolean large_test_partitions ${FLAGS_FALSE} \ |
| "Make partitions 9 and 10 large (for filesystem testing)" |
| DEFINE_boolean skip_postinstall ${FLAGS_FALSE} \ |
| "Skip postinstall for situations where you're building for a \ |
| non-native arch. Note that this will probably break verity." |
| DEFINE_string lab_preserve_logs "" "Path to a file containing logs to be \ |
| preserved" |
| DEFINE_boolean storage_diags "${FLAGS_FALSE}" "Print storage diagnostic \ |
| information on failure" |
| DEFINE_string oobe_pub_key "" "Path to public key for OOBE auto-configuration \ |
| validation" |
| DEFINE_string oobe_priv_key "" "Path to private key for OOBE \ |
| auto-configuration signing" |
| DEFINE_boolean lvm_stateful "${FLAGS_FALSE}" "Create LVM-based stateful \ |
| partition" |
| |
| |
| # Parse command line |
| FLAGS "$@" || exit 1 |
| eval set -- "${FLAGS_ARGV}" |
| |
| die() { |
| echo "$*" >&2 |
| exit 1 |
| } |
| |
| fast_dd() { |
| # Usage: fast_dd <count> <seek> <skip> other dd args |
| # Note: <count> and <seek> are in units of SRC_BLKSIZE, while <skip> is in |
| # units of DST_BLKSIZE. |
| local user_count="$1" |
| local user_seek="$2" |
| local user_skip="$3" |
| local chunk_num="$4" |
| local total_chunks="$5" |
| shift 5 |
| # Provide some simple progress updates to the user. |
| set -- "$@" status=progress |
| # Find the largest block size that all the parameters are a factor of. |
| local block_size=$((2 * 1024 * 1024)) |
| while [ $(((user_count * SRC_BLKSIZE) % block_size)) -ne 0 ] || \ |
| [ $(((user_skip * SRC_BLKSIZE) % block_size)) -ne 0 ] || \ |
| [ $(((user_seek * DST_BLKSIZE) % block_size)) -ne 0 ]; do |
| |
| : $((block_size /= 2)) |
| done |
| |
| # Print a humble info line if the block size is not super, and complain more |
| # loudly if it's really small (and the partition is big). |
| if [ "${block_size}" -ne $((2 * 1024 * 1024)) ]; then |
| echo "DD with block size ${block_size}" |
| if [ "${block_size}" -lt $((128 * 1024)) ] && \ |
| [ $((user_count * SRC_BLKSIZE)) -gt $((128 * 1024 * 1024)) ]; then |
| echo |
| echo "WARNING: DOING A SLOW MISALIGNED dd OPERATION. PLEASE FIX" |
| echo "count=${user_count} seek=${user_seek} skip=${user_skip}" |
| echo "SRC_BLKSIZE=${SRC_BLKSIZE} DST_BLKSIZE=${DST_BLKSIZE}" |
| echo |
| fi |
| fi |
| |
| # Convert the block counts in their respective sizes into the common block |
| # size, and blast off. |
| local count_common=$((user_count * SRC_BLKSIZE / block_size)) |
| local seek_common=$((user_seek * DST_BLKSIZE / block_size)) |
| local skip_common=$((user_skip * SRC_BLKSIZE / block_size)) |
| |
| if [ "${total_chunks}" -ne 1 ]; then |
| # Divide the count by the number of chunks, rounding up. This is the |
| # chunk size. |
| local chunk_size=$(((count_common + total_chunks - 1) / total_chunks)) |
| |
| : $(( seek_common += chunk_size * (chunk_num - 1) )) |
| : $(( skip_common += chunk_size * (chunk_num - 1) )) |
| |
| if [ "${chunk_num}" -ne "${total_chunks}" ]; then |
| count_common="${chunk_size}" |
| else |
| : $(( count_common -= chunk_size * (chunk_num - 1) )) |
| fi |
| fi |
| |
| dd "$@" bs="${block_size}" seek="${seek_common}" skip="${skip_common}" \ |
| "count=${count_common}" |
| } |
| |
| # Get the specified env var for the specified partition. |
| # $1 the field name such as "PARTITION_SIZE", "FS_FORMAT" |
| # $2 the partition such as "1", or "ROOT_A" |
| _get_field() { |
| local field part |
| field="$1" |
| part="$2" |
| eval echo \""\${${field}_${part}}"\" |
| } |
| |
| get_format() { |
| _get_field FORMAT "$@" |
| } |
| |
| get_fs_format() { |
| _get_field FS_FORMAT "$@" |
| } |
| |
| get_partition_size() { |
| _get_field PARTITION_SIZE "$@" |
| } |
| |
| get_reserved_ebs() { |
| _get_field RESERVED_EBS "$@" |
| } |
| |
| # Calculate the maximum number of bad blocks per 1024 blocks for UBI. |
| # $1 partition number |
| calculate_max_beb_per_1024() { |
| local part_no mtd_size eb_size nr_blocks |
| part_no="$1" |
| # The max beb per 1024 is on the total device size, not the partition size. |
| mtd_size=$(cat /sys/class/mtd/mtd0/size) |
| eb_size=$(cat /sys/class/mtd/mtd0/erasesize) |
| nr_blocks=$((mtd_size / eb_size)) |
| reserved_ebs=$(get_reserved_ebs "${part_no}") |
| echo $((reserved_ebs * 1024 / nr_blocks)) |
| } |
| |
| # Format and make UBI volume if it's not already there. |
| # $1 partition number such as "1", "2" |
| # $2 volume name |
| init_ubi_volume() { |
| local part_no volume_name phy_ubi log_ubi |
| part_no="$1" |
| volume_name="$2" |
| phy_ubi="/dev/ubi${part_no}" |
| log_ubi="${phy_ubi}_0" |
| if [ ! -e "${phy_ubi}" ]; then |
| ubiformat -y -e 0 "/dev/mtd${part_no}" |
| ubiattach -d "${part_no}" -m "${part_no}" \ |
| --max-beb-per1024 "$(calculate_max_beb_per_1024 "${part_no}")" |
| fi |
| if [ ! -e "${log_ubi}" ]; then |
| local volume_size |
| volume_size=$(get_partition_size "${part_no}") |
| ubimkvol -s "${volume_size}" -N "${volume_name}" "${phy_ubi}" |
| fi |
| } |
| |
| # Update a specific partition in the destination device. |
| write_partition() { |
| local user_part="$1" |
| local user_count="$2" |
| local user_seek="$3" |
| local user_skip="$4" |
| local src="$5" |
| local dst="$6" |
| local chunk_num="$7" |
| local total_chunks="$8" |
| local cache_input="$9" |
| local format fs_format |
| |
| if [ "${user_count}" -eq 0 ]; then |
| echo "Skipping partition as it does not exist" |
| return 0 |
| fi |
| |
| format="$(get_format "${user_part}")" |
| case ${format} in |
| nand) |
| if [ "${chunk_num}" -ne 1 ]; then |
| return |
| elif [ "${total_chunks}" -ne 1 ]; then |
| echo "Chunking not supported for NAND; will install all in one go" |
| fi |
| |
| flash_erase "/dev/mtd${user_part}" 0 0 |
| nandwrite --input-skip $((user_skip * SRC_BLKSIZE)) \ |
| --input-size $((user_count * SRC_BLKSIZE)) \ |
| "/dev/mtd${user_part}" "${src}" |
| ;; |
| |
| ubi) |
| if [ "${chunk_num}" -ne 1 ]; then |
| return |
| elif [ "${total_chunks}" -ne 1 ]; then |
| echo "Chunking not supported for UBI; will install all in one go" |
| fi |
| |
| local phy_ubi="/dev/ubi${user_part}" |
| local log_ubi="${phy_ubi}_0" |
| local sysfs_name="/sys/class/mtd/mtd${user_part}/name" |
| |
| init_ubi_volume "${user_part}" "$(cat "${sysfs_name}")" |
| |
| fs_format="$(get_fs_format "${user_part}")" |
| case ${fs_format} in |
| ubifs) |
| local src_mnt="${TMPMNT}/src" dst_mnt="${TMPMNT}/dst" |
| mkdir -p "${src_mnt}" "${dst_mnt}" |
| |
| mkfs.ubifs -y -x none -R 0 "${log_ubi}" |
| mount "${log_ubi}" "${dst_mnt}" |
| |
| # Have to copy the files over by hand as the source partition is a |
| # different filesystem type (like ext4). |
| loop_offset_setup "${src}" "${user_seek}" "${SRC_BLKSIZE}" |
| TMPMNT="${src_mnt}" mount_on_loop_dev |
| cp -a "${src_mnt}"/* "${dst_mnt}"/ |
| TMPMNT="${src_mnt}" umount_from_loop_dev |
| loop_offset_cleanup |
| |
| umount "${log_ubi}" |
| ;; |
| *) |
| ubiupdatevol --skip $((user_skip * SRC_BLKSIZE)) \ |
| --size $((user_count * SRC_BLKSIZE)) "${log_ubi}" "${src}" |
| ;; |
| esac |
| ;; |
| |
| *) |
| # The "dd" command can be used to advise Linux to drop caches and we use |
| # this feature to maximize available cache for future transfers. We deal |
| # with the input and output block devices differently. Here's why: |
| # - For the input block device there are some cases where we know we'll |
| # want the input to stay in cache (because we're going to use it again) |
| # and other cases where we know we're done with it. Also note that we |
| # are likely running from the input block device, so it's good not to |
| # be too liberal about dropping the cache. |
| # - For the output block we never need the cache kept. We won't be reading |
| # it again. We can just tell Linux to drop the cache for the whole |
| # device each time. It should also be noted that it appears most |
| # efficient not to drop the cache on the output as part of the same "dd" |
| # doing the transfer (presumably it makes write buffers less efficient). |
| local dd_cache_arg=""; |
| if ! "${cache_input}"; then |
| dd_cache_arg="iflag=nocache" |
| fi |
| |
| fast_dd "${user_count}" "${user_seek}" "${user_skip}" \ |
| "${chunk_num}" "${total_chunks}" if="${src}" of="${dst}" conv=notrunc \ |
| ${dd_cache_arg} |
| |
| # Incantation to advise the kernel to drop cache on the whole dst. |
| dd if="${dst}" iflag=nocache count=0 status=none |
| ;; |
| esac |
| } |
| |
| # Find root partition of the block device that we are installing from |
| get_root_device() { |
| rootdev -s |
| } |
| |
| # Check for optional payload image |
| check_payload_image() { |
| if [ "${FLAGS_skip_rootfs}" -eq "${FLAGS_TRUE}" ]; then |
| # Usually this is used for partition setup. |
| SRC="" |
| ROOT="" |
| elif [ -z "${FLAGS_payload_image}" ]; then |
| # Find root partition of the root block device |
| SRC=$(get_block_dev_from_partition_dev "$(get_root_device)") |
| ROOT="" |
| else |
| if [ ! -e "${FLAGS_payload_image}" ]; then |
| die "Error: No payload image found at ${FLAGS_payload_image}" |
| fi |
| |
| # Needed to copy PMBR code off image |
| SRC="${FLAGS_payload_image}" |
| ROOT="$(mktemp -d)" |
| fi |
| } |
| |
| # Clean any mounts that might be present to avoid |
| # aliasing access to block devices. |
| prepare_disk() { |
| if [ -e /etc/init/cros-disks.conf ]; then |
| initctl stop cros-disks || true |
| fi |
| # Often times, nothing is mounted, so swallow the warnings. |
| umount -f /media/*/* 2>&1 | \ |
| grep -v -i -F \ |
| -e 'no mount point specified' \ |
| -e 'not mounted' \ |
| -e 'No such file or directory' \ |
| -e 'not found' || true |
| } |
| |
| # Like mount but keeps track of the current mounts so that they can be cleaned |
| # up automatically. |
| tracked_mount() { |
| local last_arg |
| eval last_arg=\$$# |
| MOUNTS="${last_arg}${MOUNTS:+ }${MOUNTS:-}" |
| mount "$@" |
| } |
| |
| # Unmount with tracking. |
| tracked_umount() { |
| # dash does not support ${//} expansions. |
| local new_mounts |
| for mount in $MOUNTS; do |
| if [ "$mount" != "$1" ]; then |
| new_mounts="${new_mounts:-}${new_mounts+ }$mount" |
| fi |
| done |
| MOUNTS=${new_mounts:-} |
| |
| umount "$1" |
| } |
| |
| # Create a loop device on the given file at a specified (sector) offset. |
| # Remember the loop device using the global variable LOOP_DEV. |
| # Invoke as: command |
| # Args: FILE OFFSET BLKSIZE |
| loop_offset_setup() { |
| local filename=$1 |
| local offset=$2 |
| local blocksize=$3 |
| |
| set -- |
| if [ "${blocksize}" -ne 512 ]; then |
| set -- "$@" -b "${blocksize}" |
| fi |
| |
| LOOP_DEV=$(losetup -f "$@" --show -o $((offset * blocksize)) "${filename}") |
| if [ -z "$LOOP_DEV" ]; then |
| die "No free loop device. Free up a loop device or reboot. Exiting." |
| fi |
| |
| LOOPS="${LOOP_DEV}${LOOPS:+ }${LOOPS:-}" |
| } |
| |
| # Delete the current loop device. |
| loop_offset_cleanup() { |
| # dash does not support ${//} expansions. |
| local new_loops |
| for loop in $LOOPS; do |
| if [ "$loop" != "$LOOP_DEV" ]; then |
| new_loops="${new_loops:-}${new_loops+ }$loop" |
| fi |
| done |
| LOOPS=${new_loops:-} |
| |
| # losetup -a doesn't always show every active device, so we'll always try to |
| # delete what we think is the active one without checking first. Report |
| # success no matter what. |
| losetup -d "${LOOP_DEV}" || /bin/true |
| } |
| |
| # Mount the existing loop device at the mountpoint in $TMPMNT. |
| # Args: optional 'readwrite'. If present, mount read-write, otherwise read-only. |
| mount_on_loop_dev() { |
| local rw_flag=${1-readonly} |
| set -- |
| if [ "${rw_flag}" != "readwrite" ]; then |
| set -- "$@" -o ro |
| fi |
| tracked_mount "$@" "${LOOP_DEV}" "${TMPMNT}" |
| } |
| |
| # Unmount loop-mounted device. |
| umount_from_loop_dev() { |
| mount | grep -q " on ${TMPMNT} " && tracked_umount "${TMPMNT}" |
| } |
| |
| # Check if all arguments are non-empty values |
| check_non_empty_values() { |
| local value |
| for value in "$@"; do |
| if [ -z "$value" ]; then |
| return ${FLAGS_FALSE} |
| fi |
| done |
| return ${FLAGS_TRUE} |
| } |
| |
| # Undo all mounts and loops and runs hw diagnostics on failure. |
| cleanup_on_failure() { |
| set +e |
| |
| if [ "${FLAGS_storage_diags}" -eq "${FLAGS_TRUE}" ]; then |
| if [ -b "${DST-}" ]; then |
| # Generate the diagnostics log that can be used by a caller. |
| echo "Running a hw diagnostics test -- this might take a couple minutes." |
| badblocks -e 100 -sv "${DST}" 2>&1 | tee "${HARDWARE_DIAGNOSTICS_PATH}" |
| fi |
| |
| if [ -f /usr/share/misc/storage-info-common.sh ]; then |
| . /usr/share/misc/storage-info-common.sh |
| # Run a few extra diagnostics with output to stdout. These will |
| # be stored as part of the recovery.log for recovery images. |
| get_storage_info |
| fi |
| fi |
| |
| cleanup |
| } |
| |
| # Undo all mounts and loops. |
| cleanup() { |
| set +e |
| |
| local mount_point |
| for mount_point in ${MOUNTS:-}; do |
| umount "${mount_point}" || /bin/true |
| done |
| MOUNTS="" |
| |
| local loop_dev |
| for loop_dev in ${LOOPS:-}; do |
| losetup -d "${loop_dev}" || /bin/true |
| done |
| LOOPS="" |
| |
| if [ -n "${ROOT}" ]; then |
| rmdir "${ROOT}" |
| fi |
| } |
| |
| check_removable() { |
| if [ "${FLAGS_skip_dst_removable}" -eq "${FLAGS_TRUE}" ]; then |
| return |
| fi |
| |
| local removable |
| |
| if ! removable="$(cat "/sys/block/${DST#/dev/}/removable")"; then |
| die "Error: Invalid destination device (must be whole device): ${DST}" |
| fi |
| |
| if [ "${removable}" != "0" ]; then |
| die "Error: Attempt to install to a removeable device: ${DST}" |
| fi |
| } |
| |
| mkfs() { |
| local fs_format="$1" |
| local fs_sector_count="$2" |
| local device="$3" |
| local label="$4" |
| # We re-use $@ for our custom options below, so reset it. |
| set -- |
| |
| # Check if the kernel we are going to install support ext4 crypto. |
| if ext4_dir_encryption_supported; then |
| set -- "$@" -O encrypt |
| fi |
| |
| # Check if the kernel we are going to install support ext4 fs-verity. |
| if ext4_fsverity_supported; then |
| set -- "$@" -O verity |
| fi |
| |
| # Calculate the number of 4k sectors for the ext4 partition. |
| local num_4k_sectors |
| local sector_size=4096 |
| if [ "${DST_BLKSIZE}" -gt "${sector_size}" ]; then |
| num_4k_sectors=$(( fs_sector_count * (DST_BLKSIZE / sector_size) )) |
| else |
| num_4k_sectors=$(( fs_sector_count / (sector_size / DST_BLKSIZE) )) |
| fi |
| |
| # We always make any ext* stateful partitions ext4. |
| case "${fs_format}" in |
| ext[234]) |
| mkfs.ext4 -F -b ${sector_size} -L "${label}" "$@" "${device}" \ |
| ${num_4k_sectors} |
| ;; |
| ubifs) |
| mkfs.ubifs -y -x none -R 0 "/dev/ubi${PARTITION_NUM_STATE}_0" |
| ;; |
| esac |
| |
| } |
| |
| # Wipes and expands the stateful partition. |
| wipe_stateful() { |
| echo "Clearing the stateful partition..." |
| local stateful_fs_format |
| local vg_name |
| stateful_fs_format="$(get_fs_format "${PARTITION_NUM_STATE}")" |
| # state options are stored in $@. |
| set -- |
| |
| case "${STATEFUL_FORMAT}" in |
| ubi) |
| local phy_ubi="/dev/ubi${PARTITION_NUM_STATE}" |
| local log_ubi="${phy_ubi}_0" |
| local sysfs_name="/sys/class/mtd/mtd${PARTITION_NUM_STATE}/name" |
| |
| init_ubi_volume "${PARTITION_NUM_STATE}" "$(cat "${sysfs_name}")" |
| ;; |
| *) |
| if [ -b "${DST}" ]; then |
| DEV=$(make_partition_dev "${DST}" "${PARTITION_NUM_STATE}") |
| else |
| loop_offset_setup "${DST}" "${START_STATEFUL}" "${DST_BLKSIZE}" |
| DEV="${LOOP_DEV}" |
| fi |
| ;; |
| esac |
| |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| # Now we recreate the logical volume set up. |
| # Create physical volume on the partition. |
| echo "Creating physical volumes" |
| pvcreate -ff --yes "${DEV}" |
| |
| vg_name="$(generate_random_vg_name)" |
| if [ -z "${vg_name}" ]; then |
| die "Failed to generate valid volume group name" |
| fi |
| |
| echo "Creating volume group" |
| vgcreate -p 1 "${vg_name}" "${DEV}" |
| |
| vgchange -ay "${vg_name}" |
| |
| echo "Creating thinpool" |
| local thinpool_size="$(get_thinpool_size "${DEV}")" |
| local thinpool_metadata_size="$(get_thinpool_metadata_size \ |
| "${thinpool_size}")" |
| |
| lvcreate --size "${thinpool_size}M" --poolmetadatasize \ |
| "${thinpool_metadata_size}M" --thinpool "thinpool" "${vg_name}/thinpool" |
| |
| echo "Creating unencrypted volume" |
| local lv_size="$(get_logical_volume_size "${DEV}")" |
| lvcreate --thin -V "${lv_size}M" -n "unencrypted" "${vg_name}/thinpool" |
| |
| NUM_STATEFUL_SECTORS=$(( lv_size * 2 * 1024 )) |
| DEV="/dev/${vg_name}/unencrypted" |
| fi |
| |
| mkfs "${stateful_fs_format}" ${NUM_STATEFUL_SECTORS} "${DEV}" "H-STATE" |
| |
| case ${STATEFUL_FORMAT} in |
| ubi) ;; |
| *) |
| # Need to synchronize before releasing loop device, otherwise calling |
| # loop_offset_cleanup may return "device busy" error. |
| sync |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| vgchange --force -an "${vg_name}" |
| elif [ ! -b "${DST}" ]; then |
| loop_offset_cleanup |
| fi |
| ;; |
| esac |
| |
| # When the stateful partition is wiped the TPM ownership must be reset. This |
| # command will not work on older devices which do not support it. In that case |
| # it will be ignored. |
| crossystem clear_tpm_owner_request=1 || true |
| } |
| |
| # Install the stateful partition content |
| # Method handles copying data over to the stateful partition. This is done |
| # differently than other partitions due to the EXPAND option i.e. src partition |
| # and dst partitions are of different sizes. In addition, there are some special |
| # tweaks we do for stateful here for various workflows. |
| install_stateful() { |
| # In general, the system isn't allowed to depend on anything |
| # being in the stateful partition at startup. We make some |
| # exceptions for dev images (only), as enumerated below: |
| # |
| # var_overlay |
| # These are included to support gmerge, and must be kept in |
| # sync with those listed in /etc/init/var-overlay.conf: |
| # db/pkg |
| # lib/portage |
| # These are included to support dlcservice preloading for testing from usb |
| # flash. |
| # cache/dlc-images |
| # |
| # dev_image |
| # This provides tools specifically chosen to be mounted at |
| # /usr/local as development only tools. |
| # |
| # Every exception added makes the dev image different from |
| # the release image, which could mask bugs. Make sure every |
| # item you add here is well justified. |
| local dst_stateful_partition |
| local vg_name |
| |
| echo "Installing the stateful partition..." |
| case "${STATEFUL_FORMAT}" in |
| ubi) |
| # We modify the global used here as it affects how we unmount later. |
| LOOP_DEV="/dev/ubi${PARTITION_NUM_STATE}_0" |
| ;; |
| *) |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| dst_stateful_partition="$(make_partition_dev ${DST} \ |
| ${PARTITION_NUM_STATE})" |
| vg_name="$(get_volume_group ${dst_stateful_partition})" |
| vgchange -ay "${vg_name}" |
| LOOP_DEV="/dev/${vg_name}/unencrypted" |
| else |
| loop_offset_setup "${DST}" "${START_STATEFUL}" "${DST_BLKSIZE}" |
| fi |
| ;; |
| esac |
| mount_on_loop_dev readwrite |
| |
| # Move log files listed in FLAGS_lab_preserve_logs from stateful_partition to |
| # a dedicated location. This flag is used to enable Autotest to collect log |
| # files before reimage deleting all prior logs. |
| if crossystem 'cros_debug?1' && [ -n "${FLAGS_lab_preserve_logs}" ]; then |
| local gatherme="${TMPMNT}/.gatherme" |
| touch "${gatherme}" |
| local prior_log_dir="${TMPMNT}/unencrypted/prior_logs" |
| mkdir -p "${prior_log_dir}" |
| local log_path |
| for log_path in $(sed -e '/^#/ d' -e '/^$/ d' "${FLAGS_lab_preserve_logs}"); do |
| case "${log_path}" in |
| /dev/* | /sys/*) |
| ;; |
| /*) |
| echo "${log_path}" >> "${gatherme}" |
| continue |
| ;; |
| *) |
| log_path="${TMPMNT}/${log_path}" |
| ;; |
| esac |
| if [ -d "${log_path}" ]; then |
| cp -au -r --parents "${log_path}" "${prior_log_dir}" || true |
| elif [ -f "${log_path}" ]; then |
| cp -au "${log_path}" "${prior_log_dir}" || true |
| fi |
| done |
| fi |
| |
| # Whitelist files to copy onto the stateful partition. |
| # |
| # When adding to the whitelist, consider the need for related changes in |
| # src/platform/init/chromeos_startup, and in src/platform/dev/stateful_update. |
| # |
| local dirlist=" |
| unencrypted/cros-components/offline-demo-mode-resources |
| unencrypted/import_extensions |
| " |
| |
| if crossystem 'cros_debug?1'; then |
| # For a debug install, add a separate partition for developer-tools. |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| local lv_size="$(get_logical_volume_size "${dst_stateful_partition}")" |
| local stateful_fs_format="$(get_fs_format "${PARTITION_NUM_STATE}")" |
| |
| lvcreate --thin -V "${lv_size}M" -n "dev-image" "${vg_name}/thinpool" |
| lvchange -ay "${vg_name}/dev-image" |
| |
| mkfs "${stateful_fs_format}" ${NUM_STATEFUL_SECTORS} \ |
| "/dev/${vg_name}/dev-image" "DEVTOOLS" |
| mkdir -p "${TMPMNT}/dev_image" |
| mount "/dev/${vg_name}/dev-image" "${TMPMNT}/dev_image" |
| fi |
| |
| dirlist=" |
| ${dirlist} |
| var_overlay/db/pkg |
| var_overlay/lib/portage |
| dev_image |
| " |
| |
| local rootfs_dlc="${ROOT}/opt/google/dlc" |
| if [ -d "${rootfs_dlc}" ]; then |
| for f in $(find "${rootfs_dlc}" -name "*.json"); do |
| local f_path=$(dirname "${f}") |
| local dlc_package=$(basename "${f_path}") |
| local dlc_id=$(basename $(dirname "${f_path}")) |
| if grep '\"preload-allowed\":\s*true' "${f}"; then |
| dirlist=" |
| ${dirlist} |
| var_overlay/cache/dlc-images/${dlc_id}/${dlc_package} |
| " |
| fi |
| done |
| fi |
| fi |
| |
| if crossystem 'devsw_boot?1' ; then |
| # This is a base build, and the dev switch was on when we booted; |
| # we assume it will be on for the next boot. We touch |
| # ".developer_mode" to avoid a pointless delay after reboot while |
| # chromeos_startup wipes an empty stateful partition. |
| # |
| # See chromeos_startup for the companion code that checks for this |
| # file. |
| # |
| touch ${TMPMNT}/.developer_mode |
| fi |
| |
| if [ -n "${IS_RECOVERY_INSTALL-}" ] ; then |
| # This is a recovery install; write some recovery metrics to the stateful |
| # partition to be reported after next boot. See: |
| # init/upstart/send-recovery-metrics.conf |
| local recovery_histograms="${TMPMNT}/.recovery_histograms" |
| metrics_client -W "${recovery_histograms}" -e "Installer.Recovery.Reason" \ |
| "$(crossystem recovery_reason)" 255 |
| fi |
| |
| local dir |
| for dir in ${dirlist}; do |
| if [ ! -d "${ROOT}/mnt/stateful_partition/${dir}" ]; then |
| continue |
| fi |
| local parent |
| parent=$(dirname "${dir}") |
| mkdir -p "${TMPMNT}/${parent}" |
| # The target directory may already exist (eg. dev_image mounted from a |
| # separate volume), use the parent directory as destination for cp. |
| cp -au "${ROOT}/mnt/stateful_partition/${dir}" "${TMPMNT}/${parent}" |
| done |
| |
| if [ -n "${FLAGS_oobe_pub_key}" ] && [ -n "${FLAGS_oobe_priv_key}" ]; then |
| echo "Finalizing OOBE auto-config setup..." |
| |
| # Start udevd since it might not be running, and finish_oobe_auto_config |
| # needs it for walking /dev/disk/by-id/. |
| udevd --daemon |
| udevadm trigger |
| udevadm settle |
| |
| local stateful_device |
| stateful_device="$(cgpt find -l STATE "${FLAGS_payload_image}")" |
| |
| finish_oobe_auto_config \ |
| --private_key="${FLAGS_oobe_priv_key}" \ |
| --public_key="${FLAGS_oobe_pub_key}" \ |
| --src_stateful_dev="${stateful_device}" \ |
| --src_stateful="${ROOT}/mnt/stateful_partition" \ |
| --dst_stateful="${TMPMNT}" |
| fi |
| |
| if mountpoint -q "${TMPMNT}/dev_image"; then |
| umount "${TMPMNT}/dev_image" |
| fi |
| |
| umount_from_loop_dev |
| case ${STATEFUL_FORMAT} in |
| ubi) ;; |
| *) |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| vgchange -an "${vg_name}" |
| else |
| loop_offset_cleanup |
| fi |
| ;; |
| esac |
| } |
| |
| # Copy partition from src to dst (figures out partition offsets). Note, this |
| # has some special casing for rootfs, kernel, and stateful partitions. In |
| # addition, it only copies partitions that are equally sized over one another. |
| # $1 - Partition number we are copying to. |
| # $2 - src image |
| # $3 - dst image. |
| # $4 - chunk_num |
| # $5 - total_chunks |
| # $6 - cache_input |
| copy_partition() { |
| local part_num=$1 |
| local src=$2 |
| local dst=$3 |
| local chunk_num=$4 |
| local total_chunks=$5 |
| local cache_input=$6 |
| local part_size |
| local src_offset |
| local dst_offset |
| |
| part_size="$(partsize "${src}" "${part_num}")" |
| src_offset="$(partoffset "${src}" "${part_num}")" |
| dst_offset="$(partoffset "${dst}" "${part_num}")" |
| |
| local chunk_status="" |
| if [ "${total_chunks}" -ne 1 ]; then |
| chunk_status=", chunk ${chunk_num} of ${total_chunks}" |
| fi |
| |
| echo "Installing partition ${part_num} to ${dst}${chunk_status}" |
| |
| case "${part_num}" in |
| "${PARTITION_NUM_STATE}") |
| install_stateful |
| ;; |
| "${PARTITION_NUM_ROOT_A}"|"${PARTITION_NUM_ROOT_B}") |
| # Always copy from ROOT_A for rootfs partitions. |
| part_size=$(partsize "${src}" "${PARTITION_NUM_ROOT_A}") |
| src_offset=$(partoffset "${src}" "${PARTITION_NUM_ROOT_A}") |
| write_partition "${part_num}" "${part_size}" "${dst_offset}" \ |
| "${src_offset}" "${src}" "${dst}" "${chunk_num}" "${total_chunks}" \ |
| "${cache_input}" |
| ;; |
| "${PARTITION_NUM_KERN_A}"|"${PARTITION_NUM_KERN_B}") |
| # Use kernel B from the source into both kernel A and B in the destination. |
| part_size="$(partsize "${src}" "${PARTITION_NUM_KERN_B}")" |
| src_offset="$(partoffset "${src}" "${PARTITION_NUM_KERN_B}")" |
| write_partition "${part_num}" "${part_size}" "${dst_offset}" \ |
| "${src_offset}" "${src}" "${dst}" "${chunk_num}" "${total_chunks}" \ |
| "${cache_input}" |
| ;; |
| *) |
| local src_part_size |
| local dst_part_size |
| src_part_size="$((part_size * SRC_BLKSIZE))" |
| dst_part_size="$(partsize "${dst}" "${part_num}")" |
| dst_part_size="$((dst_part_size * DST_BLKSIZE))" |
| if [ "${src_part_size}" -ne "${dst_part_size}" ] || \ |
| [ "${src_part_size}" -le 4096 ]; then |
| # We only copy partitions that are equally sized and greater than the |
| # min fs block size. This matches the build_image logic. |
| return |
| fi |
| write_partition "${part_num}" "${part_size}" "${dst_offset}" \ |
| "${src_offset}" "${src}" "${dst}" "${chunk_num}" "${total_chunks}" \ |
| "${cache_input}" |
| ;; |
| esac |
| } |
| |
| # Remove partitions 1 to 12 from MTD device. Recreate partitions 1 to 12 with |
| # information from the current GPT table. |
| # $1 is the device node, such as "/dev/mtd0" |
| recreate_nand_partitions() { |
| local dst=$1 |
| local blocksize=$2 |
| local part_no |
| |
| for part_no in /dev/mtd*; do |
| part_no="$(echo "${part_no}" | cut -b9- | grep '^[0-9]*$' || :)" |
| if [ -z "${part_no}" ] || [ "${part_no}" = "0" ]; then |
| continue |
| fi |
| # Ignore any error in case no UBI volume is attached. |
| ubidetach -m "${part_no}" > /dev/null 2>&1 || true |
| nand_partition del "${dst}" "${part_no}" |
| done |
| |
| local gpt_file mtd_size mtd_gpt_file part_size part_offset |
| gpt_file=$(mktemp) |
| flashrom -r "-iRW_GPT:${gpt_file}" |
| mtd_size=$(cat "/sys/class/mtd/$(basename "${dst}")/size") |
| mtd_gpt_file="-D ${mtd_size} ${gpt_file}" |
| for part_no in $(seq "${PARTITION_NUM_STATE}" \ |
| "${PARTITION_NUM_EFI_SYSTEM}"); do |
| part_size=$(partsize "${mtd_gpt_file}" "${part_no}") |
| : $(( part_size *= blocksize )) |
| part_offset=$(partoffset "${mtd_gpt_file}" "${part_no}") |
| : $(( part_offset *= blocksize )) |
| nand_partition add "${dst}" "${part_no}" \ |
| "${part_offset}" "${part_size}" |
| done |
| rm -f "${gpt_file}" |
| } |
| |
| # Find our destination device. |
| # If the user hasn't selected a destination, |
| # we expect that the disk layout declares it for us. |
| check_dst() { |
| if [ -z "${DST}" ]; then |
| die "Error: can not determine destination device. Specify --dst yourself." |
| fi |
| |
| if [ "${DST}" = "/dev/mtd0" ]; then |
| FLAGS_mtd_layout=${FLAGS_TRUE} |
| fi |
| |
| # Check out the dst device. |
| if [ "${FLAGS_mtd_layout}" -eq "${FLAGS_TRUE}" ]; then |
| FLAGS_skip_dst_removable=${FLAGS_TRUE} |
| elif [ ! -b "${DST}" ]; then |
| die "Error: Unable to find destination block device: ${DST}" |
| fi |
| |
| if [ "${DST}" = "${SRC}" ]; then |
| die "Error: src and dst are the same: ${SRC} = ${DST}" |
| fi |
| } |
| |
| # Gets the right PMBR (protective master boot record) code (either from |
| # FLAGS_pmbr_code, source or destination media) by printing the file path |
| # containing PMBR code in standard out. |
| get_pmbr_code() { |
| local pmbr_code="/tmp/gptmbr.bin" |
| |
| if [ -n "${FLAGS_pmbr_code}" ]; then |
| echo "${FLAGS_pmbr_code}" |
| elif [ "${FLAGS_mtd_layout}" -eq "${FLAGS_TRUE}" ]; then |
| # We don't use PMBR if this is on MTD. |
| dd bs="${DST_BLKSIZE}" \ |
| count=1 if=/dev/zero of="${pmbr_code}" >/dev/null 2>&1 |
| echo "${pmbr_code}" |
| else |
| # Steal the PMBR code from the source MBR to put on the dest MBR, for |
| # booting on legacy-BIOS devices. |
| dd bs="${DST_BLKSIZE}" count=1 if="${SRC}" of="${pmbr_code}" >/dev/null 2>&1 |
| echo "${pmbr_code}" |
| fi |
| } |
| |
| # Reload the system partitions after the partition table was modified (so the |
| # device nodes like /dev/sda1 can be accessed). |
| reload_partitions() { |
| if [ "${FLAGS_mtd_layout}" -eq "${FLAGS_FALSE}" ]; then |
| # Reload the partition table on block devices only. |
| # On MTD, the ChromeOS kernel loads the partition table at boot time. |
| # |
| # In some cases, we may be racing with udev for access to the |
| # device leading to EBUSY when we reread the partition table. We |
| # avoid the conflict by using `udevadm settle`, so that udev goes |
| # first. cf. crbug.com/343681. |
| udevadm settle |
| /sbin/blockdev --rereadpt "${DST}" |
| else |
| # On NAND, we need to recreate the partition table. |
| recreate_nand_partitions "${DST}" "${DST_BLKSIZE}" |
| fi |
| } |
| |
| # Post partition copying work and special casing |
| do_post_install() { |
| set -- |
| if [ -n "${FLAGS_target_bios}" ]; then |
| set -- "$@" --bios "${FLAGS_target_bios}" |
| fi |
| if [ "${FLAGS_debug}" -eq "${FLAGS_TRUE}" ]; then |
| set -- "$@" --debug |
| fi |
| local dst_rootfs_dev="" |
| |
| # Now run the postinstall script on one new rootfs. Note that even though |
| # we're passing the new destination partition number as an arg, the postinst |
| # script had better not try to access it, for the reasons we just gave. |
| # We can't run this if the target arch isn't the same as the host arch |
| if [ "${FLAGS_skip_postinstall}" -eq "${FLAGS_FALSE}" ]; then |
| if [ ${FLAGS_mtd_layout} -eq ${FLAGS_TRUE} ]; then |
| if ! [ -b "/dev/ubiblock${PARTITION_NUM_ROOT_A}_0" ]; then |
| ubiblock -c "/dev/ubi${PARTITION_NUM_ROOT_A}_0" |
| fi |
| LOOP_DEV="/dev/ubiblock${PARTITION_NUM_ROOT_A}_0" |
| # We need to pass the __writable__ device to postinst, hence ubiX_0. |
| dst_rootfs_dev="/dev/ubi${PARTITION_NUM_ROOT_A}_0" |
| else |
| loop_offset_setup "${DST}" "${START_ROOTFS_A}" "${DST_BLKSIZE}" |
| dst_rootfs_dev="$(make_partition_dev "${DST}" "${PARTITION_NUM_ROOT_A}")" |
| fi |
| |
| mount_on_loop_dev |
| IS_INSTALL="1" "${TMPMNT}/postinst" "${dst_rootfs_dev}" "$@" |
| umount_from_loop_dev |
| if [ "${FLAGS_mtd_layout}" -eq "${FLAGS_FALSE}" ]; then |
| loop_offset_cleanup |
| fi |
| fi |
| } |
| |
| legacy_offset_size_export() { |
| # Exports all the variables that install_gpt did previously. |
| # This should disappear eventually, but it's here to make existing |
| # code work for now. |
| |
| START_STATEFUL="$(partoffset "$1" "${PARTITION_NUM_STATE}")" |
| START_ROOTFS_A="$(partoffset "$1" "${PARTITION_NUM_ROOT_A}")" |
| |
| NUM_STATEFUL_SECTORS="$(partsize "$1" "${PARTITION_NUM_STATE}")" |
| } |
| |
| main() { |
| # Be aggressive. |
| set -eu |
| if [ "${FLAGS_debug}" = "${FLAGS_TRUE}" ]; then |
| set -x |
| fi |
| |
| check_payload_image |
| mkdir -p "${TMPMNT}" |
| |
| # We untrap on success and run cleanup ourselves. Otherwise, on any failure, |
| # run our custom trap method to gather any diagnostic data before cleaning up. |
| trap cleanup_on_failure EXIT |
| |
| # Clean media browser mounts if they've popped up. |
| prepare_disk |
| locate_gpt |
| |
| # Special handling for payload_image. This is passed in for recovery images |
| # and USB installs. This is done first so we can read the gpt partition |
| # file below. |
| if [ -n "${FLAGS_payload_image}" ]; then |
| PARTITION_NUM_ROOT_A=$(cgpt find -n -l ROOT-A "${FLAGS_payload_image}") |
| PARTITION_NUM_STATE=$(cgpt find -n -l STATE "${FLAGS_payload_image}") |
| |
| SRC="${FLAGS_payload_image}" |
| # Mount files that are required to be referenced (when not already mounted). |
| loop_offset_setup "${SRC}" \ |
| "$(partoffset "${SRC}" "${PARTITION_NUM_ROOT_A}")" 512 |
| tracked_mount -o ro "${LOOP_DEV}" "${ROOT}" |
| loop_offset_setup "${SRC}" \ |
| "$(partoffset "${SRC}" "${PARTITION_NUM_STATE}")" 512 |
| tracked_mount -o ro "${LOOP_DEV}" "${ROOT}"/mnt/stateful_partition |
| fi |
| |
| # Reload the GPT helper functions and the image settings from target root. |
| . "${ROOT}/usr/sbin/write_gpt.sh" |
| load_base_vars |
| |
| # This was moved out of check_payload_image because DEFAULT_ROOTDEV |
| # is not defined until after the GPT helper functions are loaded. |
| if [ "${FLAGS_skip_src_removable}" -eq "${FLAGS_FALSE}" ]; then |
| if [ "$(cat "/sys/block/${SRC#/dev/}/removable")" != "1" ]; then |
| # Removable flag is implemented inconsistently for ARM sdcard reader. |
| # Allow all devices except the default fixed drive. |
| if [ "${SRC}" = "$(get_fixed_dst_drive)" ]; then |
| trap - EXIT |
| cleanup |
| die "Error: Source can not be the destination device: ${SRC}" |
| fi |
| fi |
| fi |
| |
| # Now that we have loaded the partition table we can actually read the format |
| # and partition information. |
| STATEFUL_FORMAT="$(get_format "${PARTITION_NUM_STATE}")" |
| |
| DST=${FLAGS_dst:-$(get_fixed_dst_drive)} |
| check_dst |
| check_removable |
| |
| DST_BLKSIZE="$(blocksize "${DST}")" |
| |
| # Ask for confirmation to be sure. |
| echo "This will install from '${SRC}' to '${DST}'." |
| echo "This will erase all data at this destination: ${DST}" |
| local sure |
| if [ "${FLAGS_yes}" -eq "${FLAGS_FALSE}" ]; then |
| read -r -p "Are you sure (y/N)? " sure |
| if [ "${sure}" != "y" ]; then |
| # Don't run diagnostics if the user explicitly bailed out. |
| trap - EXIT |
| cleanup |
| die "Ok, better safe than sorry; you answered '${sure}'." |
| fi |
| fi |
| |
| # For LVM partitions, the logical volumes/volume groups on the stateful |
| # partition may be active. Deactivate the partitions. |
| if [ "${FLAGS_lvm_stateful}" -eq "${FLAGS_TRUE}" ]; then |
| local dst_stateful="$(make_partition_dev "${DST}" "${PARTITION_NUM_STATE}")" |
| local vg_name="$(get_volume_group "${dst_stateful}")" |
| if [ -n "${vg_name}" ]; then |
| vgchange -an --force "${vg_name}" |
| fi |
| fi |
| |
| # Write the GPT using the board specific script. The parameters are ignored |
| # on MTD devices. |
| write_base_table "${DST}" "$(get_pmbr_code)" |
| legacy_offset_size_export "${DST}" |
| reload_partitions |
| |
| if [ "${FLAGS_skip_rootfs}" -eq "${FLAGS_TRUE}" ]; then |
| echo "Done installing partitions." |
| exit 0 |
| fi |
| |
| if [ "${FLAGS_preserve_stateful}" -eq "${FLAGS_FALSE}" ] && \ |
| [ -z "${FLAGS_lab_preserve_logs}" ]; then |
| wipe_stateful |
| fi |
| |
| # First do the easy ones. Do so in reverse order to have stateful |
| # get installed last. The order shouldn't matter but legacy behavior has |
| # us go in reverse order. |
| copy_partition "${PARTITION_NUM_EFI_SYSTEM}" "${SRC}" "${DST}" 1 1 false # 12 |
| copy_partition "${PARTITION_NUM_RWFW}" "${SRC}" "${DST}" 1 1 false # 11 |
| copy_partition 10 "${SRC}" "${DST}" 1 1 false # 10 |
| copy_partition 9 "${SRC}" "${DST}" 1 1 false # 9 |
| copy_partition "${PARTITION_NUM_OEM}" "${SRC}" "${DST}" 1 1 false # 8 |
| copy_partition "${PARTITION_NUM_ROOT_C}" "${SRC}" "${DST}" 1 1 false # 7 |
| copy_partition "${PARTITION_NUM_KERN_C}" "${SRC}" "${DST}" 1 1 false # 6 |
| copy_partition "${PARTITION_NUM_KERN_B}" "${SRC}" "${DST}" 1 1 false # 4 |
| copy_partition "${PARTITION_NUM_KERN_A}" "${SRC}" "${DST}" 1 1 false # 2 |
| |
| # We want to chunk up the root filesystem. We do this because we're |
| # going to read the source once and write it to the destination twice. |
| # If the rootfs is big and we don't have extra RAM, we can blow out |
| # the amount of free RAM and by the time we write the second |
| # destination we won't have the source cached. Doing it in chunks |
| # prevents this. |
| local chunk_num |
| for chunk_num in $(seq "${NUM_ROOTFS_CHUNKS}"); do |
| copy_partition "${PARTITION_NUM_ROOT_B}" "${SRC}" "${DST}" \ |
| "${chunk_num}" "${NUM_ROOTFS_CHUNKS}" true # 5 |
| copy_partition "${PARTITION_NUM_ROOT_A}" "${SRC}" "${DST}" \ |
| "${chunk_num}" "${NUM_ROOTFS_CHUNKS}" false # 3 |
| done |
| |
| # Last is stateful. |
| copy_partition "${PARTITION_NUM_STATE}" "${SRC}" "${DST}" 1 1 false # 1 |
| |
| do_post_install |
| |
| # Force data to disk before we declare done. |
| sync |
| cleanup |
| trap - EXIT |
| |
| echo "------------------------------------------------------------" |
| echo "" |
| echo "Installation to '${DST}' complete." |
| echo "Please shutdown, remove the USB device, cross your fingers, and reboot." |
| } |
| |
| main "$@" |