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).
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
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