Skip to content

Instantly share code, notes, and snippets.

@kylemanna
Created August 16, 2012 20:04
Show Gist options
  • Select an option

  • Save kylemanna/3373171 to your computer and use it in GitHub Desktop.

Select an option

Save kylemanna/3373171 to your computer and use it in GitHub Desktop.
Embedded Target Deployment Script
#!/bin/sh
#
# Goal of this script is to deploy a root filesystem to a target. It can be
# used for factory deployment or to reflash the system with the latest image
# and pull everything out of the tarball.
#
# A use case for factory deployment might include putting this script in a
# initramfs image that then auto invokes this script. Arguments are passed
# via the kernel command line. Example:
#
# deploytarget=/dev/disk/by-path/platform-mmci-omap-hs.2
# deploysource=/dev/disk/by-path/platform-mmci-omap-hs.1-part1
# deploytarball=deploy-image.tar.bz2
#
# Author: Kyle Manna
#
BIN=$0
EMMC=mmc2
UIMAGE_SRC=uImage-2.6.39
UIMAGE_DST=uImage
MNT_BOOT=/mnt/boot
MNT_ROOT=/mnt/rootfs
MNT_SRC=/mnt/src
MTD_MLO=/dev/mtd0
MTD_UBOOT=/dev/mtd1
MTD_ENV=/dev/mtd2
#
# Almost all echos should use this
#
note() {
echo ">> $@"
}
#
# Something went wrong, complain and quit
#
fatal() {
note "Fatal: $@"
exit 1
}
#
# Something went right, brag and quit
#
success() {
note "Success: $@"
exit 0
}
#
# Read kernel command line for $1 argument and echo it
#
read_cmdline() {
[ -z "$CMDLINE" ] && CMDLINE=$(cat /proc/cmdline)
for arg in $CMDLINE; do
optarg=`expr "x$arg" : 'x[^=]*=\(.*\)'`
case $arg in
$1=*)
echo $optarg
return 0
;;
esac
done
}
#
# Print usage help
#
usage() {
echo
echo "Usage $BIN CMD [ARGS ...]"
echo
echo "CMD can be one of:"
echo " unmount DEVICE unmount all the partitions on a DEVICE"
echo " format DEVICE create partitions and filesystems on DEVICE"
echo " extract PART TARBALL DEST mount PART and extract TARBALL to PART/DEST"
echo " spiflash MLO UBOOT flash MLO and UBOOT bootloaders"
echo " boot PART MLO UBOOT KERNEL copy MLO, UBOOT, and KERNEL to PART boot partition"
echo " post PART SCRIPT mount PART and run SCRIPT with PART passed as argument"
echo
echo " auto [DEVICE TARBALL [SCRIPT]] Optional arguments, if not specified are read from kernel cmdline"
echo " Kernel cmdline arguments:"
echo " deploytarget - the target device to operate on"
echo " deploysource - the source partition to read script and tarball from"
echo " could also be nfs: 192.168.1.1:/nfsroots/deploy"
echo " deploytarball - the source tarball relative to deploysource"
echo " deployscript - the source script to execute after tarball extraction"
echo " relative to deploysource"
echo
}
#
# Check that $1 is a valid block device
#
check_dev() {
DELIM=$(expr match $SRC '.*:')
if [ "$DELIM" -ne "0" ]; then
# If a semi-colon was found, this is a nfs mount, return 0
return 0
elif [ -z "$1" -o ! -b "$1" ]; then
fatal "Invalid device (\"$1\") given"
fi
return 0
}
#
# Get partitions (Doesn't handle spaces properly!)
# Regex is also screwy to work in GNU find and Busybox find
#
get_parts() {
DEV=$1
find "$(dirname $DEV)" -maxdepth 1 -regex "${DEV}..*" | sort
}
#
# Mount a partition
#
mount_part() {
PART=$1
MNT=$2
OPT=$3
CNT=$4
# Default to 5 tries
[ -z "$CNT" ] && CNT=5
CNT=$(($CNT - 1))
[ -z "$OPT" ] && OPT=" "
check_dev "$PART"
# Try to unmount anyways
umount "$PART" > /dev/null 2>&1 | grep -v "not mounted"
# Mount it where we want it
note "Mounting $PART at $MNT with options \"$OPT\""
mkdir -p "$MNT"
mount $OPT "$PART" "$MNT"
if [ $? -ne 0 -a $CNT -gt 0 ]; then
note "Mount of $PART at $MNT failed, trying $CNT more times"
sleep 1
mount_part "$PART" "$MNT" "$OPT" "$CNT"
elif [ $CNT -eq 0 ]; then
fatal "Failed to mount $PART at $MNT"
fi
}
#
# Unmount all partitions on $1 device
#
do_unmount() {
DEV=$1
check_dev "$DEV"
note "Unmounting $DEV"
#find "$(dirname $DEV)" -maxdepth 1 -regex "${DEV}..*" -print0 | xargs -t -r -0 umount 2>&1 | grep -v "not mounted"
get_parts $DEV | xargs -t -r umount 2>&1 | grep -v "not mounted"
return $?
}
#
# Copy the first and second stage bootloaders to SPI flash and clear the env
#
do_spiflash() {
MLO=$1
UBOOT=$2
if [ ! -r "$MLO" -o ! -r "$UBOOT" ]; then
usage
fatal "Unable to read MLO (\"$MLO\") or u-boot (\"$UBOOT\")"
fi
note "Flashing bootloaders to SPI flash"
#
# In an ideal world the following could be combined, but
# the busybox shell is crippled.
#
[ ! -c "$MTD_MLO" ] && fatal "Invalid $MTD_MLO device"
[ ! -c "$MTD_UBOOT" ] && fatal "Invalid $MTD_UBOOT device"
[ ! -c "$MTD_ENV" ] && fatal "Invalid $MTD_ENV device"
echo ">> Swapping MLO endianess..."
MLO_SPI=$(mktemp)
arm-angstrom-linux-gnueabi-objcopy -Ibinary --reverse-bytes=4 $MLO $MLO_SPI || fatal "Failed to convert MLO"
echo ">> Erasing $MTD_MLO..."
flash_erase -q $MTD_MLO 0 0 || fatal "Failed to erase MLO"
echo ">> Flashing $MLO to $MTD_MLO..."
flashcp $MLO_SPI $MTD_MLO || fatal "Failed to write MLO"
rm -f $MLO_SPI
echo ">> Erasing $MTD_UBOOT..."
flash_erase -q $MTD_UBOOT 0 0 || fatal "Failed to erase u-boot.img"
echo ">> Flashing $UBOOT to $MTD_UBOOT..."
flashcp $UBOOT $MTD_UBOOT || fatal "Failed to write MLO"
echo ">> Erasing u-boot environment in $MTD_ENV"
flash_erase -q $MTD_ENV 0 0 || fatal "Failed to reset environment"
return $?
}
#
# Copy the bootloaders and kernel to the boot partition
#
do_boot() {
PART=$1
MLO=$2
UBOOT=$3
KERNEL=$4
check_dev "$PART"
if [ ! -r "$MLO" ]; then
usage
fatal "Unable to read MLO (\"$MLO\")"
fi
if [ ! -r "$UBOOT" ]; then
usage
fatal "Unable to read u-boot.img (\"$UBOOT\")"
fi
if [ ! -r "$KERNEL" ]; then
usage
fatal "Unable to read kernel (\"$KERNEL\")"
fi
mount_part "$PART" "$MNT_BOOT"
note "Copying MLO from $MLO to $MNT_BOOT"
cp $MLO $MNT_BOOT || fatal "Failed to copy MLO (\"$MLO\") to boot (\"$MNT_BOOT\")"
note "Copying u-boot.img from $UBOOT to $MNT_BOOT"
cp $UBOOT $MNT_BOOT || fatal "Failed to copy u-boot.img (\"$UBOOT\") to boot (\"$MNT_BOOT\")"
KERNEL_DEST=$MNT_BOOT/$UIMAGE_DST
note "Copying the kernel from $KERNEL to $KERNEL_DEST"
cp $KERNEL $KERNEL_DEST || fatal "Failed to copy $KERNEL to $KERNEL_DEST"
# Should we unmount here? Instead mount read only
note "Remounting $MNT_BOOT as read-only"
mount -o remount,ro $MNT_BOOT || fatal "Failed to remount $MNT_BOOT as readonly"
}
#
# Extract the tarball to the rootfs
#
do_extract() {
PART=$1
TARBALL=$2
# Maybe unset and empty string
DEST=$3
check_dev "$PART"
if [ ! -r "$TARBALL" ]; then
usage
fatal "Unable to read tarball (\"$TARBALL\")"
fi
mount_part "$PART" "$MNT_ROOT/$DEST"
# Extract the tarball
note "Extracting $TARBALL to $MNT_ROOT/$DEST"
time bar "$TARBALL" | tar xj -C "$MNT_ROOT/$DEST" || fatal "Failed to extract tarball"
note "Syncing the filesystem changes"
time sync
}
#
# Format the specified device and error on the side of DOS compatability.
# This creates partitions, and filesystems. All data _WILL_ be _LOST_.
#
do_format()
{
DEV=$1
do_unmount "$DEV"
note "Clearing partition table on $DEV"
dd if=/dev/zero of=$DEV count=1024 || fatal "Failed to clear partition table"
# Re-read the empty partition table so that we can be sure udev won't automount
sfdisk -R $DEV 2>&1 | grep "BLKRRPART.*busy"
if [ "$?" -eq "0" ]; then
do_unmount "$DEV"
sfdisk -R $DEV 2>&1 | grep "BLKRRPART.*busy" \
&& fatal "Disk is in use, maybe fscking an unclean unmount. Please try again after waiting or rebooting"
fi
#
# These are cylinders in DOS compat mode
#
# 255 * 63 * 512 = 8225280 bytes/cylinder
# 255 * 63 * 512 = 7.84423828 megabytes/cylinder
# Round up:
HEADS=255
SECTORS=63
BLKSZ=512
CYLINDERS=$(($HEADS * $SECTORS))
BYTES_PER_CYL=$(($CYLINDERS * $BLKSZ))
MB_PER_CYL=8
# Partion sizes in cylinders
PART1_START=1
PART1_SZ=$((64/$MB_PER_CYL)) # Target about 64 MB
PART2_START=$(($PART1_START + $PART1_SZ))
PART2_SZ=$((3500/$MB_PER_CYL)) # Target about 4096 MB
PART3_START=$(($PART2_START + $PART2_SZ))
PART3_SZ=$((4000/$MB_PER_CYL)) # Target about 4096 MB
# Assemble sfdisk command
note "Creating partition table on $DEV"
SF_CMD="$PART1_START,$PART1_SZ,0x0C,*\n"
SF_CMD="$SF_CMD$PART2_START,$PART2_SZ,L\n"
SF_CMD="$SF_CMD$PART3_START,$PART3_SZ,L\n"
echo -e $SF_CMD | sfdisk -D -H $HEADS -S $SECTORS $DEV
[ $? -lt 0 ] && fatal "Unable to create partitions"
note "Waiting for kernel to re-read partition table and udev to create device nodes"
udevadm control --start-exec-queue
sfdisk -R $DEV
while [ $(get_parts "$DEV" | wc -l) -ne 3 ]; do echo -n "."; sleep 1; done
udevadm control --stop-exec-queue
PARTS=$(get_parts "$DEV")
PART1=$(echo $PARTS | cut -d " " -f 1)
note "Making filesystem on $PART1"
mkfs.vfat -F 32 -n "boot" $PART1 || fatal "Failed to format part1"
PART2=$(echo $PARTS | cut -d " " -f 2)
note "Making filesystem on $PART2"
mke2fs -j -L "rootfs" $PART2 || fatal "Failed to format part2"
PART3=$(echo $PARTS | cut -d " " -f 3)
MNT_TMP=$(mktemp -d)
mount -o ro $PART3 $MNT_TMP &> /dev/null
if [ "$?" -eq "0" ]; then
note "Leaving intact filesystem on $PART3"
umount $MNT_TMP
else
note "Making filesystem on $PART3"
mke2fs -j -L "data" $PART3 || fatal "Failed to format part3"
fi
return 0
}
#
# Run post install script $2 after mounting $1
#
do_post() {
PART=$1
SCRIPT=$2
[ -x $SCRIPT ] || fatal "Post install script (\"$SCRIPT\") not executable"
mount_part "$PART" "$MNT_ROOT"
$SCRIPT $MNT_ROOT
}
#
# Automatically do everything
#
do_auto() {
DEV=$1
TARBALL=$2
SCRIPT=$3
DATA_TARBALL=$4
NEED_MNT_SRC=0
# If zero length, default to kernel command line
if [ -z "$DEV" ]; then
note "DEV and TARBALL arguments are unset, trying kernel cmdline"
grep -q deploy /proc/cmdline || fatal "No deploy values found on kernel cmdline"
# Read command line for DEV and TARBALL and override
DEV=$(read_cmdline "deploytarget")
fi
# If zero length, default to kernel command line
if [ -z "$TARBALL" -a -n "$(read_cmdline "deploytarball")" ]; then
NEED_MNT_SRC=1
TARBALL="$MNT_SRC/$(read_cmdline "deploytarball")"
fi
# If zero length, default to kernel command line
if [ -z "$SCRIPT" -a -n "$(read_cmdline "deployscript")" ]; then
NEED_MNT_SRC=1
SCRIPT=$MNT_SRC/$(read_cmdline "deployscript")
fi
# If zero length, default to kernel command line
if [ -z "$DATA_TARBALL" -a -n "$(read_cmdline "deploydatatarball")" ]; then
NEED_MNT_SRC=1
DATA_TARBALL=$MNT_SRC/$(read_cmdline "deploydatatarball")
fi
# Mount source if needed
if [ "$NEED_MNT_SRC" != "0" ]; then
SRC=$(read_cmdline "deploysource")
mkdir -p "$MNT_SRC" || fatal "Unable to mkdir $MNT_SRC"
umount "$SRC" "$MNT_SRC" 2>&1 | grep -v -e "not mounted" -e "not found"
DELIM=$(expr match $SRC '.*:')
if [ "$DELIM" -ne "0" ]; then
# The ":" delimiter was found, assume NFS mount was given
#NFS_HOST=${SRC:0:$(($DELIM - 1))}
#NFS_ROOT=${SRC:$DELIM}
# Always get a dhcp address if we detect networking
note "Requesting an IP address"
udhcpc -t 5 -n -i eth0 || fatal "Unable to bring up networking"
MNT_EXTRA="-t nfs4 -n"
fi
mount_part "$SRC" "$MNT_SRC" "-o ro $MNT_EXTRA"
fi
# If the tarball/script is set, and it's not readable/executable, quit
[ -n "$TARBALL" -a ! -r "$TARBALL" ] && fatal "Unable to read tarball (\"$TARBALL\")"
[ -n "$SCRIPT" -a ! -x "$SCRIPT" ] && fatal "Unable to execute script (\"$SCRIPT\")"
#
# Begin actual work
#
if [ -r "$TARBALL" ]; then
note "Auto deploying rootfs tarball"
do_format "$DEV"
# This needs to happen after formatting
PARTS=$(get_parts "$DEV")
PART1=$(echo $PARTS | cut -d " " -f 1)
PART2=$(echo $PARTS | cut -d " " -f 2)
do_extract "$PART2" "$TARBALL"
# Only program SPI flash if we detect we are programming the on-board eMMC
readlink -f "/sys/block/$(basename $(readlink -f "$DEV"))/device" | grep -q "$EMMC"
if [ $? -eq 0 ]; then
do_spiflash "$MNT_ROOT/boot/MLO" "$MNT_ROOT/boot/u-boot.img"
else
note "Skipping SPI Flash as $DEV appears removable"
fi
do_boot "$PART1" "$MNT_ROOT/boot/MLO" \
"$MNT_ROOT/boot/u-boot.img" "$MNT_ROOT/boot/$UIMAGE_SRC"
MNT_ROOT_RO="1"
fi
#
# Extract data tarball if readable
#
if [ -r "$DATA_TARBALL" ]; then
DEST="calvin"
note "Auto deploying data tarball"
# This needs to happen after formatting
PARTS=$(get_parts "$DEV")
PART3=$(echo $PARTS | cut -d " " -f 3)
do_extract "$PART3" "$DATA_TARBALL" "$DEST"
# Should we unmount here? Instead mount read only
note "Remounting $MNT_ROOT/$DEST as read-only"
mount -o remount,ro "$MNT_ROOT/$DEST" || fatal "Failed to remount $MNT_ROOT/$DEST as read-only"
MNT_ROOT_RO="1"
fi
if [ -x "$SCRIPT" ]; then
note "Auto running post install script"
# This needs to happen after formatting
PARTS=$(get_parts "$DEV")
PART2=$(echo $PARTS | cut -d " " -f 2)
do_post "$PART2" "$SCRIPT"
fi
if [ -n "$MNT_ROOT_RO" ]; then
# Should we unmount here? Instead mount read only
note "Remounting $MNT_ROOT as read-only"
mount -o remount,ro $MNT_ROOT || fatal "Failed to remount $MNT_ROOT as read-only"
fi
}
#
# Begin main program
#
# Extract cmd
if [ $# -lt 1 ]; then
usage
fatal "No CMD given"
fi
CMD=$1
# Wait for udev to settle, then disable it
# Use a trap to make sure we always re-enable it
udevadm settle
trap "udevadm control --start-exec-queue" INT TERM EXIT
udevadm control --stop-exec-queue
case "$CMD" in
"unmount")
DEV=$2
do_unmount "$DEV"
;;
"format")
DEV=$2
do_format "$DEV"
;;
"extract")
PART=$2
TARBALL=$3
DEST=$3
do_extract "$PART" "$TARBALL" "$DEST"
;;
"boot")
PART=$2
MLO=$3
UBOOT=$4
KERNEL=$5
do_boot "$PART" "$MLO" "$UBOOT" "$KERNEL"
;;
"spiflash")
MLO=$2
UBOOT=$3
do_spiflash "$MLO" "$UBOOT"
;;
"post")
PART=$2
SCRIPT=$3
do_post "$PART" "$SCRIPT"
;;
"auto")
DEV=$2
TARBALL=$3
SCRIPT=$4
DATA_TARBALL=$5
do_auto "$DEV" "$TARBALL" "$SCRIPT" "$DATA_TARBALL"
;;
*)
fatal "Unknown CMD = \"$CMD\""
;;
esac
success "Command \"$CMD\" completed, exiting"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment