Created
August 16, 2012 20:04
-
-
Save kylemanna/3373171 to your computer and use it in GitHub Desktop.
Embedded Target Deployment Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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