tar2fs 11.9 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
BOOTLOADERPARTSIZEM=0	# PReP partition size (ppc*)
28 29 30 31 32

CUR_BOUNDARY=0		# align first partition at 1MB for performance (+1)

BOOTFSTYPE=
BOOTPART=
33 34
EFIPARTFSTYPE=
EFIPART=
35

36 37
BOOTLOADER="$5"

38 39 40 41 42
if [ -n "$6" ]; then
	ARCH="$6"
else
	ARCH="$(arch)"
fi
43

44 45
BOOTTYPE="$8"

46
case "$ARCH" in
47
e2k*)
48 49 50 51 52
	BOOTFSTYPE="ext2"       # firmware knows it
	BLOCKDEV="/dev/sda"     # ...hopefully...
	BOOTPART="1"
	ROOTPART="2"
	;;
53 54 55 56 57 58 59
ppc*)
	BOOTFSTYPE="ext4"
	BLOCKDEV="/dev/sda"
	BOOTLOADERPART="1"
	BOOTLOADERPARTSIZEM="8"
	ROOTPART="2"
	;;
60 61 62 63 64
arm*|aarch64)
	ROOTPART="1"
	BLOCKDEV="/dev/sda"
	CUR_BOUNDARY=15 # offset 16 MiB for singleboard's
	;;
65 66 67 68 69
riscv64)
	ROOTPART="1"
	BLOCKDEV="/dev/sda"
	CUR_BOUNDARY=33 # offset 34 MiB for singleboard's
	;;
70
*)
71
	ROOTPART="1"
72 73
	BLOCKDEV="/dev/sda"
	;;
74 75
esac

76 77 78 79
if [ "$BOOTLOADER" == grub-efi -o "$BOOTTYPE" == EFI ]; then
	EFIPART="1"
	EFIPARTSIZEM="256"
	EFIPARTFSTYPE="fat"
80 81 82 83 84 85
	if [ "$ARCH" = x86_64 ]; then
		BIOSPART="2"
		ROOTPART="3"
	else
		ROOTPART="2"
	fi
86 87
fi

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
# tested to work: ext[234], jfs
# NB: xfs doesn't have a spare sector for the bootloader
ROOTFSTYPE="${4:-ext4}"

if [ -f "$ROOTFS/boot/extlinux/extlinux.conf" ] && [ "$ROOTFSTYPE" != ext4 ]; then
	if [ -n "$EFIPART" ]; then
		BOOTPART="2"
		ROOTPART="3"
	else
		BOOTPART="1"
		ROOTPART="2"
	fi
	BOOTFSTYPE="ext4"
fi

103 104 105 106 107 108 109 110 111
PARTTABLE="$7"
if [ -z "$PARTTABLE" ]; then
	if [ "$BOOTLOADER" == grub-efi ]; then
		PARTTABLE=gpt
	else
		PARTTABLE=msdos
	fi
fi

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
# 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
139
		local end="$OFFSET"MiB
140
	fi
141 142 143 144 145 146
	if [ -n "$2" ]; then
		CUR_FS="$2"
	else
		CUR_FS=ext2
	fi
	parting mkpart primary "$CUR_FS" "$start"MiB "$end"
147 148
}

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
create_inner_grub_cfg() {
	local boot_path boot_uuid cfg_path
	cfg_path=$1
	[ -n "$cfg_path" ] || return 1
	if [ -n "$BOOTUUID" ]; then
		boot_uuid="$BOOTUUID"
		boot_path=grub
	else
		boot_uuid="$ROOTUUID"
		boot_path=boot/grub
	fi
	[ -n "$boot_uuid" ] || return 1
	cat >"$cfg_path" <<-GRUB_EOF
	search.fs_uuid $boot_uuid root
	set prefix=(\$root)/$boot_path
	configfile \$prefix/grub.cfg
	GRUB_EOF
}

168 169 170 171 172 173 174 175 176 177 178 179
# 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

180 181 182
BOOTSIZE="$9"
[ -n "$BOOTSIZE" -a "$BOOTSIZE" != 0 ] || unset BOOTSIZE

