summaryrefslogtreecommitdiffstats
path: root/scripts/mkefi-agl.sh
blob: c44ecedde53e757c794ee98a593dc50197fc4178 (288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
#!/bin/sh
#
# Copyright (c) 2012, Intel Corporation.
# All rights reserved.
#
# This program is free software;  you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY;  without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
# the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program;  if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Modification from mkefidisk.sh provided by the Yocto project by Dominig
# to install Automotive Grade Linux (AGL) on Minnowboard and any PC with UEFI boot
#
# changes
#         - simpler use model
#         - keep initrd if present
#         - create a grub config with PARTUID to ease boot from various devices automaticaly
#         - add a UEFI startup.nsh script for autoboot
#         - remove any configured swap is fstab
#         - accept .hddimg, wic and wic.xz as sources

LANG=C

# Set to 1 to enable additional output
DEBUG=0
exec 3>/dev/null

#
# Defaults
#
# 100 Mb for the boot partition
BOOT_SIZE=100
# min available space on TMP_DIR for uncompressing xz image in kB e.g. 5G (5000000)
TMP_SIZE_MIN=5000000
# TMP_DIR directory use for holding image file for uncompression (e.g. /tmp or $HOME)
TMP_DIR=/tmp

# Cleanup after die()
cleanup() {
	debug "Syncing and unmounting devices"
	# Unmount anything we mounted
	unmount $ROOTFS_MNT || error "Failed to unmount $ROOTFS_MNT"
	unmount $BOOTFS_MNT || error "Failed to unmount $BOOTFS_MNT"
	unmount $HDDIMG_ROOTFS_MNT || error "Failed to unmount $HDDIMG_ROOTFS_MNT"
    unmount $HDDIMG_MNT || error "Failed to unmount $HDDIMG_MNT"
	if [ "$IMG_TYPE" = "DISK" ]; then
        debug "de-attaching loop devices"
        for LOOP_DEVICE in `losetup --list |grep $HDDIMG | cut -d" " -f1` ; do
           losetup -d $LOOP_DEVICE  1>&3 2>&1 || error "Detaching $LOOP_DEVICE from $HDDIMG failled"
        done   
     fi

	# Remove the TMPDIR
	debug "Removing temporary files"
	if [ -d "$TMPDIR" ]; then
		rm -rf "$TMPDIR" || error "Failed to remove $TMPDIR"
	fi
        [ -f "$TMP_DIR/TMP-AGL-wic-image.wic" ] && rm -f $TMP_DIR/TMP-AGL-wic-image.wic
}

trap 'die "Signal Received, Aborting..."' HUP INT TERM

# Logging routines
WARNINGS=0
ERRORS=0
CLEAR="$(tput sgr0)"
INFO="$(tput bold)"
RED="$(tput setaf 1)$(tput bold)"
GREEN="$(tput setaf 2)$(tput bold)"
YELLOW="$(tput setaf 3)$(tput bold)"
info() {
	echo "${INFO}$1${CLEAR}"
}
error() {
	ERRORS=$((ERRORS+1))
	echo "${RED}$1${CLEAR}"
}
warn() {
	WARNINGS=$((WARNINGS+1))
	echo "${YELLOW}$1${CLEAR}"
}
success() {
	echo "${GREEN}$1${CLEAR}"
}
die() {
	error "$1"
	cleanup
	exit 1
}
debug() {
	if [ $DEBUG -eq 1 ]; then
		echo "$1"
	fi
}

usage() {
	echo "Install AGL on a removable device to boot on IA UEFI based computer"
	echo "In particular is can create USB or SD bootable support for Minnowboard"
	echo ""
	echo "Usage: $(basename $0) [-v] IDSK_IMAGE REMOVABLE_DEVICE"
	echo "       -v: Verbose debug"
	echo "       HDDIMG: The DISK_IMAGE file generated by Yocto the efi disk from"
        echo "               Supported formats are .hddimg, .wic .wic.xz"
	echo "       REMOVABLE_DEVICE: The block device to write the image to, e.g. /dev/sdh"
	echo "ex:"
	echo "   mkefi-agl.sh   agl-demo-platform-intel-corei7-64.hddimg /dev/sdd"
        echo "   mkefi-agl.sh   agl-demo-platform-intel-corei7-64.wic.xz /dev/sdd"
	exit 1
}

image_details() {
	IMG=$1
	info "Image details"
	echo "    image: $(stat --printf '%N\n' $IMG)"
	echo "     size: $(stat -L --printf '%s bytes\n' $IMG)"
	echo " modified: $(stat -L --printf '%y\n' $IMG)"
	echo "     type: $(file -L -b $IMG)"
	echo ""
}

device_details() {
	DEV=$1
	BLOCK_SIZE=512

	info "Device details"
	echo "  device: $DEVICE"
	if [ -f "/sys/class/block/$DEV/device/vendor" ]; then
		echo "  vendor: $(cat /sys/class/block/$DEV/device/vendor)"
	else
		echo "  vendor: UNKOWN"
	fi
	if [ -f "/sys/class/block/$DEV/device/model" ]; then
		echo "   model: $(cat /sys/class/block/$DEV/device/model)"
	else
		echo "   model: UNKNOWN"
	fi
	if [ -f "/sys/class/block/$DEV/size" ]; then
		echo "    size: $(($(cat /sys/class/block/$DEV/size) * $BLOCK_SIZE)) bytes"
	else
		echo "    size: UNKNOWN"
	fi
	echo ""
}

unmount_device() {
	grep -q $DEVICE /proc/mounts
	if [ $? -eq 0 ]; then
		warn "$DEVICE listed in /proc/mounts, attempting to unmount"
		umount $DEVICE* 2>/dev/null
		! grep -q $DEVICE /proc/mounts && info "Unmounted successfully"
		return $?
	fi
	return 0
}

unmount() {
	if [ "$1" = "" ] ; then
		return 0
	fi
	grep -q $1 /proc/mounts
	if [ $? -eq 0 ]; then
		debug "Unmounting $1"
		umount $1
		! grep -q $1 /proc/mounts # check if unmounted successfully
		return $?
	fi
	return 0
}

#
# Parse and validate arguments
#
if [ "$1" != "-v" ] && [ $# -ne 2 ]; then
        usage
fi
if [ "$1" = "-v" ] && [ $# -ne 3 ]; then
        usage
fi

if [ "$1" = "-v" ] ; then
	DEBUG=1
	exec 3>&1
	shift
fi

HDDIMG=$1
DEVICE=$2

LINK=$(readlink $DEVICE)
if [ $? -eq 0 ]; then
	DEVICE="$LINK"
fi

if [ ! -w "$DEVICE" ]; then
	if [ ! -e "${DEVICE}" ] ; then
		die "Device $DEVICE cannot be found"
	else
		die "Device $DEVICE is not writable (need to run under sudo?)"
	fi
fi

if [ ! -e "$HDDIMG" ]; then
	die "HDDIMG $HDDIMG does not exist"
fi
HDDIMG_EXT=${HDDIMG##*.}
case $HDDIMG_EXT in
  hddimg)
     IMG_TYPE="MOUNT"
     IMG_COMPRESS="NO"
     debug "Detected: uncompressed image type .hddimg"
     ;;
  wic)
     IMG_TYPE="DISK"
     IMG_COMPRESS="NO"
     debug "Detected: uncompressed image type .wic"
     ;;
  xz)
     IMG_TYPE="DISK"
     IMG_COMPRESS="YES"
     debug "Detected: xz compressed image type .wic"
     command -v xz >/dev/null 2>&1 || { die "xz command is not available, pleaes install xz package"; }
     TMP_SIZE=`df -k $TMP_DIR | awk '/[0-9]%/{print $(NF-2)}'`
     if [ "$TMP_SIZE" -lt "$TMP_SIZE_MIN" ]; then
       die "Available space on $TMP_DIR must be at least $TMP_SIZE_MIN kB" 
     fi
     printf "Starting decompression of the image. It may take some time ..."
     xz --decompress --keep --format=auto --force --threads=0 --stdout > $TMP_DIR/TMP-AGL-wic-image.wic  $HDDIMG|| \
         die "xz command failled: xz --decompress --keep --format=auto --force --threads=0 --stdout > $TMP_DIR/TMP-AGL-wic-image.wic"
     HDDIMG="$TMP_DIR/TMP-AGL-wic-image.wic"
     echo "Image uncompressed, starting doing real work ..."
     ;;
  *)
     die "Unsupported image format: $HDDIMG_EXT Supported format are .hddimg .wic wic.xz"
     ;;
esac

#
# Ensure the hddimg is not mounted
#
debug "will now try to umount /detach previous images"
case $IMG_TYPE in
  MOUNT)
      unmount "$HDDIMG" || die "Failed to unmount $HDDIMG"
      ;;
  DISK)
      [ `losetup --list |grep $HDDIMG | wc -l ` -gt 1 ] && die "Image mounted more than once, manual cleaning required see: losetup --list"
      debug "ready to attach the wic image to aloop device"
      LOOP_DEVICE=`losetup --find --show $HDDIMG`  && ( losetup -d $LOOP_DEVICE  1>&3 2>&1 || die "Detaching $LOOP_DEVICE from $HDDIMG failled")
      ;;
  *)
      die "unknown image format $IMG_TYPE"
      ;;
esac

#
# Check if any $DEVICE partitions are mounted
#
unmount_device || die "Failed to unmount $DEVICE"

#
# Confirm device with user
#
image_details $HDDIMG
device_details $(basename $DEVICE)
echo -n "${INFO}Prepare EFI image on $DEVICE [y/N]?${CLEAR} "
read RESPONSE
if [ "$RESPONSE" != "y" ]; then
	die "Image creation aborted"
fi


#
# Prepare the temporary working space
#
TMPDIR=$(mktemp -d mkefidisk-XXX) || die "Failed to create temporary mounting directory."
HDDIMG_MNT=$TMPDIR/hddimg
HDDIMG_ROOTFS_MNT=$TMPDIR/hddimg_rootfs
ROOTFS_MNT=$TMPDIR/rootfs
BOOTFS_MNT=$TMPDIR/bootfs
mkdir $HDDIMG_MNT || die "Failed to create $HDDIMG_MNT"
mkdir $HDDIMG_ROOTFS_MNT || die "Failed to create $HDDIMG_ROOTFS_MNT"
mkdir $ROOTFS_MNT || die "Failed to create $ROOTFS_MNT"
mkdir $BOOTFS_MNT || die "Failed to create $BOOTFS_MNT"


