#!/bin/bash # This helps to see what is going on set -e PROG_REQ=( kpartx mkfs.ext4 losetup sgdisk readlink awk ) SLACK=128 function check_depends() { for ((i=0; i<${#PROG_REQ[@]}; i++)) do if ! command -v ${PROG_REQ[i]} > /dev/null then echo "Please install the needed program: ${PROG_REQ[i]}." exit 1 fi done } function add_rootfs() { local dev=${LOAD_CMD%:*} dev=${dev##* } local par=${LOAD_CMD#*:} local j=$1 if [ -z "$dev" ] || [ -z "$par" ] then echo "Could not parse device and partition." exit 1 fi par=$(( $par + $j )) if [[ $LOAD_CMD =~ mmc ]] then retval="/dev/mmcblk${dev}p${par}" elif [[ $LOAD_CMD =~ scsi ]] then # converts number to a scsi device character dev=$((dev + 97)) dev=$(printf %x $dev) dev=$(printf "\x$dev") retval="/dev/sd${dev}${par}" else echo "Only mmc and scsi are supported for automatically setting the root partition." echo "Manually set DOM0_CMD with the root device in the config file to bypass this." exit 1 fi } function generate_domU_configs() { local i=0 local j=$1 # $j + 1 - 1 as it is starting from 0 local n=$1 local dest mount -t ext4 /dev/mapper/diskimage$j $DESTDIR/part/disk$j mkdir -p $DESTDIR/part/disk$j/etc/xen add_rootfs 0 first_part=$retval while test $i -lt $NUM_DOMUS do if test "${DOMU_NOBOOT[$i]}" then dest="$DESTDIR/part/disk$j/etc/xen/domU$i.cfg" echo "name=\"domU$i\"" >> $dest echo "memory=${DOMU_MEM[$i]}" >> $dest echo "vcpus=${DOMU_VCPUS[$i]}" >> $dest echo "# mount $first_part /boot" >> $dest echo "kernel=\"/boot/${DOMU_KERNEL[$i]}\"" >> $dest if test "${DOMU_RAMDISK[$i]}" then echo "ramdisk=\"/boot/${DOMU_RAMDISK[$i]}\"" >> $dest fi if [ -z "${DOMU_CMD[$i]}" ] then DOMU_CMD[$i]="console=hvc0" fi if [[ ! ${DOMU_CMD[$i]} =~ root= ]] then if test -z "${DOMU_ROOTFS[$i]}" then DOMU_CMD[$i]="${DOMU_CMD[$i]} root=/dev/ram0" >> $dest else DOMU_CMD[$i]="${DOMU_CMD[$i]} root=/dev/xvda" >> $dest fi fi echo "extra=\"${DOMU_CMD[$i]}\"" >> $dest if test "${DOMU_ROOTFS[$i]}" then add_rootfs $n echo "disk=[\"$retval,,xvda\"]" >> $dest fi fi n=$(( $n + 1 )) i=$(( $i + 1 )) done umount $DESTDIR/part/disk$j } function add_partition() { local rootfs="$1" local aux_dir=$(mktemp -d) cd "$aux_dir" if [[ $rootfs = *.cpio.gz ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | gunzip | cpio -id _part_size=$(du -sb| awk '{print $1}') elif [[ $rootfs = *.tar.gz ]] then tar -xf "${UBOOT_OUT_ABS}/$rootfs" _part_size=$(du -sb| awk '{print $1}') else echo "Ignoring $rootfs: unsupported file format. Use cpio.gz or tar.gz." fi cd - rm -rf "$aux_dir" _part_size=$(( $_part_size + $offset - 1)) _part_size=$(( $_part_size & ~($offset - 1) )) # account for gzip compression _part_size=$(( $_part_size * 2 )) # add some slack _part_size=$(( $SLACK*1024*1024 + $_part_size )) _part_size=$(( $_part_size / 1024 / 1024 )) echo PART size: "$_part_size"MB prev=$(( $_npart - 1 )) _sector_start[$_npart]=$(( ${_sector_end[$prev]} + 1 )) _sector_end[$_npart]=$(( $_part_size * 1024 * 1024 / $_sector_size + ${_sector_start[$_npart]} - 1)) _tot_size=$(( $_tot_size + $_part_size )) _npart=$(( $_npart + 1 )) } function write_rootfs() { local j=$1 local rootfs=$2 # create mount point and mount diskn mkdir -p ${DESTDIR}/part/disk$j mount -t ext4 /dev/mapper/diskimage$j $DESTDIR/part/disk$j # Extract rootfs cpio archive into `.../part/vos_$j` cd ${DESTDIR}/part/disk$j if [[ $rootfs = *.cpio.gz ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | gunzip | cpio -id elif [[ $rootfs = *.tar.gz ]] then tar -xf "${UBOOT_OUT_ABS}/$rootfs" else echo "Ignoring $rootfs: unsupported file format. Use cpio.gz or tar.gz." fi cd - # umount sync umount $DESTDIR/part/disk$j } function print_help { echo "usage:" echo " $0 -c CONFIG_FILE -d UBOOT_DIRECTORY -t UBOOT_TYPE <-w WORK_DIRECTORY> <-s SLACK> -o IMG_FILE" echo " $0 -h" echo "where:" echo " -c CONFIG_FILE - configuration file" echo " -d UBOOT_DIRECTORY - root directory for the paths specified in CONFIG_FILE" echo " -t UBOOT_TYPE can be:" echo " sd - alias for \"mmc load 0:1\" for uboot load commands" echo " scsi - alias for \"scsi load 0:1\" for uboot load commands" echo " tftp - alias for \"tftpb\" for uboot load cammnds" echo " < > - used for uboot load commands" echo " -w WORK_DIRECTORY - work directory used when building the image" echo " -s SLACK - free MB to add to each partition, default 128" echo " -o IMG_FILE - the output img file " echo "Example:" echo " $0 -c ../config -d ./build42 -w tmp -o disk.img" } # before anything else, check if we have root privilege if ! [ $(id -u) = 0 ] then echo "This script needs root privilege to run, exiting." exit 1 fi while getopts ":w:d:c:t:s:o:h" opt do case ${opt} in t ) case $OPTARG in scsi ) LOAD_CMD="load scsi 0:1" ;; sd ) LOAD_CMD="load mmc 0:1" ;; tftp ) LOAD_CMD="tftpb" ;; * ) LOAD_CMD="$OPTARG" ;; esac ;; s ) SLACK=$OPTARG if ! test $SLACK -gt 0 then echo "Invalid SLACK parameter." exit 1 fi ;; w ) DESTDIR=$OPTARG ;; d ) UBOOT_OUT=$OPTARG ;; c ) CFG_FILE=$OPTARG ;; o ) IMG=$OPTARG ;; h ) print_help exit 0 ;; * ) echo "Unknown option, see \"$0 -h\"" exit 1 ;; esac done shift $((OPTIND -1)) if [ -z "$UBOOT_OUT" ] || [ -z "$CFG_FILE" ] || [ -z "$IMG" ] then echo "Undefined arguments, see \"$0 -h\"" exit 1 fi # if the user hasn't specified a working directing, create it if [ -z "$DESTDIR" ] then DESTDIR="$(mktemp -d /tmp/imagebuilder.XXXXXX)" DESTDIR_DEL=true else DESTDIR_DEL=false fi UBOOT_OUT_ABS="$(readlink -f $UBOOT_OUT)" DESTDIR_ABS="$(readlink -f $DESTDIR)" check_depends source "$CFG_FILE" i=0 while test $i -lt $NUM_DOMUS do if test -z "${DOMU_MEM[$i]}" then DOMU_MEM[$i]=512 fi if test -z "${DOMU_VCPUS[$i]}" then DOMU_VCPUS[$i]=1 fi i=$(( $i + 1 )) done offset=$((2*1024*1024)) _part1_size=`stat -L --printf="%s" $UBOOT_OUT/$DOM0_KERNEL` _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$DEVICE_TREE` )) _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$UBOOT_SCRIPT` )) if test "${DOM0_RAMDISK}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOM0_RAMDISK}` )) fi if test "${UBOOT_SOURCE}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$UBOOT_SOURCE` )) fi if test "${XEN}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$XEN` )) fi if test "$NUM_BOOT_AUX_FILE" && test "$NUM_BOOT_AUX_FILE" -gt 0 then i=0 while test $i -lt "$NUM_BOOT_AUX_FILE" do if [ ! -f "$UBOOT_OUT/${BOOT_AUX_FILE[$i]}" ] then echo "Can not find ${BOOT_AUX_FILE[$i]}, exiting" fi _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${BOOT_AUX_FILE[$i]}` )) i=$(( $i + 1 )) done fi i=0 while test $i -lt $NUM_DOMUS do _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_KERNEL[$i]}` )) if test "${DOMU_RAMDISK[$i]}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_RAMDISK[$i]}` )) fi if test "${DOMU_PASSTHROUGH_DTB[$i]}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_PASSTHROUGH_DTB[$i]}` )) fi i=$(( $i + 1 )) done # add 16 MB slack _part1_size=$(( $_part1_size + 16777216 )) _part1_size=$(( $_part1_size + $offset - 1)) _part1_size=$(( $_part1_size & ~($offset - 1) )) _part1_size=$(( $_part1_size / 1024 / 1024 )) echo PART1 size: "$_part1_size"MB _sector_size=512 # _sector_start[0] needs to be aligned to 2048 _sector_start[0]=2048 _sector_end[0]=$(( $_part1_size * 1024 * 1024 / $_sector_size + ${_sector_start[0]} - 1)) _tot_size=$(( $_part1_size )) _npart=1 if test "$DOM0_ROOTFS" then add_partition "$DOM0_ROOTFS" fi i=0 while test $i -lt $NUM_DOMUS do if test "${DOMU_ROOTFS[$i]}" then add_partition "${DOMU_ROOTFS[$i]}" fi i=$(( $i + 1 )) done _tot_size=$(( $_tot_size + 16 )) # NOTE: Increase vos_a to 256 to accomodate rootfs # 528 MiB (256 + 256 + 16) truncate $IMG -s "$_tot_size"M # create GPT partition table sgdisk -og $IMG i=0 j=1 while test $i -lt $_npart do sgdisk -n $j:${_sector_start[$i]}:${_sector_end[$i]} -c $j:"Linux""$j" -t $j:8300 $IMG i=$(( $i + 1 )) j=$(( $j + 1 )) done # find the first available loop device _loop_dev=$(losetup -f) # attach loopback device to $IMG losetup $_loop_dev $IMG _disksize=$(blockdev --getsize $_loop_dev) dmsetup create diskimage --table "0 $_disksize linear $_loop_dev 0" # ensure that /dev/mapper/diskimage exists while [ ! -b /dev/mapper/diskimage ] do sleep 2 done kpartx -a /dev/mapper/diskimage # ensure that /dev/mapperdiskimage1 exists while [ ! -b /dev/mapper/diskimage1 ] do sleep 2 done i=0 j=1 while test $i -lt $_npart do mkfs.ext4 -L vos_$j -F /dev/mapper/diskimage$j i=$(( $i + 1 )) j=$(( $j + 1 )) done # create mount point and mount disk1 mkdir -p ${DESTDIR}/part/disk1 mount -t ext4 /dev/mapper/diskimage1 $DESTDIR/part/disk1 # only copy over files that were counted for the partition size cd "$UBOOT_OUT" cp --parents "$DOM0_KERNEL" "${DESTDIR_ABS}/part/disk1/" cp --parents "$DEVICE_TREE" "${DESTDIR_ABS}/part/disk1/" cp --parents "$UBOOT_SCRIPT" "${DESTDIR_ABS}/part/disk1/" if test "${DOM0_RAMDISK}" then cp --parents "$DOM0_RAMDISK" "${DESTDIR_ABS}/part/disk1/" fi if test "${UBOOT_SOURCE}" then cp --parents "$UBOOT_SOURCE" "${DESTDIR_ABS}/part/disk1/" fi if test "${XEN}" then cp --parents "$XEN" "${DESTDIR_ABS}/part/disk1/" fi if test "$NUM_BOOT_AUX_FILE" && test "$NUM_BOOT_AUX_FILE" -gt 0 then i=0 while test $i -lt "$NUM_BOOT_AUX_FILE" do cp --parents "${BOOT_AUX_FILE[$i]}" "${DESTDIR_ABS}/part/disk1/" i=$(( $i + 1 )) done fi i=0 while test $i -lt $NUM_DOMUS do cp --parents "${DOMU_KERNEL[$i]}" "${DESTDIR_ABS}/part/disk1/" if test "${DOMU_RAMDISK[$i]}" then cp --parents "${DOMU_RAMDISK[$i]}" "${DESTDIR_ABS}/part/disk1/" fi if test "${DOMU_PASSTHROUGH_DTB[$i]}" then cp --parents "${DOMU_PASSTHROUGH_DTB[$i]}" "${DESTDIR_ABS}/part/disk1/" fi i=$(( $i + 1 )) done cd - # unmount sync # This fails for some reason. It could work now because we are not using qemu-user # fstrim $DESTDIR/part/disk1 umount $DESTDIR/part/disk1 j=2 if test "$DOM0_ROOTFS" then write_rootfs 2 "$DOM0_ROOTFS" generate_domU_configs 2 j=$(( $j + 1 )) fi i=0 while test $i -lt $NUM_DOMUS do if test "${DOMU_ROOTFS[$i]}" then write_rootfs $j "${DOMU_ROOTFS[$i]}" j=$(( $j + 1 )) fi i=$(( $i + 1 )) done kpartx -d /dev/mapper/diskimage dmsetup remove diskimage losetup -d $_loop_dev if [ "$DESTDIR_DEL" = true ] then rm -rf "$DESTDIR" fi