mkimage-profiles/bin/tar2fs
Michael Shigorin d7689f30c7 tar2vm: rewrote as tar2fs
Overview of the changes:
- ARM support: separate ext2 /boot, no LILO
- avoid race condition with devmapper
- trap ERR so that -e in shebang doesn't result in extra cleanup hassle
- configurable root filesystem type (ext4 by default)
- jumps through parted hoops

Details:

1. LILO is x86-specific while the rest of the script can be used
   to prepare e.g. Marvell ArmadaXP or CuBox images; we can generally
   count on uboot supporting ext2 for relatively sane platforms but
   not ext4 that would be a better root filesystem performance-wise.

2. Apparently /dev/mapper/loopXpY can be still missing at the time
   when kpartx returns and pop up a bit later... sit there, wait
   and check for it.

3. If something went wrong with any command of the script it would bail out
   due to -e in shebang; it is now better to clean up the loopback device
   and its mappings in this situation either.

4. One size doesn't fit all, really.

5. The parted sizing was sloppy as in broken, now it's just half insane.
   Someone's decision to stick units and auto-alignment knobs into
   a single one was apparently hilarious...

   http://www.gnu.org/software/parted/manual/parted.html#unit

Manual loop/dm cleanup is described in documentation just in case.

/boot size meter is suboptimal in terms of additional I/O incurred,
will be most likely rewritten to make use of advance "du -s".
2013-06-17 19:29:11 +04:00

237 lines
5.9 KiB
Bash
Executable File

#!/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
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 (note the final ceil rounding to megabytes)
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))"
# image and /boot sizes in megabytes
ROOTSIZEM="$(($ROOTSIZE / $MB))"
BOOTSIZEM="$((($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 "$LOOPDEV"
LOOPROOT="/dev/mapper/$(basename "$LOOPDEV")p$ROOTPART"
# might take a tiny bit of time to actually appear
for i in `seq 10`; do
[ -b "$LOOPROOT" ] && break || sleep 0.1
done
mkfs."$ROOTFSTYPE" "$LOOPROOT"
if [ -n "$BOOTPART" ]; then
LOOPBOOT="/dev/mapper/$(basename "$LOOPDEV")p$BOOTPART"
mkfs."$BOOTFSTYPE" "$LOOPBOOT"
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 "$LOOPROOT / $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 /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