#
# Partition $DEVICE
#
DEVICE_SIZE=$(parted -s $DEVICE unit mb print | grep ^Disk | cut -d" " -f 3 | sed -e "s/MB//")
# If the device size is not reported there may not be a valid label
if [ "$DEVICE_SIZE" = "" ] ; then
	parted -s $DEVICE mklabel msdos || die "Failed to create MSDOS partition table"
	DEVICE_SIZE=$(parted -s $DEVICE unit mb print | grep ^Disk | cut -d" " -f 3 | sed -e "s/MB//")
fi
ROOTFS_SIZE=$((DEVICE_SIZE-BOOT_SIZE))
ROOTFS_START=$((BOOT_SIZE))
ROOTFS_END=$((ROOTFS_START+ROOTFS_SIZE))

# MMC devices use a partition prefix character 'p'
PART_PREFIX=""
if [ ! "${DEVICE#/dev/mmcblk}" = "${DEVICE}" ] || [ ! "${DEVICE#/dev/nvme0n}" = "${DEVICE}" ] || [ ! "${DEVICE#/dev/loop}" = "${DEVICE}" ]; then
	PART_PREFIX="p"
fi
BOOTFS=$DEVICE${PART_PREFIX}1
ROOTFS=$DEVICE${PART_PREFIX}2

