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.


Download latest gentoo-sources script@github