183
# image size in bytes
184 185
TARSIZE="$(stat -Lc %s "$TAR")"
# /boot size in that tarball
186 187
BOOTDEFSIZE="$(boot_size "$TAR")"
DEFSIZE="$(($SIZE_FACTOR * ($TARSIZE - ${BOOTSIZE:-$BOOTDEFSIZE})))"	# (exact sizes)
188
ROOTSIZE="$((${ROOTSIZE:-$DEFSIZE} + $MB - 1))"	# for ceil rounding to MB
189 190
# image and /boot sizes in megabytes
ROOTSIZEM="$(($ROOTSIZE / $MB))"
191 192 193
BOOTDEFSIZE="$(($SIZE_FACTOR * $BOOT_SIZE_FACTOR * $BOOTDEFSIZE))"
BOOTSIZE="$((${BOOTSIZE:-$BOOTDEFSIZE} + $MB - 1))"	# for ceil rounding to MB
BOOTSIZEM="$(($BOOTSIZE / $MB))"
194 195 196 197 198 199

# single root partition hardwired so far,
# add another image for home/data/swap if needed
ROOTDEV="$BLOCKDEV$ROOTPART"

# last preparations...
200 201
MKFS="mkfs.$ROOTFSTYPE ${BOOTFSTYPE:+mkfs.$BOOTFSTYPE} \
  ${EFIPARTFSTYPE:+mkfs.$EFIPARTFSTYPE}"
202 203 204 205 206 207 208 209 210
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

ROOTFS="$WORKDIR/chroot"

BOOTFS=
211
EFIPARTFS=
212 213 214
if [ -n "$BOOTPART" ]; then
	BOOTFS="$ROOTFS/boot"
fi
215 216 217
if [ -n "$EFIPART" ]; then
	EFIPARTFS="$ROOTFS/boot/efi"
fi
218 219 220

exit_handler() {
	rc=$?
221
	cd /
222
	if [ -n "$ROOTFS" ]; then
223 224
		umount ${EFIPARTFS:+"$EFIPARTFS"} ${BOOTFS:+"$BOOTFS"} \
		  "$ROOTFS"{/dev,/proc,/sys,}
225 226

		if [ -n "$LOOPDEV" ]; then
227 228 229 230
			kpartx -d -s "$LOOPDEV" || {
				sleep 10
				kpartx -d -s -v "$LOOPDEV"
			}
231 232 233 234 235 236 237 238 239 240 241 242 243
			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"
244
OFFSET="$(($CUR_BOUNDARY + $EFIPARTSIZEM + $BOOTLOADERPARTSIZEM + $BOOTSIZEM + ${BIOSPART:+1} + $ROOTSIZEM - 1))"
245
dd if=/dev/zero of="$IMG" conv=notrunc bs=$MB count=1 seek="$OFFSET"
246 247
losetup -f "$IMG"
LOOPDEV=$(losetup -j "$IMG" | cut -f 1 -d ':')
248

249
parting mklabel "$PARTTABLE"
250

251 252 253
if [ -n "$BOOTLOADERPART" ] && [ -n "$BOOTLOADERPARTSIZEM" ]; then
	case "$ARCH" in
		ppc*)
254
			parting mkpart primary ext2 $((CUR_BOUNDARY+1))MiB $((BOOTLOADERPARTSIZEM + 1))MiB
255
			CUR_BOUNDARY="$BOOTLOADERPARTSIZEM"
256 257 258 259 260 261
			parting set 1 prep on
			parting set 1 boot on
			;;
	esac
fi

262 263
if [ -n "$EFIPART" ]; then
	EFIDEV="$EFIDEV$EFIPART"
264
	if [ "$PARTTABLE" == gpt ]; then
265
		parting mkpart fat32 $((CUR_BOUNDARY+1))MiB $(($EFIPARTSIZEM + 1))MiB
266
	else
267
		parting mkpart primary fat32 $((CUR_BOUNDARY+1))MiB $(($EFIPARTSIZEM + 1))MiB
268
	fi
269 270
	CUR_BOUNDARY="$EFIPARTSIZEM"
	parting set 1 boot on
271 272 273
	if [ "$PARTTABLE" == gpt ]; then
		parting set 1 esp on
	fi
274 275
fi

276 277 278 279 280 281
if [ -n "$BIOSPART" ]; then
	parting mkpart bios $((CUR_BOUNDARY+1))MiB $(($CUR_BOUNDARY + 2))MiB
	CUR_BOUNDARY="$(($CUR_BOUNDARY + 1))"
	parting set "$BIOSPART" bios on