TARGET_PART_PREFIX=""
if [ ! "${TARGET_DEVICE#/dev/mmcblk}" = "${TARGET_DEVICE}" ] || [ ! "${TARGET_DEVICE#/dev/nvme0n}" = "${TARGET_DEVICE}" ]; then
	TARGET_PART_PREFIX="p"
fi
TARGET_ROOTFS=$TARGET_DEVICE${TARGET_PART_PREFIX}2

echo ""
info "Boot partition size:   $BOOT_SIZE MB ($BOOTFS)"
info "ROOTFS partition size: $ROOTFS_SIZE MB ($ROOTFS)"
echo ""

# Use MSDOS by default as GPT cannot be reliably distributed in disk image form
# as it requires the backup table to be on the last block of the device, which
# of course varies from device to device.

info "Partitioning installation media ($DEVICE)"

debug "Deleting partition table on $DEVICE"
dd if=/dev/zero of=$DEVICE bs=512 count=2 1>&3 2>&1 || die "Failed to zero beginning of $DEVICE"

debug "Creating new partition table (MSDOS) on $DEVICE"
parted -s $DEVICE mklabel msdos 1>&3 2>&1 || die "Failed to create MSDOS partition table"

debug "Creating boot partition on $BOOTFS"
parted -s $DEVICE mkpart primary 0% $BOOT_SIZE 1>&3 2>&1 || die "Failed to create BOOT partition"

debug "Enabling boot flag on $BOOTFS"
parted -s $DEVICE set 1 boot on 1>&3 2>&1 || die "Failed to enable boot flag"

debug "Creating ROOTFS partition on $ROOTFS"
parted -s $DEVICE mkpart primary $ROOTFS_START $ROOTFS_END 1>&3 2>&1 || die "Failed to create ROOTFS partition"

# as blkid does not provide PARTUUID on Ubuntu LTS 14.04 we myst hack via fdisk
#ROOTFS_PARTUUID=$(blkid |grep -e "$ROOTFS" |sed -n 's/^.*PARTUUID=/PARTUUID=/p')
export LC_ALL=C
ROOTFS_DISKID=$(fdisk -l "$DEVICE" | grep -e "Disk identifier" | sed -n 's/^.*Disk identifier: 0x/PARTUUID=/p')
if [ $ROOTFS_DISKID = "" ]; then
    die "Failed to read DISKID"
fi
BOOTFS_PARTUUID="$ROOTFS_DISKID-01"
ROOTFS_PARTUUID="$ROOTFS_DISKID-02"
debug "PARTUUID for ROOTFS is $ROOTFS_PARTUUID"

if [ $DEBUG -eq 1 ]; then
	parted -s $DEVICE print
fi


#
# Check if any $DEVICE partitions are mounted after partitioning
#
unmount_device || die "Failed to unmount $DEVICE partitions"


#
# Format $DEVICE partitions
#
info "Formatting partitions"
debug "Formatting $BOOTFS as vfat"
if [ ! "${DEVICE#/dev/loop}" = "${DEVICE}" ]; then
	mkfs.vfat -I $BOOTFS -n "EFI" 1>&3 2>&1 || die "Failed to format $BOOTFS"
else
	mkfs.vfat $BOOTFS -n "EFI" 1>&3 2>&1 || die "Failed to format $BOOTFS"
fi

debug "Formatting $ROOTFS as ext4"
mkfs.ext4 -F $ROOTFS -L "ROOT" 1>&3 2>&1 || die "Failed to format $ROOTFS"


