#!/bin/bash -ex # usage: # tar2fs chroot.tar image.raw [size_in_bytes [fstype]] . shell-error if [ $# -lt 2 ]; then fatal "error: tar2fs needs at least two arguments" fi # this needs env_keep sudo setup to actually work if [ -n "$GLOBAL_BUILDDIR" ]; then WORKDIR="$GLOBAL_BUILDDIR/vmroot" else WORKDIR="$(mktemp --tmpdir -d vmroot-XXXXX)" fi [ -n "$WORKDIR" ] || fatal "couldn't come up with suitable WORKDIR" [ -n "$GLOBAL_DEBUG" ] || message "WORKDIR: $WORKDIR" MB=1048576 # a parted's "megabyte" in bytes is *broken* SIZE_FACTOR=2 # multiply the sizes found by this value BOOT_SIZE_FACTOR=2 # multiply /boot size by this value additionally CUR_BOUNDARY=0 # align first partition at 1MB for performance (+1) INITRD_MODULES= BOOTFSTYPE= BOOTPART= case "`arch`" in # NB: sudo => no GLOBAL_ will do either; mind qemu-* *86*) # NB: different storage modules might be needed for non-kvm INITRD_MODULES="sd_mod ata_piix" BLOCKDEV="/dev/sda" # might be /dev/vda for virtio ROOTPART="1" ;; arm*) BOOTFSTYPE="ext2" # support expected in every sane target uboot BLOCKDEV="/dev/mmcblk0p" # ...hopefully... BOOTPART="1" ROOTPART="2" ;; esac # figure out the part taken by /boot in the given tarball boot_size() { if [ -n "$BOOTPART" ]; then tar tvf "$1" \ | awk ' \ BEGIN { sum=0 } /^-.* \.\/boot\// { sum=sum+$3 } END { print sum }' else echo "0" fi } # parted wrapper for convenience parting() { parted "$LOOPDEV" --align optimal --script -- "$@"; } # unfortunately parted is insane enough to lump alignment controls # into unit controls so creating adjacent partitions sized in MiB # is not as straightforward as it might be... thus "+1" padding; # see also http://www.gnu.org/software/parted/manual/parted.html#unit mkpart() { # a bit different start/end semantics to handle end of device too local start="$(($CUR_BOUNDARY + 1))" # yes, we lose a megabyte if [ -n "$1" ]; then CUR_BOUNDARY="$(($start + $1))" local end="$CUR_BOUNDARY"MiB else local end="-1s" # last sector of the image fi parting mkpart primary ext2 "$start"MiB "$end" } # a tarball containing chroot with a kernel TAR="$1" [ -s "$TAR" ] || fatal "source tarball doesn't really exist" # a path to the image to be generated IMG="$2" [ -d "$(dirname "$IMG")" ] || fatal "target directory doesn't exist" # 0 means auto; if a value is specified it holds (no /boot subtracted) ROOTSIZE="$3" [ -n "$ROOTSIZE" -a "$ROOTSIZE" != 0 ] || unset ROOTSIZE # image size in bytes TARSIZE="$(stat -Lc %s "$TAR")" # /boot size in that tarball BOOTSIZE="$(boot_size "$TAR")" DEFSIZE="$(($SIZE_FACTOR * ($TARSIZE - $BOOTSIZE)))" # (exact sizes) ROOTSIZE="$((${ROOTSIZE:-$DEFSIZE} + $MB - 1))" # for ceil rounding to MB # image and /boot sizes in megabytes ROOTSIZEM="$(($ROOTSIZE / $MB))" BOOTSIZEM="$((($SIZE_FACTOR * $BOOT_SIZE_FACTOR * $BOOTSIZE + $MB - 1) / $MB))" # tested to work: ext[234], jfs # NB: xfs doesn't have a spare sector for the bootloader ROOTFSTYPE="${4:-ext4}" # single root partition hardwired so far, # add another image for home/data/swap if needed ROOTDEV="$BLOCKDEV$ROOTPART" # last preparations... MKFS="mkfs.$ROOTFSTYPE ${BOOTFSTYPE:+mkfs.$BOOTFSTYPE}" for i in losetup sfdisk parted kpartx $MKFS; do if ! type -t "$i" >&/dev/null; then fatal "$i required but not found in host system" fi done LOOPDEV="$(losetup --find)" # would be sad about non-binary megabytes too ROOTFS="$WORKDIR/chroot" BOOTFS= if [ -n "$BOOTPART" ]; then BOOTFS="$ROOTFS/boot" fi exit_handler() { rc=$? if [ -n "$ROOTFS" ]; then umount ${BOOTFS:+"$BOOTFS"} "$ROOTFS"{/dev,/proc,/sys,} if [ -n "$LOOPDEV" ]; then kpartx -d "$LOOPDEV" losetup --detach "$LOOPDEV" fi rm -r -- "$ROOTFS" rmdir -- "$WORKDIR" fi exit $rc } # handle -e in shebang as well trap exit_handler EXIT ERR # prepare disk image and a filesystem inside it rm -f -- "$IMG" OFFSET="$(($CUR_BOUNDARY + $BOOTSIZEM + $ROOTSIZEM - 1))" dd if=/dev/zero of="$IMG" conv=notrunc bs=$MB count=1 seek="$OFFSET" losetup "$LOOPDEV" "$IMG" parting mklabel msdos if [ -n "$BOOTPART" ]; then BOOTDEV="$BLOCKDEV$BOOTPART" mkpart "$BOOTSIZEM" fi # not ROOTSIZEM but "the rest"; somewhat non-trivial arithmetics lurk in parted mkpart kpartx -a -s "$LOOPDEV" LOOPROOT="/dev/mapper/$(basename "$LOOPDEV")p$ROOTPART" mkfs."$ROOTFSTYPE" "$LOOPROOT" if [ -n "$BOOTPART" ]; then LOOPBOOT="/dev/mapper/$(basename "$LOOPDEV")p$BOOTPART" mkfs."$BOOTFSTYPE" "$LOOPBOOT" fi ROOTUUID="$(blkid -s UUID -o value -c /dev/null "$LOOPROOT")" if [ -n "$ROOTUUID" ]; then ROOTDEV="UUID=$ROOTUUID" else ROOTDEV="$LOOPROOT" fi if [ -n "$BOOTPART" ]; then BOOTUUID="$(blkid -s UUID -o value -c /dev/null "$LOOPBOOT")" if [ -n "$ROOTUUID" ]; then BOOTDEV="UUID=$BOOTUUID" fi fi # mount and populate it mkdir -pm755 "$ROOTFS" mount "$LOOPROOT" "$ROOTFS" if [ -n "$BOOTPART" ]; then mkdir -pm700 "$BOOTFS" mount "$LOOPBOOT" "$BOOTFS" fi tar -C "$ROOTFS" --numeric-owner -xf "$TAR" for i in /dev /proc /sys; do mount --bind "$i" "$ROOTFS$i"; done # loop device so lilo could work... echo "$ROOTDEV / $ROOTFSTYPE relatime 1 1" >> "$ROOTFS/etc/fstab" # target device at once if [ -n "$BOOTPART" ]; then echo "$BOOTDEV /boot $BOOTFSTYPE defaults 1 2" >> "$ROOTFS/etc/fstab" fi echo "MODULES_PRELOAD += $INITRD_MODULES $ROOTFSTYPE" >> "$ROOTFS/etc/initrd.mk" KERNEL="$(readlink $ROOTFS/boot/vmlinuz | sed 's,vmlinuz-,,')" chroot "$ROOTFS" make-initrd -k "$KERNEL" # ...target device too sed -i "s,$LOOPROOT,$ROOTDEV," "$ROOTFS/etc/fstab" if [ -x "$ROOTFS"/sbin/lilo ]; then # configure and install bootloader REGEXP='^Disk .*: ([0-9]+) cylinders, ([0-9]+) heads, ([0-9]+) sectors/track*$' set -- $(sfdisk -l "$LOOPDEV" | grep -E "$REGEXP" | sed -r "s@$REGEXP@\1 \2 \3@") LILO_COMMON="lba32 delay=1 vga=0 image=/boot/vmlinuz initrd=/boot/initrd.img append=\"root=$ROOTDEV rootdelay=3 quiet\" label=linux" cat > "$ROOTFS"/etc/lilo-loop.conf <<-EOF boot=$LOOPDEV disk=$LOOPDEV bios=0x80 cylinders=$1 heads=$2 sectors=$3 partition=$LOOPROOT start=63 $LILO_COMMON EOF chroot "$ROOTFS" lilo -C /etc/lilo-loop.conf cat > "$ROOTFS"/etc/lilo.conf <<-EOF boot=$BLOCKDEV $LILO_COMMON EOF fi if [ -n "$SUDO_USER" ]; then chown "$SUDO_USER" "$IMG" "$ROOTFS" "$WORKDIR" fi