fi

282 283 284 285 286 287 288 289
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
290
kpartx -a -s "$LOOPDEV"
291 292 293 294 295 296 297 298 299
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

300 301 302 303
if [ -n "$BOOTLOADERPART" ] && [ -n "$BOOTLOADERPARTSIZEM" ]; then
	LOOPBOOTLOADER="/dev/mapper/$(basename "$LOOPDEV")p$BOOTLOADERPART"
fi

304 305 306 307 308
if [ -n "$EFIPART" ]; then
	LOOPEFI="/dev/mapper/$(basename "$LOOPDEV")p$EFIPART"
	mkfs.fat -F32 "$LOOPEFI"
fi

309 310 311 312 313 314 315 316 317
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")"
318
	if [ -n "$BOOTUUID" ]; then
319 320 321 322
		BOOTDEV="UUID=$BOOTUUID"
	fi
fi

323 324 325 326 327 328 329
if [ -n "$EFIPART" ]; then
	EFIUUID="$(blkid -s UUID -o value -c /dev/null "$LOOPEFI")"
	if [ -n "$EFIUUID" ]; then
		EFIDEV="UUID=$EFIUUID"
	fi
fi

330 331 332 333 334 335 336 337 338
# mount and populate it
mkdir -pm755 "$ROOTFS"
mount "$LOOPROOT" "$ROOTFS"

if [ -n "$BOOTPART" ]; then
	mkdir -pm700 "$BOOTFS"
	mount "$LOOPBOOT" "$BOOTFS"
fi

339 340 341 342 343
if [ -n "$EFIPART" ]; then
	mkdir -pm751 "$EFIPARTFS"
	mount "$LOOPEFI" "$EFIPARTFS"
fi

344 345 346 347
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...
348 349 350 351 352
if grep -qe "[[:space:]]/[[:space:]]" "$ROOTFS/etc/fstab"; then \
	sed -i "s/LABEL=ROOT/$ROOTDEV/" "$ROOTFS/etc/fstab"
else
	echo "$ROOTDEV / $ROOTFSTYPE relatime 1 1" >> "$ROOTFS/etc/fstab"
fi
353 354 355 356 357

# target device at once
if [ -n "$BOOTPART" ]; then
	echo "$BOOTDEV /boot $BOOTFSTYPE defaults 1 2" >> "$ROOTFS/etc/fstab"
fi
358 359 360
if [ -n "$EFIPART" ]; then
	echo "$EFIDEV /boot/efi vfat umask=0,quiet,showexec,iocharset=utf8,codepage=866 1 2" >> "$ROOTFS/etc/fstab"
fi
361

Anton Midyukov's avatar
Anton Midyukov committed
362 363
# clean fstab
sed -i "/LABEL=ROOT/d" "$ROOTFS/etc/fstab"
364 365 366
# ...target device too
sed -i "s,$LOOPROOT,$ROOTDEV," "$ROOTFS/etc/fstab"

367 368 369 370
# Update cmdline.txt for Raspberry Pi
[ -f "$ROOTFS/boot/efi/cmdline.txt" ] &&
    sed -i "s/LABEL=ROOT/$ROOTDEV/" "$ROOTFS/boot/efi/cmdline.txt"

371
# Update extlinux.conf
372 373 374
if [ -f "$ROOTFS/boot/extlinux/extlinux.conf" ]; then
	sed -i "s/LABEL=ROOT/$ROOTDEV/g" "$ROOTFS/boot/extlinux/extlinux.conf"
	if [ "$PARTTABLE" == gpt ]; then
375 376 377 378 379
		if [ -n "$BOOTPART" ]; then
			parting set "$BOOTPART" legacy_boot on
		else
			parting set "$ROOTPART" legacy_boot on
		fi
380 381
	fi
	if [ "$PARTTABLE" == msdos ]; then
382 383 384 385 386
		if [ -n "$BOOTPART" ]; then
			parting set "$BOOTPART" boot on
		else
			parting set "$ROOTPART" boot on
		fi
387 388
	fi
fi
389