#
# Mounting image file system on loop devices
#
case $IMG_TYPE in

  MOUNT) 
     debug "Mounting images and device in preparation for installation"
     mount -o loop $HDDIMG $HDDIMG_MNT 1>&3 2>&1 || die "Failed to mount $HDDIMG"
     mount -o loop $HDDIMG_MNT/rootfs.img $HDDIMG_ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount rootfs.img"
     ;;
  DISK)
     debug "Attaching image and mounting partitions then device in preparation for installation"
     LOOP_DEVICE=`losetup --find` || die "Failled to find an available loop device see: losetup --find"
     losetup -P $LOOP_DEVICE $HDDIMG  1>&3 2>&1 || die "Attaching $LOOP_DEVICE from $HDDIMG failled"
     mount "$LOOP_DEVICE"p2  $HDDIMG_ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount $LOOP_DEVICEp1 on $HDDIMG_ROOTFS_MNT"
     mount "$LOOP_DEVICE"p1  $HDDIMG_MNT       1>&3 2>&1 || die "Failed to mount $LOOP_DEVICEp2 on $HDDIMG_MNT"
     ;;
  *)
      die "unknown image format $IMG_TYPE"
      ;;
esac
# Mount removable device
     mount $ROOTFS $ROOTFS_MNT 1>&3 2>&1 || die "Failed to mount $ROOTFS on $ROOTFS_MNT"
     mount $BOOTFS $BOOTFS_MNT 1>&3 2>&1 || die "Failed to mount $BOOTFS on $BOOTFS_MNT"


info "Preparing boot partition"
EFIDIR="$BOOTFS_MNT/EFI/BOOT"
if [ -f $HDDIMG_MNT/vmlinuz ]; then
   cp $HDDIMG_MNT/vmlinuz $BOOTFS_MNT 1>&3 2>&1 || die "Failed to copy vmlinuz"
   KERNEL_TYPE="VMLINUZ"
   debug "kernel is vmlinuz"
fi
if [ -f $HDDIMG_MNT/bzimage ]; then
   cp $HDDIMG_MNT/bzimage $BOOTFS_MNT 1>&3 2>&1 || die "Failed to copy bzimage"
   KERNEL_TYPE="BZIMAGE"
   debug "kernel is bzimage"
fi
if [ -f $HDDIMG_MNT/microcode.cpio ]; then
   cp $HDDIMG_MNT/microcode.cpio $BOOTFS_MNT 1>&3 2>&1 || die "Failed to copy microcode.cpio"
   debug "microcode.cpio copied"
fi
[ -z $KERNEL_TYPE ] && die "Linux kernel type in $HDDIMG is unsupported"

if [ -f $HDDIMG_MNT/initrd ]
  then
    echo "initrd detected"
    cp $HDDIMG_MNT/initrd $BOOTFS_MNT 1>&3 2>&1 || die "Failed to copy initrd"
  else
    warn "initrd missing"
fi
echo "bootx64.efi" > $BOOTFS_MNT/startup.nsh || die "Failed to create startup.nsh"
# Copy the efi loader and configs (booti*.efi and grub.cfg if it exists)
cp -r $HDDIMG_MNT/EFI $BOOTFS_MNT 1>&3 2>&1 || die "Failed to copy EFI dir"
# Silently ignore a missing systemd-boot or gummiboot loader dir (we might just be a GRUB image)
cp -r $HDDIMG_MNT/loader $BOOTFS_MNT 1>&3 2>&1

# Update the boot loaders configurations for an installed image
# Remove any existing root= kernel parameters and:
# o Add a root= parameter with the target rootfs
# o Specify ro so fsck can be run during boot
# o Specify rootwait in case the target media is an asyncronous block device
#   such as MMC or USB disks
# o Specify "quiet" to minimize boot time when using slow serial consoles

# Look for a GRUB installation
GRUB_CFG="$EFIDIR/grub.cfg"
if [ -e "$GRUB_CFG" ]; then
	info "Configuring GRUB"
	# Delete the install entry
	sed -i "/menuentry 'install'/,/^}/d" $GRUB_CFG
	# Delete any LABEL= strings
	sed -i "s/ LABEL=[^ ]*/ /" $GRUB_CFG
        # detect config initrd=microcode.cpio
        if  grep -q microcode $GRUB_CFG; then
            warn "initrd=microcode.cpio detected, might not work on all boot configurations"
        fi
	sed -i "s@ root=[^ ]*@ @" $GRUB_CFG
	sed -i "s@vmlinuz @vmlinuz root=$ROOTFS_PARTUUID @" $GRUB_CFG
