Tuesday, July 23, 2019

5 - Philosophy, Intermediate System Build



Toucan Linux Project - 5



A Roll Your Own Distribution

Goal 1 – Base System

Stage 1 – Building the Tool Chain


The New World or The Old One

Before we begin we have one last major decision to make, and this is a VERY large one. What we decide will shape the rest of the project and the complexity of the final system. That decision is whether to use systemd or not. I will be right up front and say that TTLP will not use or support systemd. Those in the know might immediately think, “so TTLP won’t support the Gnome Desktop” but that’s not true. I have no intention of using the Gnome Desktop because one of the primary goals of TTLP is to keep it simple and the Gnome Desktop certainly isn’t simple. Nonetheless, it is possible to run Gnome without systemd if you really have the desire (using systemd-shim or elogind) but even that is beyond the complexity desired by the TTLP.

It certainly begs the question, why not systemd? As the creator of this project, I think I can answer that. There are two very good reasons to not use systemd and many lesser reasons. I’ll address the two big ones. First, complexity. It is a complex piece of code, and will only get more complex as time goes by. It was originally designed to replace the old initialization system of SysVInit to allow dependencies to handled better and perform tasks on startup in parallel. The aim was to solve some long fought problems in the original SysVInit.

Back in the days of Unix, only servers ran the operating system as there was no such thing as a “personal computer.” These systems were very rarely restarted without the pressure of uptime we have now (everything available 24-hours a day every day of the year plus the leap second) system boot speed wasn’t important at all. The startup program init ran one startup script at a time and dependencies were handled manually by specifying the order to run the scripts. But as desktops and, especially, laptops started running Linux, pressing the power button and having the system be ready for use three minutes later became a problem (along with added impatience “can’t wait a second due” to FOMO).

At any rate, systemd serves the purpose it was originally designed for (and has been extended to do far more now) and has a decent reputation for security. While it has a long record of bugs most of them have not created a security issues. It is good software, just not necessary software though its creator would like it to become so. It adds layers of complexity, makes system administration more difficult, and doesn't provide any functionality that has not already been solved but something far simpler. It will serve its purpose for most distros and probably not get in the way unless you do customization in case you don't like exactly what you get.

The SysV init program looks at a file called /etc/inittab that created various runlevels with each runlevel specifying what scripts to run. For instance, runlevel 1 was usually called single-user and brought the machine up to state where someone could login to the console, but remote terminals were ignored. While init could launch the programs (mostly daemons) that performed the work directly, instead the runlevels defined a script to ran and that script then used configuration files to further launch programs.

Here’s a fairly typical /etc/inittab:
# Begin /etc/inittab 
id:3:initdefault: 
si::sysinit:/etc/rc.d/init.d/rc S
l0:0:wait:/etc/rc.d/init.d/rc 0l1:S1:wait:/etc/rc.d/init.d/rc 1l2:2:wait:/etc/rc.d/init.d/rc 2l3:3:wait:/etc/rc.d/init.d/rc 3l4:4:wait:/etc/rc.d/init.d/rc 4l5:5:wait:/etc/rc.d/init.d/rc 5l6:6:wait:/etc/rc.d/init.d/rc 6
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now 
su:S016:once:/sbin/sulogin 
1:2345:respawn:/sbin/agetty --noclear tty1 96002:2345:respawn:/sbin/agetty tty2 96003:2345:respawn:/sbin/agetty tty3 96004:2345:respawn:/sbin/agetty tty4 96005:2345:respawn:/sbin/agetty tty5 96006:2345:respawn:/sbin/agetty tty6 9600
# End /etc/inittab


The first field (as separated by colons) is simply a unique name. The second is what runlevels the program should be launched in, the third species an action to be taken such as respawn the process if it dies (respawn) or execute once when the runlevel is specified and wait on it (wait), or execute it once without a wait (once), or others depending on the version of init. What the above reveals with the entries “l1”, “l2”, “l3”, “l4”, “l5” and “l6” that it runs a script called /etc/rc.d/init.d/rc followed by the runlevel. That script is the work horse of the system startup, it will run a series of other scripts, all located in /etc/rc.d/init.d that bring up the various sub-systems such as sshd for SSH logins. There are various versions of the rc script, but the standard is to find a directory named “rc” + runlevel and run scripts in it that either start with a “S” for on runlevel startup or “K” for on runlevel stop, short for Start and Kill. Those scripts are actually links to scripts located in /etc/rc.d/init.d and rc runs them with a given parameter based on the “S” or “K”. After the initial letter is a number that indicates the order to start the script, lower numbers start first. These scripts can also be run manually. To stop the SSH daemon sshd, use:
# /etc/rc.d/init.d/sshd stop
To start it use:
# /etc/rc.d/init.d/sshd start
This total system of init, the rc script and the subsystem scripts are what we call SysVInit.

One of the biggest problems of this is dependencies. For example, if network must be up before sshd is ran so the network script is given a lower number after the “S” in the runlevel directory. But suppose the network doesn’t start? Suppose there is a problem? The network script might return failure, but the rc script will continue to run the other scripts in order, ignoring failures except, perhaps, noting them in the system log. If you wanted the sshd startup script to do something different if the network was down (like wait for it to come up because it took some time for a DHCP configure), there would need to be additional logic in the sshd script making it complicated. The more dependencies the more complicated this script became, and as the system got more complicated (more subsystems) the dependency trail could become horrendous. As more and more daemons were added it became a nightmare to keep everything working properly.

The developers at RedHat decided to address this with systemd. It basically is a framework for services and came about the same time something was needed to populate the /dev directory when the devfs became standard in Linux (devfs creates devices as needed instead of the old method of using an inode to hold every possible device that might be needed using hard nodes). There goal was to have a built-in dependency system and to run programs in parallel for a faster startup. And initially it did very well at that, but it is a system of “plugins” that are part of systemd itself and later “helpers” can along to handle things completely unrelated to system startup. Soon ASCII logs went away as systemd handled system logging and you could no longer read system logs with a simple program like less. Now you needed systemctl and a filtering system to dig the logs out of a binary format that often was corrupted. If a system crashed and you needed to examine the logs from a another host, things got complicated quickly. The host had to have systemd, be configured to read the other system’s log, and because different plugins might log differently, this wasn’t always so easy. Thus systems started having a “rescue” mode that tried to help by bringing up a minimal system but if the system was so crashed that “rescue” mode failed, it became a big task to fix it.

The very nature of systemd as a system of plugins and helpers means anyone can write a plugin, which is not a simple “rc script” like SysVInit uses but complex code which has led to feature creep as more and more things were being placed in systemd. This certainly doesn’t follow the Unix Philosophy (https://en.wikipedia.org/wiki/Unix_philosophy ) and, in fact, later systemd added code to handle session logins and RedHat invited others to adopt it instead of ConsoleKit, a stand-alone session handler, to handle this function. Initially everyone refused but eventually in the Gnome Desktop release 3, they required systemd to handle sessions. This now meant the very complicated systemd was a necessary component of the desktop user interface (many, many layers above system initialization) and since systemd was also the startup script it meant the GUI desktop was now dependent on the 1.2 million lines of code that is systemd. In contrast init has roughly 8,000 lines of code.

Other systems have come out to handle dependencies and still stay simple. Of note is OpenRC by the Gentoo team. It does both parallel startup and dependencies. It is far more simple and does the one thing it is supposed to do, being the system up to a known state reliably. Even for small, simpler systems SySVInit is still fine. The base version of LFS starts in about 5 seconds on my laptop with NVMe and about 18 seconds on my laptop without an NVMe drive but an SSD or 32 seconds from the hard disk. Granted TTLP is simpler and doesn’t have to bring up 200 – 250 daemons just to allow the user to login so that along means a faster startup.

For now, the TTLP will be using the old world and SysVIinit as it will remain simple enough that it shouldn’t matter. As an experiment later, we will move to OpenRC and see how difficult that is. Fortunately we have eudev to populate the devfs without the need for systemd. If the Gnome Desktop becomes supported in TTLD it will do so through systemd-shim a project emulates systemd to enable using the helpers that high-level applications like the GUI desktop need.

As for me, I’ll stick to KISS as there are plenty of very usable and beautiful desktops available that don’t need the complexity of systemd. With that decision made, we now move on. We will be using the non-systemd version of LFS and following the development tree (which does change and might be different from the book). We are experimental and we are willing to accept development. The development book is a moving target, not everything is the same, but the LFS book is very well done. If something fails in it, check here to see if there is a difference.

Step 7 - Building the Intermediate System
The first step to the actual build of the new system is building a very small minimal Linux system that has just enough tools to build the final system. In this step we will start with the tool chain on the host system (MX Linux gcc, ld, glibc, etc.) to build a new tool chain for the new architecture defined by LFS (x86_64-lfs-linux-gnu). These will be placed in the /tools directory of the host which links to the /tools/ directory in the filesystem of the target system. When complete a very small Linux system will exist in /mnt/lfs/tools (as linked to /tools) with just enough programs to allow us to use the chroot tool to and build the final system.

Keep in mind that since /tools/bin is first in the path, every time a new program is compiled and installed in /tools/bin the next time we execute that program it will be found in /tools/bin and we will use the one on the intermediate system instead of the hosts. Little by little, we will replace each program from the host with a new version for the new intermediate architecture located in /tools (this includes libraries, linkers, compilers, and even system data). This intermediate system will also use the host’s kernel.
Again there is no reason to re-document this process when the Linux From Scratch project has done it so well. We will follow that process step by step with any additional commentary here in the TTLP.

First, check all the requirements listed in the last post. The target’s filesystem should be mounted under /mnt/lfs, the LFS and LFS_TGT variables should be set and exported, the lfs user (or another with the proper environment should available), etc. For all the following steps, you should be the lfs user (or the custom one you created – MINIMAL environment!).
For the following steps, assure that the version of each is the same. I do recommend following the development version of LFS because it does contain the newest files, but it also means that sometimes the instructions aren’t fully up to date, or the instructions can be incorrect as the team is working on it. You can use the last stable version 8.4 but there is no reason to do so. Should you run into problems, post a comment and we’ll solve it.

The first requirement is to make sure we are using bash. MX Linux will default to dash for most accounts. Solve this first by doing the following:
rm /bin/sh && ln -s /bin/bash /bin/sh && bash
Now that we are in bash, we are ready to begin. Start by changing to the root user first, and then
su – lfs
to become the lfs user. Check the environment (it never hurts):
env
and make sure both LFS and LFS_TGT are set to the proper values and /tools/bin is the start of PATH.

Step 7a – binutils


Let’s make a few helpers:
alias ut=”tar -xf”
alias del=”rm -rf”
The “ut” alias will call tar to unarchive a tarball. The del alias will delete a whole directory and all its contents without even asking (DANGER, double check EVERY command before pressing ENTER). Keep in mind the LFS book will give you the exact commands you need and you can easily copy from the browser and paste in your working shell. That is the best policy to follow. Add these two aliases to the startup with
echo 'alias ut=”tar -xf”' >> ~/.bashrc
echo 'alias del=”rm -rf”' >> ~/.bashrc
Here’s the full process for the first file binutils-2.32
cd /$LFS/sourcesut binutils-2.32.tar.xzcd binutils-2.32mkdir -v buildcd build../configure --prefix=/tools \--with-sysroot=$LFS \--with-lib-path=/tools/lib \--target=$LFS_TGT \--disable-nls \--disable-werrormakecase $(uname -m) in   x86_64) mkdir -v /tools/lib && ln -sv lib /tools/lib64 ;;esacmake installcd /$LFS/sourcesdel binutils-2.32
I prefer not to set MAKEOPTS as I have found some packages inadvertently clean this even though parallel builds work and with other packages it is necessary to disable parallel builds. Instead of setting MAKEOPTS I simply use:
make -j8
Also, TTLP is ONLY for the x86_64 architecture. It doesn’t not support 32-bit or multilib systems (at least not initially). If you need 32-bit within 64-bit, it is better to build a 32-bit jail and use it to run anything 32-bit. You will see in the LFS manual the lines:
case $(uname -m) in   x86_64) <some command> ;;esac
This won’t matter if you use the case statement instead of just running the internal command. Since you should be copying and pasting this shouldn’t matter.
Note that /tools/bin now has several programs in it, including ld, as, readelf, and strip.