390 391 392 393 394 395 396 397
# e2k
if [ -f "$ROOTFS/boot/boot.conf" ]; then
	sed -i "s/LABEL=ROOT/$ROOTDEV/g" "$ROOTFS/boot/boot.conf"
fi

# Query ARCH in chroot and redefine arch-dependent variable
ARCH="$(chroot "$ROOTFS" rpm --eval '%_host_cpu')"

398 399 400
# Setup bootloader
case "$BOOTLOADER" in
lilo)
401
	# configure and install bootloader
402 403
	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@")
404 405 406 407 408 409

	LILO_COMMON="lba32
delay=1
vga=0
image=/boot/vmlinuz
  initrd=/boot/initrd.img
410
  append=\"root=$ROOTDEV rootdelay=3 console=tty1 console=ttyS0,115200n8\"
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
  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
431 432
	;;
grub-efi)
433
	echo 'GRUB_DISABLE_OS_PROBER=true' >> "$ROOTFS"/etc/sysconfig/grub2
434
	chroot "$ROOTFS" grub-mkconfig -o /boot/grub/grub.cfg
435
	mkdir -p "$ROOTFS"/boot/efi/EFI/BOOT
436 437
	case "$ARCH" in
		x86_64)
438
			chroot "$ROOTFS" grub-install --target=i386-efi --recheck \
439
			  --removable --uefi-secure-boot
440
			chroot "$ROOTFS" grub-install --target=x86_64-efi --recheck \
441
			  --removable --uefi-secure-boot
442 443 444 445
			sed -i 's/initrd16/initrd/g' "$ROOTFS/boot/grub/grub.cfg"
			sed -i 's/linux16/linux/g' "$ROOTFS/boot/grub/grub.cfg"
			[ -n "$BIOSPART" ] &&
				chroot "$ROOTFS" grub-install --target=i386-pc "$LOOPDEV"
446 447
		;;
		aarch64)
448 449
			cp "$ROOTFS"/usr/lib64/efi/grubaa64.efi \
			   "$ROOTFS"/boot/efi/EFI/BOOT/bootaa64.efi
450
		;;
451
		riscv64)
452 453
			cp "$ROOTFS"/usr/lib64/efi/grubriscv64.efi \
			   "$ROOTFS"/boot/efi/EFI/BOOT/bootriscv64.efi
454
		;;
455
		loongarch64)
456 457
			cp "$ROOTFS"/usr/lib64/efi/grubloongarch64.efi \
			   "$ROOTFS"/boot/efi/EFI/BOOT/bootloongarch64.efi
458
		;;
459
	esac
460
	sed -i '/GRUB_DISABLE_OS_PROBER=true/d' "$ROOTFS/etc/sysconfig/grub2"
461 462
	[ -s "$ROOTFS"/boot/efi/EFI/BOOT/grub.cfg ] ||
		create_inner_grub_cfg "$ROOTFS"/boot/efi/EFI/BOOT/grub.cfg
463 464
	;;
grub)
465
	echo 'GRUB_DISABLE_OS_PROBER=true' >> "$ROOTFS"/etc/sysconfig/grub2
466
	chroot "$ROOTFS" grub-mkconfig -o /boot/grub/grub.cfg
467 468 469
	case "$ARCH" in
		*86*)
			chroot "$ROOTFS" grub-install --target=i386-pc "$LOOPDEV"
470 471
			sed -i 's/initrdefi/initrd/g' "$ROOTFS/boot/grub/grub.cfg"
			sed -i 's/linuxefi/linux/g' "$ROOTFS/boot/grub/grub.cfg"
472
		;;
473 474 475 476
		ppc*)
			[ -z "$LOOPBOOTLOADER" ] ||
				chroot "$ROOTFS" grub-install --target=powerpc-ieee1275 \
				--no-nvram "$LOOPBOOTLOADER"
477
		;;
478
	esac
479
	sed -i '/GRUB_DISABLE_OS_PROBER=true/d' "$ROOTFS/etc/sysconfig/grub2"
480 481
	;;
esac
482 483

if [ -n "$SUDO_USER" ]; then
484
	chown "$SUDO_USER:$(id -g "$SUDO_USER")" "$IMG" ||:
485
fi
486 487 488
# maybe qemu interpreter was copied to chroot;
# this is no longer necessary, remove
rm -rf "$ROOTFS"/.host ||: