tar2fs 5.89 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
#!/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