#!/bin/bash # $Id: usbimg2disk.sh,v 1.23 2012/09/03 20:52:31 eha Exp eha $ # # Copyright 2009, 2010, 2011, 2012 Eric Hameleers, Eindhoven, NL # All rights reserved. # # Redistribution and use of this script, with or without modification, is # permitted provided that the following conditions are met: # # 1. Redistributions of this script must retain the above copyright # notice, this list of conditions and the following disclaimer. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Define some variables ahead of time, so that cleanup knows about them: MNTDIR1="" MNTDIR2="" MNTDIR3="" # Clean up in case of failure: cleanup() { # Clean up by unmounting our loopmounts, deleting tempfiles: echo "--- Syncing I/O..." sync echo "--- Unmounting volumes and deleting temporary files..." [ ! -z "${MNTDIR1}" ] && ( umount -f ${MNTDIR1} ; rmdir $MNTDIR1 ) [ ! -z "${MNTDIR2}" ] && ( umount -f ${MNTDIR2} ; rmdir $MNTDIR2 ) [ ! -z "${MNTDIR3}" ] && rm -rf ${MNTDIR3} || true } showhelp() { echo "# " echo "# Purpose #1: to use the content of Slackware's usbboot.img and" echo "# transform a standard USB thumb drive with a single vfat partition" echo "# into a bootable medium containing the Slackware Linux installer." echo "# " echo "# Purpose #2: to use the contents of a Slackware directory tree" echo "# and transform a standard USB thumb drive with" echo "# a single vfat partition and 2GB of free space into" echo "# a self-contained USB installation medium for Slackware Linux." echo "# " echo "# " echo "# Your USB thumb drive may contain data!" echo "# This data will *not* be overwritten, unless you have" echo "# explicitly chosen to format the drive by using the '-f' parameter." echo "# " echo "# $(basename $0) accepts the following parameters:" echo "# -h|--help This help" echo "# -e|--errors Abort operations in case of any errors" echo "# -f|--format Format the USB drive before use" echo "# -i|--infile <filename> Full path to the usbboot.img file" echo "# -l|--logfile <filename> Optional logfile to catch fdisk output" echo "# -o|--outdev <filename> The device name of your USB drive" echo "# -s|--slackdir <dir> Use 'dir' as the root of Slackware tree" echo "# -u|--unattended Do not ask any questions" echo "# -L|--label <labelname> FAT label when formatting the USB drive" echo "# " echo "# Examples:" echo "# " echo "# $(basename $0) -i ~/download/usbboot.img -o /dev/sdX" echo "# $(basename $0) -f -s /home/ftp/pub/slackware-13.0 -o /dev/sdX" echo "# " echo "# The second example shows how to create a fully functional Slackware" echo "# installer on a USB stick (it needs a Slackware tree as the source)." echo "# " } reformat() { # Commands to re-create a functional USB stick with VFAT partition: # two parameters: # (1) the name of the USB device to be formatted: # (2) FAT label to use when formatting the USB device: local TOWIPE="$1" local THELABEL="$2" # Sanity checks: if [ ! -b $TOWIPE ]; then echo "*** Not a block device: '$TOWIPE' !" exit 1 fi # Wipe the MBR: dd if=/dev/zero of=$TOWIPE bs=512 count=1 # create a FAT32 partition (type 'b') /sbin/fdisk $TOWIPE <<EOF n p 1 t b w EOF # Check if fdisk gave an error (like "error closing file"). # Some desktop environments auto-mount the partition on sight...: if [ $? -ne 0 ]; then echo "*** The fdisk command had an error." echo "*** Some desktop environments (KDE, GNOME) may automatically mount" echo "*** the new FAT partition on your USB drive, causing the fdisk error." if [ $UNATTENDED -eq 0 ]; then # if we are running interactively, allow to chicken out now: echo "*** Perhaps you want to format the device '$TOWIPE' yourself first?" read -p "*** Press ENTER to continue anyway or Ctrl-C to quit now: " JUNK fi fi if mount | grep -q ${TOWIPE}1 ; then echo "--- Un-mounting ${TOWIPE}1 because your desktop auto-mounted it..." umount -f ${TOWIPE}1 fi # We set the fat label to '$THELABEL' when formatting. # It will enable the installer to mount the fat partition automatically # and pre-fill the correct pathname for the "SOURCE" dialog. # Format with a vfat filesystem: /sbin/mkdosfs -F32 -n "${THELABEL}" ${TOWIPE}1 } makebootable() { # Only parameter: the name of the USB device to be set bootable: USBDRV="$1" # Sanity checks: if [ ! -b $USBDRV ]; then echo "*** Not a block device: '$USBDRV' !" exit 1 fi # Set the bootable flag for the first partition: /sbin/sfdisk $USBDRV -A 1 -N1 } # Parse the commandline parameters: if [ -z "$1" ]; then showhelp exit 1 fi while [ ! -z "$1" ]; do case $1 in -e|--errors) ABORT_ON_ERROR=1 shift ;; -f|--format) REFORMAT=1 shift ;; -h|--help) showhelp exit ;; -i|--infile) USBIMG="$(cd $(dirname $2); pwd)/$(basename $2)" shift 2 ;; -l|--logfile) LOGFILE="$(cd $(dirname $2); pwd)/$(basename $2)" shift 2 ;; -o|--outdev) TARGET="$2" TARGETPART="${TARGET}1" shift 2 ;; -s|--slackdir) REPODIR="$2" FULLINSTALLER="yes" shift 2 ;; -u|--unattended) UNATTENDED=1 shift ;; -L|--label) CUSTOMLABEL="$2" shift 2 ;; *) echo "*** Unknown parameter '$1'!" exit 1 ;; esac done if [ "$ABORT_ON_ERROR" = "1" ]; then set -e trap 'echo "*** $0 FAILED at line $LINENO ***"; cleanup; exit 1' ERR INT TERM # trap and abort on any error else trap 'echo "*** Ctrl-C caught -- aborting operations ***"; cleanup; exit 1' 2 14 15 # trap Ctrl-C and kill fi # Before we start: [ -x /bin/id ] && CMD_ID="/bin/id" || CMD_ID="/usr/bin/id" if [ "$($CMD_ID -u)" != "0" ]; then echo "*** You need to be root to run $(basename $0)." exit 1 fi # Check existence of the package repository if that was passed as a parameter: if [ -n "$REPODIR" ]; then if [ ! -d "$REPODIR" ]; then echo "*** This is not a directory: '$REPODIR' !" exit 1 else # This also takes care of stripping a trailing '/', which is required # for the rsync command to work as intended: REPOSROOT="$(cd $(dirname $REPODIR); pwd)/$(basename $REPODIR)" fi fi # Check FAT label: if [ -n "${CUSTOMLABEL}" ]; then if [ "x$(echo "${CUSTOMLABEL}"| tr -d '[:alnum:]._-')" != "x" ]; then echo "*** FAT label '${CUSTOMLABEL}' is not an acceptible name!" exit 1 elif [ ${#CUSTOMLABEL} -gt 11 ]; then echo "*** FAT label '${CUSTOMLABEL}' must be less than 12 characters!" exit 1 else FATLABEL="${CUSTOMLABEL}" fi else FATLABEL="USBSLACKINS" fi # Prepare the environment: MININSFREE=2200 # minimum in MB required for a Slackware tree UNATTENDED=${UNATTENDED:-0} # unattended means: never ask questions. REFORMAT=${REFORMAT:-0} # do not try to reformat by default LOGFILE=${LOGFILE:-/dev/null} # silence by default EXCLUDES=${EXCLUDES:-"--exclude=source \ --exclude=extra/aspell-word-lists \ --exclude=isolinux \ --exclude=usb-and-pxe-installers \ --exclude=pasture"} # not copied onto the stick # If we have been given a Slackware tree, we will create a full installer: if [ -n "$REPOSROOT" ]; then if [ -d "$REPOSROOT" -a -f "$REPOSROOT/PACKAGES.TXT" ]; then USBIMG=${USBIMG:-"$REPOSROOT/usb-and-pxe-installers/usbboot.img"} PKGDIR=$(head -40 $REPOSROOT/PACKAGES.TXT | grep 'PACKAGE LOCATION: ' |head -1 |cut -f2 -d/) if [ -z "$PKGDIR" ]; then echo "*** Could not find the package subdirectory in '$REPOSROOT'!" exit 1 fi else echo "*** Directory '$REPOSROOT' does not look like a Slackware tree!" exit 1 fi fi # More sanity checks: if [ -z "$TARGET" -o -z "$USBIMG" ]; then echo "*** You must specify both the names of usbboot.img and the USB device!" exit 1 fi if [ ! -f $USBIMG ]; then echo "*** This is not a useable file: '$USBIMG' !" exit 1 fi if [ ! -b $TARGET ]; then echo "*** Not a block device: '$TARGET' !" exit 1 elif [ $REFORMAT -eq 0 ]; then if ! /sbin/blkid -t TYPE=vfat $TARGETPART 1>/dev/null 2>/dev/null ; then echo "*** I fail to find a 'vfat' partition: '$TARGETPART' !" echo "*** If you want to format the USB thumb drive, add the '-f' parameter" exit 1 fi fi if mount | grep -q $TARGETPART ; then echo "*** Please un-mount $TARGETPART first, then re-run this script!" exit 1 fi # Exclude all dangling symlinks from the rsync to avoid errors and/or # rsync refusing to delete files. Such links may be present in a partial # Slackware tree with the sources removed. if [ -d "$REPOSROOT" ] ; then pushd $REPOSROOT for link in $(find * -type l ) ; do if [ ! $(readlink -f $link ) ]; then EXCLUDES="${EXCLUDES} --exclude=$link" fi done popd fi # Check for prerequisites which may not always be installed: MISSBIN=0 MBRBIN="/usr/lib/syslinux/mbr.bin" if [ ! -r $MBRBIN ]; then MBRBIN="/usr/share/syslinux/mbr.bin"; fi if [ ! -r $MBRBIN -o ! -x /usr/bin/syslinux ]; then echo "*** This script requires that the 'syslinux' package is installed!" MISSBIN=1 fi if [ ! -x /usr/bin/mtools ]; then echo "*** This script requires that the 'floppy' (mtools) package is installed!" MISSBIN=1 fi if [ ! -x /sbin/mkdosfs ]; then echo "*** This script requires that the 'dosfstools' package is installed!" MISSBIN=1 fi if [ ! -x /bin/cpio ]; then echo "*** This script requires that the 'cpio' package is installed!" MISSBIN=1 fi if [ $MISSBIN -eq 1 ]; then exit 1 ; fi # Show the USB device's information to the user: if [ $UNATTENDED -eq 0 ]; then [ $REFORMAT -eq 1 ] && DOFRMT="format and " || DOFRMT="" echo "" echo "# We are going to ${DOFRMT}use this device - '$TARGET':" echo "# Vendor : $(cat /sys/block/$(basename $TARGET)/device/vendor)" echo "# Model : $(cat /sys/block/$(basename $TARGET)/device/model)" echo "# Size : $(( $(cat /sys/block/$(basename $TARGET)/size) / 2048)) MB" echo "# " echo "# FDISK OUTPUT:" /sbin/fdisk -l $TARGET | while read LINE ; do echo "# $LINE" ; done echo "" echo "*** ***" echo "*** If this is the wrong drive, then press CONTROL-C now! ***" echo "*** ***" read -p "Or press ENTER to continue: " JUNK # OK... the user was sure about the drive... fi # Initialize the logfile: cat /dev/null > $LOGFILE # If we need to format the USB drive, do it now: if [ $REFORMAT -eq 1 ]; then echo "--- Formatting $TARGET with VFAT partition label '${FATLABEL}'..." if [ $UNATTENDED -eq 0 ]; then echo "--- Last chance! Press CTRL-C to abort!" read -p "Or press ENTER to continue: " JUNK fi ( reformat $TARGET "${FATLABEL}" ) 1>>$LOGFILE 2>&1 else # We do not format the drive, but apply a FAT label if required. # Prepare for using mlabel to change the FAT label: MTOOLSRCFILE=$(mktemp -p /tmp -t mtoolsrc.XXXXXX) echo "drive s: file=\"$TARGETPART\"" > $MTOOLSRCFILE echo "mtools_skip_check=1" >> $MTOOLSRCFILE if [ -n "$CUSTOMLABEL" ]; then # User gave us a FAT label to use, so we will force that upon the drive: echo "--- Setting FAT partition label to '$FATLABEL'" MTOOLSRC=$MTOOLSRCFILE mlabel s:${FATLABEL} elif [ -n "$(/sbin/blkid -t TYPE=vfat -s LABEL -o value $TARGETPART)" ] ; then # User did not care, but the USB stick has a FAT label that we will use: FATLABEL="$(/sbin/blkid -t TYPE=vfat -s LABEL -o value $TARGETPART)" echo "--- Using current FAT partition label '$FATLABEL'" else # No user-supplied label, nor a drive label present. We apply our default: echo "--- Setting FAT partition label to '$FATLABEL'" MTOOLSRC=$MTOOLSRCFILE mlabel s:${FATLABEL} fi # Cleanup: rm -f $MTOOLSRCFILE fi # Create a temporary mount point for the image file: mkdir -p /mnt MNTDIR1=$(mktemp -d -p /mnt -t img.XXXXXX) if [ ! -d $MNTDIR1 ]; then echo "*** Failed to create a temporary mount point for the image!" exit 1 else chmod 711 $MNTDIR1 fi # Create a temporary mount point for the USB thumb drive partition: MNTDIR2=$(mktemp -d -p /mnt -t usb.XXXXXX) if [ ! -d $MNTDIR2 ]; then echo "*** Failed to create a temporary mount point for the usb thumb drive!" exit 1 else chmod 711 $MNTDIR2 fi # Create a temporary directory to extract the initrd if needed: MNTDIR3=$(mktemp -d -p /mnt -t initrd.XXXXXX) if [ ! -d $MNTDIR3 ]; then echo "*** Failed to create a temporary directory to extract the initrd!" exit 1 else chmod 711 $MNTDIR3 fi # Mount the image file: mount -o loop,ro $USBIMG $MNTDIR1 # Mount the vfat partition: mount -t vfat -o shortname=mixed $TARGETPART $MNTDIR2 # Do we have space to create a full Slackware USB install medium? if [ "$FULLINSTALLER" = "yes" ]; then if [ $(df --block=1MB $TARGETPART |grep "^$TARGETPART" |tr -s ' ' |cut -f4 -d' ') -le $MININSFREE ]; then echo "*** The partition '$TARGETPART' does not have enough" echo "*** free space (${MININSFREE} MB) to create a Slackware installation medium!" cleanup exit 1 fi fi # Check available space for a Slackware USB setup bootdisk: USBFREE=$(df -k $TARGETPART |grep "^$TARGETPART" |tr -s ' ' |cut -d' ' -f4) IMGSIZE=$(du -k $USBIMG |cut -f1) echo "--- Available free space on the the USB drive is $USBFREE KB" echo "--- Required free space for installer: $IMGSIZE KB" # Exit when the installer image's size does not fit in available space: if [ $IMGSIZE -gt $USBFREE ]; then echo "*** The USB thumb drive does not have enough free space!" # Cleanup and exit: cleanup exit 1 fi if [ $UNATTENDED -eq 0 ]; then # if we are running interactively, warn about overwriting files: if [ -n "$REPOSROOT" ]; then if [ -d $MNTDIR2/syslinux -o -d $MNTDIR2/$(basename $REPOSROOT) ]; then echo "--- Your USB drive contains directories 'syslinux' and/or '$(basename $REPOSROOT)'" echo "--- These will be overwritten. Press CTRL-C to abort now!" read -p "Or press ENTER to continue: " JUNK fi else if [ -d $MNTDIR2/syslinux ]; then echo "--- Your USB drive contains directory 'syslinux'" echo "--- This will be overwritten. Press CTRL-C to abort now!" read -p "Or press ENTER to continue: " JUNK fi fi fi # Copy boot image files to the USB disk: echo "--- Copying boot files to the USB drive..." cp -R $MNTDIR1/* $MNTDIR2 # If we are creating a full Slackware installer, copy the package tree: if [ "$FULLINSTALLER" = "yes" ]; then # Copy Slackware package tree (no sources) to the USB disk - # we already made sure that ${REPOSROOT} does not end with a '/' echo "--- Copying Slackware package tree to the USB drive..." rsync -rpthDL --delete $EXCLUDES $REPOSROOT $MNTDIR2/ fi # Unmount/remove stuff: cleanup # Run syslinux and write a good MBR: echo "--- Making the USB drive '$TARGET' bootable..." ( makebootable $TARGET ) 1>>$LOGFILE 2>&1 /usr/bin/syslinux -s $TARGETPART 1>>$LOGFILE 2>&1 dd if=$MBRBIN of=$TARGET 1>>$LOGFILE 2>&1 echo "--- Done." # THE END