#!/bin/sh # Copyright (c) 2015-2019 Contributors as noted in the AUTHORS file # # This file is part of Solo5, a sandboxed execution environment. # # Permission to use, copy, modify, and/or distribute this software # for any purpose with or without fee is hereby granted, provided # that the above copyright notice and this permission notice appear # in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # solo5-virtio-mkimage: Builds an MBR-partitioned disk image with SYSLINUX and # a virtio unikernel in it. The image file is sparse, sized to 1GB (or modify # $SIZE below). # # Requirements: A Linux system with sfdisk, mtools and syslinux installed. # Path names for syslinux library files are based on those for Debian/Alpine, # YMMV. # usage () { cat <&2 usage: solo5-virtio-mkimage [ OPTIONS ] [ -- ] IMAGE UNIKERNEL [ ARGUMENTS ... ] Builds an MBR-partitioned disk IMAGE with a boot loader and UNIKERNEL in it. ARGUMENTS, if specified, will be embedded in the image and passed to the unikernel at boot time. UNIKERNEL must be a unikernel built for the 'virtio' target. Options: -d: Run in a Docker container (using mato/solo5-virtio-mkimage). -f FORMAT: Output a 'raw' disk image (default) or a 'tar' file suitable for uploading to Google Compute Engine. EOM exit 1 } die () { echo solo5-virtio-mkimage: error: "$@" 1>&2 if [ -n "${TMPDIR}" -a -f "${TMPDIR}"/log ]; then echo For more information, see ${TMPDIR}/log PRESERVE_TMPDIR=1 fi exit 1 } nuketmpdir () { [ -n "${PRESERVE_TMPDIR}" ] && return [ -z "${TMPDIR}" ] && return [ ! -d "${TMPDIR}" ] && return rm -rf ${TMPDIR} } maybefind () { for f in "$@"; do [ -f $f ] && echo $f && return 0 done return 1 } # Parse command line arguments. ARGS=$(getopt df: $*) [ $? -ne 0 ] && usage set -- $ARGS RUN_IN_DOCKER= FORMAT=raw while true; do case "$1" in -d) RUN_IN_DOCKER=yes shift ;; -f) case "$2" in tar|raw) FORMAT="$2" shift; shift ;; *) usage ;; esac ;; --) shift; break ;; esac done [ $# -lt 2 ] && usage IMAGE=$(readlink -f $1) shift UNIKERNEL=$(readlink -f $1) shift [ ! -f ${UNIKERNEL} ] && die "not found: ${UNIKERNEL}" SIZE=1G if [ "$(uname -s)" != "Linux" -a -z "${RUN_IN_DOCKER}" ]; then die "Sorry, this tool only runs on Linux, or in Docker with the -d option." fi if [ -n "${RUN_IN_DOCKER}" ]; then # User wants to run us in a container. Figure out the destination and source # directories (containing IMAGE and UNIKERNEL) to bind-mount from the host. DESTDIR=$(dirname ${IMAGE}) 2>/dev/null [ ! -d ${DESTDIR} ] && die "can't get parent directory of: ${IMAGE}" SRCDIR=$(dirname ${UNIKERNEL}) [ ! -d ${SRCDIR} ] && die "can't get parent directory of: ${UNIKERNEL}" DESTFILE=$(basename ${IMAGE}) SRCFILE=$(basename ${UNIKERNEL}) # --tmpfs is only supported from 1.10.0 onwards if docker run --help | grep -q tmpfs; then TMPFS="--tmpfs /tmp:rw,noexec,nosuid,size=2g" else TMPFS="" fi exec docker run --rm \ --user $(id -u) \ ${TMPFS} \ -v ${SRCDIR}:/host/src -v ${DESTDIR}:/host/dest \ mato/solo5-virtio-mkimage -f ${FORMAT} \ -- \ /host/dest/${DESTFILE} /host/src/${SRCFILE} "$@" fi # Check dependencies. type sfdisk >/dev/null 2>/dev/null || die "need sfdisk installed" type syslinux >/dev/null 2>/dev/null || die "need syslinux installed" type mtools >/dev/null 2>/dev/null || die "need mtools installed" type mkdosfs >/dev/null 2>/dev/null || die "need dosfstools installed" SYSLINUX_MBR=$(maybefind /usr/lib/syslinux/mbr/mbr.bin \ /usr/share/syslinux/mbr.bin \ /usr/lib/syslinux/bios/mbr.bin) || die "can't find syslinux mbr.bin" SYSLINUX_COM32=$(maybefind /usr/lib/syslinux/modules/bios/libcom32.c32 \ /usr/share/syslinux/libcom32.c32 \ /usr/lib/syslinux/bios/libcom32.c32) || die "can't find syslinux libcom32.c32" SYSLINUX_MBOOT=$(maybefind /usr/lib/syslinux/modules/bios/mboot.c32 \ /usr/share/syslinux/mboot.c32 \ /usr/lib/syslinux/bios/mboot.c32) || die "can't find syslinux mboot.c32" trap nuketmpdir 0 INT TERM TMPDIR=$(mktemp -d) if [ $? -ne 0 ]; then echo "error creating temporary directory" 1>&2 exit 1 fi LOG=${TMPDIR}/log # Write SYSLINUX MBR to image and extend to desired size (sparse) cp ${SYSLINUX_MBR} ${TMPDIR}/image.mbr || die "could not copy mbr" truncate -s ${SIZE} ${TMPDIR}/image.mbr # Create DOS (FAT32) primary partition # sfdisk no longer supports -D echo ",,0xc,*" | sfdisk ${TMPDIR}/image.mbr >${LOG} 2>&1 || die "sfdisk failed" # Start offset of partition (sectors) O_SECTORS=$(sfdisk -d ${TMPDIR}/image.mbr | awk -- '/(Id= |type=)c/{print $4}' \ | sed -e s/,//) [ -n "${O_SECTORS}" ] || die "internal error, can't parse sfdisk" # Size of partition (sectors) S_SECTORS=$(sfdisk -d ${TMPDIR}/image.mbr | awk -- '/(Id= |type=)c/{print $6}' \ | sed -e s/,//) [ -n "${S_SECTORS}" ] || die "internal error, can't parse sfdisk" # Start offset of partition (bytes) O_BYTES=$(expr ${O_SECTORS} \* 512) # Size of partition (1k blocks, which mkdosfs expects) S_BLOCKS=$(expr ${S_SECTORS} / 2) # Extract partition from image dd if=${TMPDIR}/image.mbr of=${TMPDIR}/image.dos bs=512 skip=${O_SECTORS} \ conv=sparse 2>/dev/null || die "dd failed" # Truncate image.mbr to contain only the MBR + padding before partition start truncate -s ${O_BYTES} ${TMPDIR}/image.mbr # Create FAT32 filesystem, install SYSLINUX mkdosfs -F 32 ${TMPDIR}/image.dos ${S_BLOCKS} >${LOG} 2>&1 \ || die "mkfs.msdos failed" syslinux --install ${TMPDIR}/image.dos cat <${TMPDIR}/syslinux.cfg SERIAL 0 115200 DEFAULT unikernel LABEL unikernel KERNEL mboot.c32 APPEND -aout unikernel.bin $@ EOM # Ugh, mtools complains about filesystem size not being a multiple of # what it thinks the sectors-per-track are, ignore. echo mtools_skip_check=1 > ${TMPDIR}/mtoolsrc export MTOOLSRC=${TMPDIR}/mtoolsrc # Populate filesystem mcopy -i ${TMPDIR}/image.dos ${TMPDIR}/syslinux.cfg ::syslinux.cfg \ || die "copy failed" mcopy -i ${TMPDIR}/image.dos ${SYSLINUX_COM32} ::libcom32.c32 \ || die "copy failed" mcopy -i ${TMPDIR}/image.dos ${SYSLINUX_MBOOT} ::mboot.c32 \ || die "copy failed" mcopy -i ${TMPDIR}/image.dos ${UNIKERNEL} ::unikernel.bin \ || die "copy failed" # Construct final image (MBR + padding + filesystem) if [ "${FORMAT}" = "raw" ]; then cat ${TMPDIR}/image.mbr ${TMPDIR}/image.dos \ | dd of=${IMAGE} conv=sparse 2>/dev/null || die "dd failed" else cat ${TMPDIR}/image.mbr ${TMPDIR}/image.dos \ | dd of=${TMPDIR}/disk.raw conv=sparse 2>/dev/null || die "dd failed" (cd ${TMPDIR} && tar -Sczf ${IMAGE} disk.raw) || die "tar failed" fi nuketmpdir