tar2fs 6.17 KB
Newer Older
1 2 3 4 5
#!/bin/bash -ex
# usage:
# tar2fs chroot.tar image.raw [size_in_bytes [fstype]]

. shell-error
6
export LANG=C
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

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*
24

25
SIZE_FACTOR=2		# multiply the sizes found by this value
26
BOOT_SIZE_FACTOR=2	# multiply /boot size by this value additionally
27 28 29 30 31 32 33 34 35 36

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
37
	INITRD_MODULES="sd_mod ata_piix ahci"
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
	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

93
# image size in bytes
94 95 96 97
TARSIZE="$(stat -Lc %s "$TAR")"
# /boot size in that tarball
BOOTSIZE="$(boot_size "$TAR")"
DEFSIZE="$(($SIZE_FACTOR * ($TARSIZE - $BOOTSIZE)))"	# (exact sizes)
98
ROOTSIZE="$((${ROOTSIZE:-$DEFSIZE} + $MB - 1))"	# for ceil rounding to MB
99 100
# image and /boot sizes in megabytes
ROOTSIZEM="$(($ROOTSIZE / $MB))"
101
BOOTSIZEM="$((($SIZE_FACTOR * $BOOT_SIZE_FACTOR * $BOOTSIZE + $MB - 1) / $MB))"
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

# 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
Michael Shigorin's avatar
Michael Shigorin committed
133
			kpartx -d -s "$LOOPDEV"
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
			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

Michael Shigorin's avatar
Michael Shigorin committed
161
kpartx -a -s "$LOOPDEV"
162 163 164 165 166 167 168 169 170
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

171 172 173 174 175 176 177 178 179 180 181 182 183 184
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

185 186 187 188 189 190 191 192 193 194 195 196 197
# 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...
198
echo "$ROOTDEV / $ROOTFSTYPE relatime 1 1" >> "$ROOTFS/etc/fstab"
199 200 201 202 203 204

# target device at once
if [ -n "$BOOTPART" ]; then
	echo "$BOOTDEV /boot $BOOTFSTYPE defaults 1 2" >> "$ROOTFS/etc/fstab"
fi

205
echo "MODULES_PRELOAD += $INITRD_MODULES $ROOTFSTYPE" >> "$ROOTFS/etc/initrd.mk"
206 207 208 209 210 211 212

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"

213
if [ -x "$ROOTFS"/sbin/lilo ]; then
214
	# configure and install bootloader
215 216
	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@")
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

	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