Chromium Code Reviews| Index: src/platform/installer/chromeos-common.sh |
| diff --git a/src/platform/installer/chromeos-common.sh b/src/platform/installer/chromeos-common.sh |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c9776a0edfaed8306863cb2a84d79d072baf5e39 |
| --- /dev/null |
| +++ b/src/platform/installer/chromeos-common.sh |
| @@ -0,0 +1,310 @@ |
| +# 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 contains common constants and functions for installer scripts. This must |
| +# evaluate properly for both /bin/bash and /bin/sh, since it's used both to |
| +# create the initial image at compile time and to install or upgrade a running |
| +# image. |
| + |
| +# Here are the GUIDs we'll be using to identify various partitions. |
| +STATEFUL_GUID='ebd0a0a2-b9e5-4433-87c0-68b6b72699c7' |
| +KERN_GUID='fe3a2a5d-4f32-41a7-b725-accc3285a309' |
| +ROOTFS_GUID='3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec' |
| +ESP_GUID='28732ac1-1ff8-d211-ba4b-00a0c93ec93b' |
| + |
| + |
| +# The GPT tables describe things in terms of 512-byte sectors, but some |
| +# filesystems prefer 4096-byte blocks. These functions help with alignment |
| +# issues. |
| + |
| +# This returns the size of a file or device in 512-byte sectors, rounded up if |
| +# needed. |
| +# Invoke as: subshell |
| +# Args: FILENAME |
| +# Return: whole number of sectors needed to fully contain FILENAME |
| +numsectors() { |
| + case $1 in |
| + /dev/*[0-9]) |
| + dnum=${1##*/} |
| + dev=${dnum%%[0-9]*} |
| + cat /sys/block/$dev/$dnum/size |
| + ;; |
| + /dev/*) |
| + dev=${1##*/} |
| + cat /sys/block/$dev/size |
| + ;; |
| + *) |
| + local bytes=$(stat -c%s "$1") |
| + local sectors=$(( $bytes / 512 )) |
| + local rem=$(( $bytes % 512 )) |
| + if [ $rem -ne 0 ]; then |
| + sectors=$(( $sectors + 1 )) |
| + fi |
| + echo $sectors |
| + ;; |
| + esac |
| +} |
| + |
| +# Round a number of 512-byte sectors up to an integral number of 4096-byte |
| +# blocks. |
| +# Invoke as: subshell |
| +# Args: SECTORS |
| +# Return: Next largest multiple-of-8 sectors (ex: 4->8, 33->40, 32->32) |
| +roundup() { |
| + local num=$1 |
| + local rem=$(( $num % 8 )) |
| + |
| + if (( $rem )); then |
| + num=$(($num + 8 - $rem)) |
| + fi |
| + echo $num |
| +} |
| + |
| +# Truncate a number of 512-byte sectors down to an integral number of 4096-byte |
| +# blocks. |
| +# Invoke as: subshell |
| +# Args: SECTORS |
| +# Return: Next smallest multiple-of-8 sectors (ex: 4->0, 33->32, 32->32) |
| +rounddown() { |
| + local num=$1 |
| + local rem=$(( $num % 8 )) |
| + |
| + if (( $rem )); then |
| + num=$(($num - $rem)) |
| + fi |
| + echo $num |
| +} |
| + |
| + |
| +# We need to locate the gpt tool. It should already be installed in the build |
| +# chroot, but some of these functions may be invoked outside the chroot (by |
| +# image_to_usb or similar), so we need to find it. |
| +GPT=$(which gpt 2>/dev/null) || /bin/true |
| +if [ -z "$GPT" ]; then |
| + if [ -x "${DEFAULT_CHROOT_DIR:-}/usr/bin/gpt" ]; then |
| + GPT="${DEFAULT_CHROOT_DIR:-}/usr/bin/gpt" |
| + else |
| + echo "can't find gpt tool" 1>&2 |
| + exit 1 |
| + fi |
| +fi |
| + |
| + |
| +# This installs a GPT into the specified device or file, using the given |
| +# components. If the target is a block device we'll do a full install. |
| +# Otherwise, it'll be just enough to boot. |
| +# Invoke as: command (not subshell) |
| +# Args: TARGET ROOTFS_IMG KERNEL_IMG STATEFUL_IMG PMBRCODE |
| +# Return: nothing |
| +# Side effects: Sets these global variables describing the GPT partitions |
| +# (all untis are 512-byte sectors): |
| +# NUM_KERN_SECTORS |
| +# NUM_ROOTFS_SECTORS |
| +# NUM_STATEFUL_SECTORS |
| +# NUM_RESERVED_SECTORS |
| +# START_KERN_A |
| +# START_STATEFUL |
| +# START_ROOTFS_A |
| +# START_KERN_B |
| +# START_ROOTFS_B |
| +# START_RESERVED |
| +install_gpt() { |
| + local outdev=$1 |
| + local rootfs_img=$2 |
| + local kernel_img=$3 |
| + local stateful_img=$4 |
| + local pmbrcode=$5 |
| + |
| + # The gpt tool requires a fixed-size target to work on, so we may have to |
| + # create a file of the appropriate size. Let's figure out what that size is |
| + # now. The partition layout is gonna look something like this: |
| + # |
| + # PMBR (512 bytes) |
| + # Primary GPT Header (512 bytes) |
| + # Primary GPT Table (16K) |
| + # Kernel A partition 2 |
| + # Kernel B partition 4 |
| + # reserved space for additional partitions partition 6,7,8,... |
| + # Stateful partition (as large as possible) partition 1 |
| + # Rootfs B partition 5 |
| + # Rootfs A partition 3 |
| + # Secondary GPT Table (16K) |
| + # Secondary GPT Header (512 bytes) |
| + # |
| + # Please refer to the official ChromeOS documentation for the details and |
| + # explanation behind the layout and partition numbering scheme. The short |
| + # version is that 1) we want everything above partition 3 to be optional when |
| + # we're creating a bootable USB key, 2) we want to be able to add new |
| + # partitions later without breaking current scripts, and 3) we may need to |
| + # increase the size of the rootfs during an upgrade, which means shrinking |
| + # the size of the stateful partition. |
| + # |
| + # One nonobvious contraint is that the ext2-based filesystems typically use |
| + # 4096-byte blocks. We'll need a little padding at each end of the disk to |
| + # align the useable space to that size boundary. |
| + |
| + # Here are the size limits that we're currently requiring |
| + local max_kern_sectors=32768 # 16M |
| + local max_rootfs_sectors=2097152 # 1G |
| + local max_reserved_sectors=131072 # 64M |
| + local min_stateful_sectors=262144 # 128M, expands to fill available space |
| + |
| + local num_pmbr_sectors=1 |
| + local num_gpt_hdr_sectors=1 |
| + local num_gpt_table_sectors=32 # 16K |
| + local num_footer_sectors=$(($num_gpt_hdr_sectors + $num_gpt_table_sectors)) |
| + local num_header_sectors=$(($num_pmbr_sectors + $num_footer_sectors)) |
| + |
| + local start_useful=$(roundup $num_header_sectors) |
| + |
| + # What are we doing? |
| + if [ -b $outdev ]; then |
| + # Block device, need to be root. |
| + local sudo=sudo |
| + |
| + # Full install, use max sizes and create both A & B images. |
| + NUM_KERN_SECTORS=$max_kern_sectors |
| + NUM_ROOTFS_SECTORS=$max_rootfs_sectors |
| + NUM_RESERVED_SECTORS=$max_reserved_sectors |
| + |
| + # Where do things go? |
| + START_KERN_A=$start_useful |
| + START_KERN_B=$(($START_KERN_A + $NUM_KERN_SECTORS)) |
| + START_RESERVED=$(($START_KERN_B + $NUM_KERN_SECTORS)) |
| + START_STATEFUL=$(($START_RESERVED + $NUM_RESERVED_SECTORS)) |
| + |
| + local total_sectors=$(numsectors $outdev) |
| + local start_gpt_footer=$(($total_sectors - $num_footer_sectors)) |
| + local end_useful=$(rounddown $start_gpt_footer) |
| + |
| + START_ROOTFS_A=$(($end_useful - $NUM_ROOTFS_SECTORS)) |
| + START_ROOTFS_B=$(($START_ROOTFS_A - $NUM_ROOTFS_SECTORS)) |
| + |
| + NUM_STATEFUL_SECTORS=$(($START_ROOTFS_B - $START_STATEFUL)) |
| + else |
| + # Just a local file. |
| + local sudo= |
| + |
| + # We'll only populate partitions 1, 2, 3. Image B isn't required for this, |
| + # and the others are still theoretical. |
| + NUM_KERN_SECTORS=$(roundup $(numsectors $kernel_img)) |
| + NUM_ROOTFS_SECTORS=$(roundup $(numsectors $rootfs_img)) |
| + NUM_STATEFUL_SECTORS=$(roundup $(numsectors $stateful_img)) |
| + NUM_RESERVED_SECTORS=0 |
| + |
| + START_KERN_A=$start_useful |
| + START_STATEFUL=$(($START_KERN_A + $NUM_KERN_SECTORS)) |
| + START_ROOTFS_A=$(($START_STATEFUL + $NUM_STATEFUL_SECTORS)) |
| + START_KERN_B="" |
| + START_ROOTFS_B="" |
| + START_RESERVED="" |
| + |
| + # For minimal install, we're not worried about the secondary GPT header |
| + # being at the end of the device because we're almost always writing to a |
| + # file. If that's not true, the secondary will just be invalid. |
| + local start_gpt_footer=$(($START_ROOTFS_A + $NUM_ROOTFS_SECTORS)) |
| + local end_useful=$start_gpt_footer |
| + |
| + local total_sectors=$(($start_gpt_footer + $num_footer_sectors)) |
| + |
| + # Create the image file if it doesn't exist. |
| + if [ ! -e ${outdev} ]; then |
| + dd if=/dev/zero of=${outdev} bs=512 count=1 seek=$(($total_sectors - 1)) |
| + fi |
| + fi |
| + |
| + echo "Creating partition tables..." |
| + |
| + # Zap any old partitions (otherwise gpt complains). |
| + $sudo dd if=/dev/zero of=${outdev} conv=notrunc bs=512 \ |
| + count=$num_header_sectors |
| + $sudo dd if=/dev/zero of=${outdev} conv=notrunc bs=512 \ |
| + seek=${start_gpt_footer} count=$num_footer_sectors |
| + |
| + # Create the new GPT partitions. The order determines the partition number. |
| + # Note that the partition label is in the GPT only. The filesystem label is |
| + # what's used to populate /dev/disk/by-label/, and this is not that. |
| + $sudo $GPT create ${outdev} |
| + |
| + $sudo $GPT add -b ${START_STATEFUL} -s ${NUM_STATEFUL_SECTORS} \ |
| + -t ${STATEFUL_GUID} ${outdev} |
| + $sudo $GPT label -i 1 -l "STATE" ${outdev} |
| + |
| + $sudo $GPT add -b ${START_KERN_A} -s ${NUM_KERN_SECTORS} \ |
| + -t ${KERN_GUID} ${outdev} |
| + $sudo $GPT label -i 2 -l "KERN-A" ${outdev} |
| + |
| + $sudo $GPT add -b ${START_ROOTFS_A} -s ${NUM_ROOTFS_SECTORS} \ |
| + -t ${ROOTFS_GUID} ${outdev} |
| + $sudo $GPT label -i 3 -l "ROOT-A" ${outdev} |
| + |
| + # add the rest of the partitions for a full install |
| + if [ -n "$START_KERN_B" ]; then |
| + $sudo $GPT add -b ${START_KERN_B} -s ${NUM_KERN_SECTORS} \ |
| + -t ${KERN_GUID} ${outdev} |
| + $sudo $GPT label -i 4 -l "KERN-B" ${outdev} |
| + |
| + $sudo $GPT add -b ${START_ROOTFS_B} -s ${NUM_ROOTFS_SECTORS} \ |
| + -t ${ROOTFS_GUID} ${outdev} |
| + $sudo $GPT label -i 5 -l "ROOT-B" ${outdev} |
| + fi |
| + |
| + # Create the PMBR and instruct it to boot ROOT-A |
| + $sudo $GPT boot -i 3 -b ${pmbrcode} ${outdev} |
| + |
| + # Display what we've got |
| + $sudo $GPT -r show -l ${outdev} |
| + |
| + sync |
| +} |
| + |
| + |
| +# Helper function, please ignore and look below. |
| +_partinfo() { |
| + local device=$1 |
| + local partnum=$2 |
| + local start size part x n |
| + sudo $GPT -r -S show $device \ |
| + | grep 'GPT part -' \ |
| + | while read start size part x x x n x; do \ |
| + if [ "${part}" -eq "${partnum}" ]; then \ |
| + echo $start $size; \ |
| + fi; \ |
| + done |
| + # The 'while' is a subshell, so there's no way to indicate success. |
| +} |
| + |
| +# Read GPT table to find information about a specific partition. |
| +# Invoke as: subshell |
| +# Args: DEVICE PARTNUM |
| +# Returns: offset and size (in sectors) of partition PARTNUM |
| +partinfo() { |
| + # get string |
| + local X=$(_partinfo $1 $2) |
| + # detect success or failure here |
| + [ -n "$X" ] && echo $X |
| +} |
| + |
| +# Read GPT table to find the starting location of a specific partition. |
| +# Invoke as: subshell |
| +# Args: DEVICE PARTNUM |
| +# Returns: offset (in sectors) of partition PARTNUM |
| +partoffset() { |
| + # get string |
| + local X=$(_partinfo $1 $2) |
| + # detect success or failure here |
| + [ -n "$X" ] && echo ${X% *} |
|
adlr
2010/03/30 19:50:20
same here?
|
| +} |
| + |
| +# Read GPT table to find the size of a specific partition. |
| +# Invoke as: subshell |
| +# Args: DEVICE PARTNUM |
| +# Returns: size (in sectors) of partition PARTNUM |
| +partsize() { |
| + # get string |
| + local X=$(_partinfo $1 $2) |
| + # detect success or failure here |
| + [ -n "$X" ] && echo ${X#* } |
|
adlr
2010/03/30 19:50:20
same here?
|
| +} |
| + |