2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-08-03 07:57:02 +00:00

Use multi-threaded unsquashfs to extract untethered images

`unsquashfs` can use multiple CPU cores during image extraction, significantly reducing boot time.
For example the whole boot time from PXE to shell on a 8-core VM, from approximately 45 seconds to 20 seconds.

This PR adds `squashfs-tools` as a dependency. Since the package is smaller than 1 MB, the additional image size is justified by the performance improvement.

For backward compatibility, the existing `cp`-based extraction method is used when `unsquashfs` is unavailable, such as with images built before this change.

The extraction logic has also been moved into the common functions and is now shared between EL9, EL10, and Ubuntu.

Both untethered `squashfs` images and `confluent_multisquash` images are supported.

Images must be rebuilt to include `unsquashfs` and benefit from the faster extraction path.
This commit is contained in:
Markus Hilger
2026-07-16 16:55:16 +02:00
parent 8b0fb03c66
commit cfc4490fe1
12 changed files with 128 additions and 80 deletions
@@ -212,5 +212,106 @@ run_remote_config() {
echo '---------------------------------------------------------------------------'
return
}
startlegacyprogress() {
[ -n "$progresspid" ] && return
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
progresspid=$!
}
extractmultisquash() {
multisquashrc=1
while read -r _ partsrc partmount; do
normalizedmount=${partmount%/}
[ "$normalizedmount" = /mnt/remote ] || continue
if unsquashfs -force -d /sysroot "$partsrc"; then
multisquashrc=0
fi
break
done < /tmp/mountparts.sh
[ $multisquashrc -eq 0 ] || return 1
while read -r _ partsrc partmount; do
normalizedmount=${partmount%/}
[ "$normalizedmount" = /mnt/remote ] && continue
partdest=/sysroot${normalizedmount#/mnt/remote}
mkdir -p "$partdest"
if ! unsquashfs -force -d "$partdest" "$partsrc"; then
return 1
fi
done < /tmp/mountparts.sh
}
cleanupremotemounts() {
if [ -f /tmp/mountparts.sh ]; then
for partmount in $(awk '{ mounts[NR] = $3 } END { for (idx = NR; idx > 0; idx--) print mounts[idx] }' /tmp/mountparts.sh); do
umount "$partmount"
done
for partdev in $(awk '{ devices[NR] = $NF } END { for (idx = NR; idx > 0; idx--) print devices[idx] }' /tmp/setupmount.sh); do
dmsetup remove "$partdev"
done
else
umount /mnt/remote
fi
}
# Extract an untethered/uncompressed diskless root image into /sysroot and
# tear down the source devices; expects rootimgformat, mountsrc, and loopdev
# from imageboot.sh and the image content mounted under /mnt/remote.
extract_untethered_rootimg() {
extractrc=1
progresspid=
if [ "$rootimgformat" = squashfs ] && command -v unsquashfs > /dev/null 2>&1; then
echo "Decrypting and extracting root filesystem in parallel"
if unsquashfs -force -d /sysroot "$mountsrc"; then
extractrc=0
else
echo "Parallel root filesystem extraction failed, retrying with cp"
fi
elif [ "$rootimgformat" = confluent_multisquash ] && command -v unsquashfs > /dev/null 2>&1; then
echo "Decrypting and extracting multipart root filesystem in parallel"
if extractmultisquash; then
extractrc=0
else
echo "Parallel multipart root filesystem extraction failed, retrying with cp"
fi
fi
if [ $extractrc -ne 0 ]; then
rm -rf /sysroot/* /sysroot/.[!.]* /sysroot/..?*
startlegacyprogress
if cp -a /mnt/remote/. /sysroot/; then
extractrc=0
fi
fi
if [ $extractrc -ne 0 ]; then
if [ -n "$progresspid" ]; then
kill "$progresspid" 2> /dev/null || true
wait "$progresspid" 2> /dev/null || true
progresspid=
fi
echo "Failed to extract the root filesystem"
fi
cleanupremotemounts
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
if [ -n "$progresspid" ]; then
wait "$progresspid"
echo -e "Decrypting and extracting root filesystem: 100%"
fi
return $extractrc
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -29,9 +29,10 @@ if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
rootimgformat=$(awk -F': ' '/^Format:/ {print $2; exit}' /tmp/rootimg.info)
if [ "$rootimgformat" = squashfs ]; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
elif [ "$rootimgformat" = confluent_multisquash ]; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
@@ -54,27 +55,7 @@ if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(get
else
mount -t tmpfs disklessroot /sysroot
fi
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -ax /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
extract_untethered_rootimg || return 1
else
TETHERED=1
mount -o discard /dev/zram0 /mnt/overlay
@@ -183,4 +164,3 @@ if grep debugssh /proc/cmdline >& /dev/null; then
else
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
fi
@@ -29,9 +29,10 @@ if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
rootimgformat=$(awk -F': ' '/^Format:/ {print $2; exit}' /tmp/rootimg.info)
if [ "$rootimgformat" = squashfs ]; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
elif [ "$rootimgformat" = confluent_multisquash ]; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
@@ -54,27 +55,7 @@ if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(get
else
mount -t tmpfs disklessroot /sysroot
fi
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -ax /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
extract_untethered_rootimg || return 1
else
TETHERED=1
mount -o discard /dev/zram0 /mnt/overlay
@@ -36,9 +36,10 @@ if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
rootimgformat=$(awk -F': ' '/^Format:/ {print $2; exit}' /tmp/rootimg.info)
if [ "$rootimgformat" = squashfs ]; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
elif [ "$rootimgformat" = confluent_multisquash ]; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
@@ -64,27 +65,7 @@ elif grep -q confluent_imagemethod=uncompressed /proc/cmdline; then
mount -t tmpfs disklessroot /sysroot
fi
if [ "$TETHERED" = 0 ]; then
echo -en "Decrypting and extracting root filesystem: 0%\r"
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
dstsz=$(du -sk /sysroot | awk '{print $1}')
pct=$((dstsz * 100 / srcsz))
if [ $pct -gt 99 ]; then
pct=99
fi
echo -en "Decrypting and extracting root filesystem: $pct%\r"
sleep 0.25
done &
cp -a /mnt/remote/* /sysroot/
umount /mnt/remote
if [ -e /dev/mapper/cryptimg ]; then
dmsetup remove cryptimg
fi
losetup -d $loopdev
rm /mnt/remoteimg/rootimg.sfs
umount /mnt/remoteimg
wait
echo -e "Decrypting and extracting root filesystem: 100%"
extract_untethered_rootimg || return 1
elif [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
+1 -2
View File
@@ -3,7 +3,7 @@ dracut_install /lib64/libtss2-tcti-device.so.0
dracut_install tpm2_create tpm2_pcrread tpm2_createpolicy tpm2_createprimary
dracut_install tpm2_load tpm2_unseal tpm2_getcap tpm2_evictcontrol
dracut_install tpm2_pcrextend tpm2_policypcr tpm2_flushcontext tpm2_startauthsession
dracut_install curl openssl tar cpio gzip lsmod ethtool xz lsmod ethtool
dracut_install curl openssl tar cpio gzip lsmod ethtool xz unsquashfs lsmod ethtool
dracut_install modprobe touch echo cut wc bash uniq grep ip hostname
dracut_install awk egrep dirname expr sort
dracut_install ssh sshd reboot parted mkfs mkfs.ext4 mkfs.xfs xfs_db mkswap
@@ -33,4 +33,3 @@ inst /usr/lib/dracut/modules.d/45net-lib/net-lib.sh /lib/net-lib.sh
# network mount, and disk imaging helpers can come from a second stage
# this is narrowly focused on getting network up and fetching images
# and those images may opt to do something with cloning or whatever
+2 -2
View File
@@ -19,8 +19,8 @@ fuse-libs
libnl3
dhcpcd
openssh-keysign
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr attr
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr attr squashfs-tools
%onlyarch x86_64
shim-x64.x86_64 grub2-efi-x64
%onlyarch aarch64
shim-aa64.aarch64 grub2-efi-aa64
shim-aa64.aarch64 grub2-efi-aa64
+1 -1
View File
@@ -19,4 +19,4 @@ fuse-libs
libnl3
dhcpcd
openssh-keysign
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr shim-aa64 grub2-efi-aa64 attr
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr shim-aa64 grub2-efi-aa64 attr squashfs-tools
+1 -2
View File
@@ -3,7 +3,7 @@ dracut_install /lib64/libtss2-tcti-device.so.0
dracut_install tpm2_create tpm2_pcrread tpm2_createpolicy tpm2_createprimary
dracut_install tpm2_load tpm2_unseal tpm2_getcap tpm2_evictcontrol
dracut_install tpm2_pcrextend tpm2_policypcr tpm2_flushcontext tpm2_startauthsession
dracut_install curl openssl tar cpio gzip lsmod ethtool xz lsmod ethtool
dracut_install curl openssl tar cpio gzip lsmod ethtool xz unsquashfs lsmod ethtool
dracut_install modprobe touch echo cut wc bash uniq grep ip hostname
dracut_install awk egrep dirname expr sort
dracut_install ssh sshd reboot parted mkfs mkfs.ext4 mkfs.xfs xfs_db mkswap
@@ -32,4 +32,3 @@ inst /usr/lib/dracut/modules.d/40network/net-lib.sh /lib/net-lib.sh
# network mount, and disk imaging helpers can come from a second stage
# this is narrowly focused on getting network up and fetching images
# and those images may opt to do something with cloning or whatever
+2 -2
View File
@@ -18,8 +18,8 @@ xfsprogs
e2fsprogs
fuse-libs
libnl3
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl dhclient openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr attr
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl dhclient openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr attr squashfs-tools
%onlyarch x86_64
shim-x64.x86_64 grub2-efi-x64
%onlyarch aarch64
shim-aa64.aarch64 grub2-efi-aa64
shim-aa64.aarch64 grub2-efi-aa64
+1 -1
View File
@@ -17,4 +17,4 @@ xfsprogs
e2fsprogs
fuse-libs
libnl3
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl dhclient openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr shim-aa64 grub2-efi-aa64 attr
chrony kernel net-tools nfs-utils openssh-server rsync tar util-linux python3 tar dracut dracut-network ethtool parted openssl dhclient openssh-clients bash vim-minimal rpm iputils lvm2 efibootmgr shim-aa64 grub2-efi-aa64 attr squashfs-tools
+6
View File
@@ -683,6 +683,8 @@ class DebHandler(OsHandler):
needpkgs = []
if not os.path.exists(os.path.join(hostpath, 'usr/bin/tpm2_getcap')):
needpkgs.append('tpm2-tools')
if not os.path.exists(os.path.join(hostpath, 'usr/bin/unsquashfs')):
needpkgs.append('squashfs-tools')
lfuses = glob.glob(os.path.join(hostpath, '/lib/*/libfuse.so.2'))
if not lfuses:
needpkgs.append('libfuse2')
@@ -779,6 +781,10 @@ class ElHandler(OsHandler):
needpkgs.append('dhcp-client')
if not os.path.exists(os.path.join(hostpath, 'usr/sbin/mount.nfs')):
needpkgs.append('nfs-utils')
if (self.oscategory in ('el9', 'el10') and
not os.path.exists(os.path.join(hostpath, 'usr/sbin/unsquashfs')) and
not os.path.exists(os.path.join(hostpath, 'usr/bin/unsquashfs'))):
needpkgs.append('squashfs-tools')
if needpkgs:
needapt = 'Missing packages needed in target for capture, to add required packages: dnf install ' + ' '.join(needpkgs)
self.captureprereqs.append(needapt)
@@ -33,6 +33,7 @@ copy_exec /usr/bin/tpm2_pcrextend
copy_exec /usr/bin/ssh-keygen
copy_exec /usr/sbin/sshd
copy_exec /usr/sbin/mkfs.xfs
copy_exec /usr/bin/unsquashfs
[ -e /usr/lib/openssh/sshd-session ] && copy_exec /usr/lib/openssh/sshd-session
[ -e /usr/lib/x86_64-linux-gnu/libfuse.so.2 ] && copy_exec /usr/lib/x86_64-linux-gnu/libfuse.so.2
[ -e /usr/lib/aarch64-linux-gnu/libfuse.so.2 ] && copy_exec /usr/lib/aarch64-linux-gnu/libfuse.so.2