fi

# look for a systemd-boot loader.conf file and create a default boot entry
SYSTEMDBOOT_CFG="$BOOTFS_MNT/loader/loader.conf"
if [ -e "$SYSTEMDBOOT_CFG" ]; then
	info "Configuring SYSTEMD-BOOT"
    SYSTEMDBOOT_BOOT="$BOOTFS_MNT/loader/entries/boot.conf"
    SYSTEMDBOOT_DEBUG="$BOOTFS_MNT/loader/entries/debug.conf"
    # Delete the install entry
	sed -i "/menuentry 'install'/,/^}/d" $SYSTEMDBOOT_CFG
	rm -rf "$BOOTFS_MNT/loader/entries/install.conf" 1>&3 2>&1
	# Add PARTUUID to the boot entry file
	if [ ! -e "$SYSTEMDBOOT_BOOT" ]; then
        die "no boot.conf entry found in systemd-boot directories"
    fi
	# Delete any LABEL= strings
	sed -i "s/ LABEL=[^ ]*/ /" $SYSTEMDBOOT_BOOT
        # detect config initrd=microcode.cpio
        if  grep -q microcode $SYSTEMDBOOT_BOOT; then
            warn "initrd=microcode.cpio detected, might not work on all boot configurations"
        fi
	sed -i "s@ root=[^ ]*@ @" $SYSTEMDBOOT_BOOT
	sed -i "s@options @options root=$ROOTFS_PARTUUID @" $SYSTEMDBOOT_BOOT
fi


# Ensure we have at least one EFI bootloader configured
if [ ! -e $GRUB_CFG ]  && [ ! -e $SYSTEMDBOOT_CFG ] ; then
	die "No EFI bootloader configuration found"
fi

printf "Copying ROOTFS files ... "
command -v rsync >/dev/null 2>&1 # check if rsync exists
if [ $DEBUG -eq 1 ] && [ $? -eq 0 ]; then
	rsync --info=progress2 -h -aHAXW --no-compress  $HDDIMG_ROOTFS_MNT/* $ROOTFS_MNT 1>&3 2>&1 || die "Root FS copy failed"
else
	cp -a $HDDIMG_ROOTFS_MNT/* $ROOTFS_MNT 1>&3 2>&1 || die "Root FS copy failed"
fi
debug "removing any swap entry in /etc/fstab"
sed --in-place '/swap/d' $ROOTFS_MNT/etc/fstab 
debug "fixing PARTUUID for /boot"
sed --in-place -e "s#PARTUUID=[0-9a-z-]\+\t/boot#${BOOTFS_PARTUUID}\t/boot#" $ROOTFS_MNT/etc/fstab

printf "flushing data on removable device. May take a while ... "
sync --file-system $ROOTFS_MNT
echo done

# We dont want udev to mount our root device while we're booting...
if [ -d $ROOTFS_MNT/etc/udev/ ] ; then
	echo "$TARGET_DEVICE" >> $ROOTFS_MNT/etc/udev/mount.blacklist
fi


# Call cleanup to unmount devices and images and remove the TMPDIR
cleanup

echo ""
if [ $WARNINGS -ne 0 ] && [ $ERRORS -eq 0 ]; then
	echo "${YELLOW}Installation completed with warnings${CLEAR}"
	echo "${YELLOW}Warnings: $WARNINGS${CLEAR}"
elif [ $ERRORS -ne 0 ]; then
	echo "${RED}Installation encountered errors${CLEAR}"
	echo "${RED}Errors: $ERRORS${CLEAR}"
	echo "${YELLOW}Warnings: $WARNINGS${CLEAR}"
else
	success "Installation completed successfully"
fi
echo ""