Step 7b – GCC 9

From now on, perform the LFS commands exactly as you see them unless otherwise noted. Don’t forget to start in the $LFS/sources directory and untar the file (with ut or tar -xf), cd to the created directory, follow the LFS instructions, cd back to $LFS/sources, and delete the whole directory with del or the “rm -rf” command.

This is the longest compile of this stage.

Step 7c – Linux API Headers
If there is a newer stable kernel, you can choose it instead. Download it from www.kernel.org and follow the same directions. At the time of this writing LFS using Linux kernel 5.2.1 but you should pick 5.2.2 or the latest stable kernel unless the major (first) number changes.
To download:
cd $LFS/sourceswget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.2.2.tar.xz

Step 7d – Glibc 2.29

Step 7e – libstdc++

Step 7f – binutils 2.32 native intermediate compile

At this point we now have a working compiler at /tools/bin/gcc and working library tools at /tools/bin/ar and /tools/bin/ranlib. So far we have used the host’s compiler to do all the work, and the executables have the host’s native linker embedded in them. Now we perform a second compile of binutils with the newly created compiler to remove those dependencies.

This is done by changing the compiler tools using CC, AR, and RANLIB environment variables before running configure.

Make sure you have deleted the old binutils-2.32 directory.

Step 7g – GCC 9.10 native intermediate compile

As above this will break the link to the host’s linker and libraries. On the LFS page is a test at the bottom labeled: CAUTION! It is absolutely imperative you do that step to verify the compiler is native. If it says the compiler can’t build executables or the linker is wrong, then something is amiss. If not, the most likely source of trouble is the LFS_TGT variable is not set. If you made a mistake, I suggest deleting everything in $LFS/tools, verifying the environment, and starting at step 7a again.

Step 7h – TCL 8.6.9

tcl-8.6.9.tar.gz from https://downloads.sourceforge.net/tcl/tcl8.6.9-src.tar.gz

Step 7I – Expect 5.45.5

Step 7J – Dejagnu 1.6.2

Step 7K – M4 1.4.18

Step 7L – Ncurses 6.1

Step 7M – Bash 5.0

Step 7N – Bison 3.4.1

After this step you won’t need to install bison on the MX Linux host if for any reason you resume your session later after shutting down. It will use the bison just built in the intermediate system.

Step 7O – Bzip 1.0.8

Step 7P – Coreutils 8.31

Step 7Q – Diffutils 3.7

Step 7R – File 5.37

Step 7S – Findutils 4.6.0

Step 7T – Gawk 5.0.1

Step 7U – Gettext 0.20.1

Step 7V – Grep 3.3

Step 7W – Gzip 1.10

Step 7X – Make 4.2.1

Step 7Y – Patch 2.7.6

Step 7Z – Perl 5.30.0

Step 7AA – Python 3.7.4

Please note there are two Python packages. One starting with a capital ‘P’ the other lowercase. This step uses thee one with a capital ‘P.’

Step 7AB – Sed

sed-4.7.tar.xz from http://ftp.gnu.org/gnu/sed/sed-4.7.tar.xz

Step 7AC – Tar 1.32

tar-1.32.tar.xz from
Step 7AD – Texinfo 6.6

Step 7AE – Xz 5.2.4

Step 8 – Preparing the Intermediate System

The intermediate system is now built. It contains enough applications and configuration to allow use to begin to build the target system. We will repeat many of the same steps and compile even more code than in the intermediate system. The steps will be very similar except we will make things easier with the beginnings of a package manager.
The last thing we need to do is change the ownership from the ‘lfs’ user to ‘root’ for security reasons (as LFS explains to make sure we start with a safe user and group identifier).
Very easy, do the following:
chown -R root:root $LFS/tools
Next time we will use the new tool chain in the intermediate system to build the final system without any of the host’s programs (and ensure we are in no way linked to them.)

Copyright (C) 2019 by Michael R Stute

No comments:

Post a Comment