Skip to content

Instantly share code, notes, and snippets.

@anthonygclark
Created September 18, 2017 03:38
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save anthonygclark/444f7c569c7b414c36c7c05714ea4a1e to your computer and use it in GitHub Desktop.
Save anthonygclark/444f7c569c7b414c36c7c05714ea4a1e to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# Creates a multi-partition QEMU disk image from our buildroot outputs and embeds grub2
#
# TODO: Better error handling
# TODO: Better args (grub.cfg path is assumed)
#
DEBUG=1
IMAGE_NAME="dhc.img"
IMAGE_SIZE="1G" # 100M used for /boot
function log_()
{
echo "[$(basename "$0")] $*"
}
(( DEBUG )) && {
KERNEL="${1:-./initrd-buildroot-2017.08/output/images/bzImage}"
log_ "Debug: Using KERNEL = $KERNEL"
INITRD="${2:-./initrd-buildroot-2017.08/output/images/rootfs.cpio.gz}"
log_ "Debug: Using INITRD = $INITRD"
ROOTFS="${3:-./rootfs-buildroot-2017.08/output/images/rootfs.tar.bz2}"
log_ "Debug: Using ROOTFS = $ROOTFS"
}
[[ -e $KERNEL ]] || {
log_ "$KERNEL: File not found"
exit 1
}
[[ -e $INITRD ]] || {
log_ "$INITRD: File not found"
exit 1
}
[[ -e $ROOTFS ]] || {
log_ "$ROOTFS: File not found"
exit 1
}
[[ -e ./grub.cfg ]] || {
log_ "./grub.cfg: File not found"
exit 1
}
function atexit()
{
[[ -d $MOUNT2 ]] && sudo umount "$MOUNT2" &> /dev/null
[[ -d $MOUNT1 ]] && sudo umount "$MOUNT1" &> /dev/null
sudo kpartx -d "$LOOPBACK" &> /dev/null
sudo losetup -d "$LOOPBACK" &> /dev/null
sudo qemu-nbd -d "$NBD" &> /dev/null
}
function error()
{
log_ "Error: $*"
atexit
exit 1
}
function check_commands()
{
for comm in qemu-img qemu-nbd losetup kpartx parted mkfs.ext2 mkfs.ext4 grub-install ;
do
command -v "$comm" &> /dev/null || {
log_ "Command '$comm' is not available"
exit 1
}
done
}
function find_next_nbd()
{
for dev in /sys/class/block/nbd*;
do
local size
size="$(cat "$dev"/size)"
if (( size == 0 ));
then
printf "%s" "/dev/nbd${dev: -1}"
return
fi
done
error "No available nbd devices"
}
function find_next_loopback()
{
sudo losetup -f &> /dev/null || error "No available loopback devices"
printf "%s" "$(sudo losetup -f)"
}
#####
if [[ -e "$IMAGE_NAME" ]] ; then
error "File '$IMAGE_NAME' already exists. Exiting to avoid overwriting!"
fi
# init
check_commands
sudo -v || exit
atexit
sudo modprobe nbd max_part=16
# vars
NBD="$(find_next_nbd)"
LOOPBACK="$(find_next_loopback)"
LOOPBACK_N="${LOOPBACK: -1}" # the space before -1 is very important
MAPPER1="/dev/mapper/loop${LOOPBACK_N}p1"
MAPPER2="/dev/mapper/loop${LOOPBACK_N}p2"
(( DEBUG )) && {
log_ "Debug: NBD=$NBD"
log_ "Debug: LOOPBACK=$LOOPBACK"
log_ "Debug: LOOPBACK_N=$LOOPBACK_N"
log_ "Debug: MAPPER2=$MAPPER1"
log_ "Debug: MAPPER2=$MAPPER1"
}
export NBD
export LOOPBACK
export MAPPER1
export MAPPER2
#####
qemu-img create -f qcow2 "$IMAGE_NAME" "$IMAGE_SIZE" || error "create image"
log_ "[+] Created image"
sudo qemu-nbd -c "$NBD" "$IMAGE_NAME" || error "mount nbd"
log_ "[+] Bound to $NBD"
sudo parted --script "$NBD" \
mklabel msdos \
mkpart primary ext2 1M 100M \
mkpart primary ext4 100M "$IMAGE_SIZE" || error "format"
log_ "[+] Partitioned via parted"
sleep 3
sudo losetup "$LOOPBACK" "$NBD" || error "losetup"
log_ "[+] losetup on $LOOPBACK"
sleep 3
sudo kpartx -a "$LOOPBACK" || error "kpartx"
sleep 3
[[ -e "$MAPPER1" ]] || error "mapper 1 doesnt exist"
[[ -e "$MAPPER2" ]] || error "mapper 2 doesnt exist"
log_ "[+] kpartx"
MOUNT1=$(mktemp -d)
MOUNT2="$MOUNT1/boot"
export MOUNT1
export MOUNT2
log_ "[+] Created mount dirs ($MOUNT1) ($MOUNT2)"
log_ "[...] Starting format..."
sudo mkfs.ext2 "$MAPPER1" || error "mkfs.ext2"
sudo mkfs.ext4 "$MAPPER2" || error "mkfs.ext4"
log_ "[+] Formatted in ext2 + ext4..."
sudo mount "$MAPPER2" "$MOUNT1" || error "Failed to mount ext4 partition"
log_ "[+] Mounted $MAPPER2 to $MOUNT1"
sudo mkdir -p "$MOUNT2" # /boot not not exist
sudo mount "$MAPPER1" "$MOUNT2" || error "Failed to mount ext2 partition"
log_ "[+] Mounted $MAPPER1 to $MOUNT2"
log_ "[+] Mounted loopback devices"
sudo cp "$KERNEL" "$MOUNT2" || error "Failed to copy kernel"
log_ "[+] Copied kernel"
sudo cp "$INITRD" "$MOUNT2/initrd.cpio.gz" || error "Failed to copy initrd"
log_ "[+] Copied initrd"
sudo tar -xjf "$ROOTFS" -C "$MOUNT1" || error "Failed to extract rootfs"
log_ "[+] Copied rootfs"
sudo grub-install --target=i386-pc --boot-directory="$(readlink -f "$MOUNT2")" "$LOOPBACK" || error "Failed to install grub"
log_ "[+] Grub installed"
sudo cp ./grub.cfg "$MOUNT2/grub/grub.cfg" || error "Failed to write grub config"
sudo umount "$MOUNT2" || error "Failed to unmount loop2"
log_ "[+] Unmounted MOUNT2"
sudo umount "$MOUNT1" || error "Failed to unmount loop1"
log_ "[+] Unmounted MOUNT1"
rm -r "$MOUNT1" # remove this dir as we no longer need it
atexit
exit 0
@stsp
Copy link

stsp commented Jun 18, 2019

At line 120:

    log_ "Debug: MAPPER2=$MAPPER1"
    log_ "Debug: MAPPER2=$MAPPER1"

Numbers are swapped.
Great script btw.

@stsp
Copy link

stsp commented Jun 18, 2019

At line 112:

LOOPBACK_N="${LOOPBACK: -1}" # the space before -1 is very important

Should be replaced with:

LOOPBACK_N="${LOOPBACK//[!0-9]/}"

... to support loop devices started from 10 and above.
Cutting only one digit doesn't work.

Copy link

ghost commented Jul 15, 2019

Excellent script, immensely helpful in resolving my issues building disk images.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment