Putting kernel sources into an squashfs image
Category: Gentoo Tags: kernel, SquashFS, Portage
How to put your kernel sources into an squash fs image, managed transparently by portage.
Motivation
Reason for putting kernel sources into an squashfs image is simple reduction of space and especially inode usage.
When you run your system from a flash based media (thumb drive, sd card) like I do, gentoo runs with limited sources of space and inodes (at least if you use standard settings when creating filesystems).
The kernel sources need ~700 MB space and have ~52k files, compressed as squashfs image there is one file that needs ~100MB of space.
So in my efforts to reduce space and inode footprint of a full featured gentoo, kernel sources were a big target.
Goals
So what I want ?
I want portage to emerge kernel sources (gentoo-sources in my case) and store it as one squashfs image rather than all the files.
Well you can't build your kernel from an squashfs image... it needs to be mounted somewhere. So portage shall also create a mountpoint, mount the image and supply a script so sources get mounted automatically after a reboot.
Finally you can't build your kernel inside a readonly (mounted squashfs image) directory.
To ease of the process of building the kernel, portage shall create the build directory and supply it with a default config, so you can directly start of from that build directory.
Base
To let portage create the image we need to hook into the emerge process. This is done via putting a script into /etc/portage/env/sys-kernel/... and defining functions that get called by portage in the emerge process.
You need to make sure that squashfs is enabled within your current kernel as well as desired compression algorithms and the loop device.
File systems --->
[*] Miscellaneous filesystems --->
<*> SquashFS 4.0 - Squashed file system support
Device Drivers --->
[*] Block devices --->
<*> Loopback device support
Also you need to emerge sys-fs/squashfs-tools so you can create squashfs images.
As I use gentoo-sources my script is /etc/portage/env/sys-kernel/gentoo-sources.
#!/usr/bin/env bash
# /etc/portage/env/sys-kernel/gentoo-sources
Creating the squashfs image
I use the pre_src_install
hook to create the squashfs. It is executed after sources are extraced from archive and patches are applied.
pre_src_install() {
ebegin "Build squashfs image from kernel sources"
mksquashfs "${WORKDIR}/linux-${KV_FULL}" "${WORKDIR}/linux-${KV_FULL}.sqfs" -comp xz -b 1M -Xdict-size 100%
retval=$?
eend $retval
[[ "$retval" -ne "0" ]] && return
ebegin "Remove sources"
find ${WORKDIR}/linux-${KV_FULL} -mindepth 1 -delete
eend $?
}
In line 3 the squashfs image is created from the kernel sources. If this is successfull everything within the kernel directory is deleted (line 8).
What remains in the workdir is the squashfs image and the kernel folder. That is what will be installed by portage.
Creating build directory and mount script
As described before the kernel is build outside of its source directory. So we can already create the build directory. Also an squashfs image needs to be mounted to get access to its contents. Both happens within the post_src_install
hook.
post_src_install() {
ebegin "Create build directory"
dodir "${EROOT}/usr/src/build-${KV_FULL}"
ebegin "Create mount skript"
dodir /etc/local.d
MOUNT_SKRIPT=${ED}/etc/local.d/mount-${KV_FULL}.start
echo "#!/bin/bash
# mounting squashfs image of kernel sources
if [[ -f \"${EROOT}/usr/src/linux-${KV_FULL}.sqfs\" ]] && [[ -d \"${EROOT}/usr/src/linux-${KV_FULL}\" ]]; then
grep \"${EROOT}/usr/src/linux-${KV_FULL}\" \"/proc/self/mountinfo\" >/dev/null
if [[ \"\$?\" -eq \"1\" ]]; then
echo \"Mount ${EROOT}/usr/src/linux-${KV_FULL}\"
mount -t squashfs,ro \"${EROOT}/usr/src/linux-${KV_FULL}.sqfs\" \"${EROOT}/usr/src/linux-${KV_FULL}\"
retval=\$?
exit \$retval
fi
fi
exit 1" > $MOUNT_SKRIPT
chmod 755 $MOUNT_SKRIPT
}
The mount script will be installed into /etc/local.d where it will be run automatically by local service. So the image will be mounted each time the system is booted.
Pre install
Before the files are installed into final location we have to make sure there isn't already a kernel image mounted. This might happen if you reinstall the kernel sources.
pre_pkg_preinst() {
grep "${EROOT}/usr/src/linux-${KV_FULL}" "/proc/self/mountinfo" >/dev/null
if [[ "$?" -eq "0" ]]; then
ebegin "Unmount ${EROOT}/usr/src/linux-${KV_FULL}"
umount "${EROOT}/usr/src/linux-${KV_FULL}"
eend $?
fi
}
Mount and populate
Squashfs image has been copied into location, kernel directory and build directory have been created. Now it is time to populate the build directory.
post_pkg_postinst() {
ebegin "Mount ${EROOT}/usr/src/linux-${KV_FULL}"
${EROOT}/etc/local.d/mount-${KV_FULL}.start >/dev/null
retval=$?
eend $retval
[[ "$retval" -ne "0" ]] && return
ebegin "Populate build directory with default config"
OLDPWD=$(pwd)
cd "${EROOT}/usr/src/linux-${KV_FULL}"
OLDARCH=$ARCH
unset ARCH
make O="${EROOT}/usr/src/build-${KV_FULL}" defconfig >/dev/null
eend $?
if [[ -f "${EROOT}/proc/config.gz" ]]; then
ebegin "Found running kernel config"
ebegin "Copying running kernel config"
zcat "${EROOT}/proc/config.gz" > "${EROOT}/usr/src/build-${KV_FULL}/.config"
eend $?
ebegin "Incorporate current kernel config"
make O="${EROOT}/usr/src/build-${KV_FULL}" oldconfig >/dev/null
eend $?
fi
ARCH=$OLDARCH
cd "${OLDPWD}"
use symlink && SYMLINK=1
if [[ "$SYMLINK" == "1" ]]; then
[[ -h ${EROOT}/usr/src/linux ]] && rm ${EROOT}/usr/src/linux
ln -sf ${EROOT}/usr/src/build-${KV_FULL} ${EROOT}/usr/src/linux
fi
}
First the mount script is called so kernels sources are available.
Next the kernel make script is run to create a default config. To do so the ARCH
enviroment variable has to be reset. Portage and kernel have different definitions of ARCH
, so let the kernel itself determine the correct value.
You might be asking why create a default config ... just because the build directory gets populated an later on, you can directly build the kernel from within the build directory ... no need to do make O="..."
anymore.
Finally a symlink to the build directory is created if requested by use flag. This way packages that rely on a specific kernel configuration have access to it.
Cleanup
Installing and setup has been dealt with. Now one has to make sure the custom setup can be uninstalled too.
To do so the images has to unmounted first.
pre_pkg_prerm() {
grep "${EROOT}/usr/src/linux-${KV_FULL}" "/proc/self/mountinfo" >/dev/null
if [[ "$?" -eq "0" ]]; then
ebegin "Unmount ${EROOT}/usr/src/linux-${KV_FULL}"
umount "${EROOT}/usr/src/linux-${KV_FULL}"
eend $?
fi
}
For convenience I also like the build directory to be removed on uninstall. Note this is against common gentoo practise only to remove files that have been installed!
Even more important your old configuration will be removed (as it resides in your build directory) so make sure your kernel exposes its config or preserve your config by keep it somewhere safe or do like me, only remove the old kernel sources after a new kernel has been successfully built.
post_pkg_postrm() {
if [[ -d "${EROOT}/usr/src/build-${KV_FULL}" ]]; then
ebegin "Remove build directory"
rm -rf "${EROOT}/usr/src/build-${KV_FULL}"
eend $?
fi
}
Conclusion
This setup is pretty much tailored to my needs. But I hope it gives some ideas of how to do such a thing.
I'll be glad for any comments.
05/31/2021
Changes in portage variables
Somehow portage variables ED and EROOT have no trailing slashes anymore. I changed the script to make it work again.
05/20/2022
Put the script on Github
For easier management of updates and bug reporting I put the script on Github.