#!/bin/bash -ex # usage: # tar2fs chroot.tar image.raw [size_in_bytes [fstype]] . shell-error export LANG=C 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= ARCH="$(arch)" # NB: sudo => no GLOBAL_ will do either; mind qemu-* case "$ARCH" in *86*) # NB: different storage modules might be needed for non-kvm INITRD_MODULES="sd_mod ata_piix ahci virtio-scsi virtio-blk" 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" ;; e2k) BOOTFSTYPE="ext2" # firmware knows it BLOCKDEV="/dev/sda" # ...hopefully... BOOTPART="1" ROOTPART="2" ;; mips*) ROOTPART="1" BLOCKDEV="/dev/sda" ;; 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=$? cd / if [ -n "$ROOTFS" ]; then umount ${BOOTFS:+"$BOOTFS"} "$ROOTFS"{/dev,/proc,/sys,} if [ -n "$LOOPDEV" ]; then kpartx -d -s "$LOOPDEV" || { sleep 10 kpartx -d -s -v "$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 # Query ARCH in chroot and redefine arch-dependent variable ARCH="$(chroot "$ROOTFS" rpm --eval '%_host_cpu')" if [[ $ARCH = *86* ]] then # NB: different storage modules might be needed for non-kvm INITRD_MODULES="sd_mod ata_piix ahci virtio-scsi virtio-blk" else INITRD_MODULES= fi echo "MODULES_PRELOAD += $INITRD_MODULES $ROOTFSTYPE" >> "$ROOTFS/etc/initrd.mk" case "$ARCH" in *86*) echo "FEATURES += qemu" >> "$ROOTFS/etc/initrd.mk" ;; e2k) echo "FEATURES += usb" >> "$ROOTFS/etc/initrd.mk" ;; esac # NB: don't stick BOOTFS here, it has slightly different semantics pushd $ROOTFS/boot # 4.9.76-elbrus-def-alt1.11.1 -> def get_label() { echo "${1# *}" | sed -r 's,.*elbrus-([0-9a-z]+)-.*$,\1,'; } KVERSIONS= if [ -s .origver ]; then while read kver; do chroot "$ROOTFS" make-initrd -k "$kver" KVERSIONS="$KVERSIONS $kver" done < .origver fi [ -n "$KVERSIONS" ] || fatal "unable to deduce kernel version" rm -f .origver # ...target device too sed -i "s,$LOOPROOT,$ROOTDEV," "$ROOTFS/etc/fstab" echo "** KVERSIONS=[$KVERSIONS]" >&2 if [ "`echo $KVERSIONS | wc -w`" = 1 ]; then # 2+ labels echo -e "default=`get_label $KVERSIONS`\n" >> boot.conf fi # FIXME: relies on particular (current) kernel package naming scheme for v in $KVERSIONS; do l="`get_label "$v"`" cat >> boot.conf <<EOF label=$l partition=0 image=/image-$v initrd=/initrd-$v.img cmdline=console=ttyS0,115200 console=tty0 consoleblank=0 hardreset root=UUID=$ROOTUUID EOF done echo "** start of boot.conf" cat boot.conf echo "** end of boot.conf" popd if [ -x "$ROOTFS"/sbin/lilo ]; then # configure and install bootloader REGEXP='^.*: ([0-9]+) cylinders, ([0-9]+) heads, ([0-9]+) sectors/track*$' set -- $(sfdisk -g "$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 console=tty1 console=ttyS0,115200n8\" 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 elif [ -x "$ROOTFS"/usr/sbin/grub-efi-autoupdate ]; then chroot "$ROOTFS" grub-mkconfig -o /boot/grub/grub.cfg chroot "$ROOTFS" grub-install elif [ -x "$ROOTFS"/usr/sbin/grub-autoupdate ]; then chroot "$ROOTFS" grub-mkconfig -o /boot/grub/grub.cfg chroot "$ROOTFS" grub-install --target=i386-pc "$LOOPDEV" fi if [ -n "$SUDO_USER" ]; then chown "$SUDO_USER:$(id -g "$SUDO_USER")" "$IMG" ||: fi # maybe qemu interpreter was copied to chroot; # this is no longer necessary, remove rm -rf "$ROOTFS"/.host ||: