Skip to content
ALB Wiki

Diff — LFS: Preparing the Build

Comparing rev 1 → rev 2

{{infobox
| title = LFS: Preparing the Build
-
| chapter = Linux Handbook, chapter 2 — the host, the partition, the toolchain staging ground
-
| needs = A running Linux host · ~30 GB free · ~90 source tarballs · a weekend
+
| chapter = Linux Handbook, chapter 2 — the host, the partition, the sources, the staging ground
+
| needs = A running glibc-based Linux host · ~30 GB free · ~90 source tarballs · a weekend
| hardest_part = The chicken-and-egg: a Linux must already exist to build a Linux
| next = LFS I — the cross-toolchain
}}
-
**Linux From Scratch** begins before a single package compiles — with a host system, a dedicated partition, ninety source tarballs verified by checksum, and a build user wearing the right environment. This chapter is the setup the LFS book assumes and newcomers most often fumble: the physical preparation of the ground where a new operating system will be grown. Get this right and the build is stamina; get it wrong and nothing after it works.
+
**Linux From Scratch** begins before a single package compiles — with a host system, a dedicated partition, ninety source tarballs verified by checksum, and a build user wearing a sterilized environment. This chapter is the setup the LFS book assumes and newcomers most often fumble — plus the traps nobody warns you about: the musl-libc host that can't work, the bash-hash cache that poisons toolchains, the `/tools` symlink that is the build's escape hatch from its own host. Get this right and the build is stamina; get it wrong and nothing after it works.
## The chicken-and-egg problem
-
You cannot compile an operating system without an operating system. The Linux you're building needs a **host** — an already-running Linux with a working compiler, core utilities, and disk space — to compile against, against which the new system's early pieces link. The host isn't cheating; it's how all software births. Cross-compilation's whole art is the careful severance of host influence — chapter 13 (the cross-toolchain) is where that severance is engineered. This chapter supplies the host.
+
You cannot compile an operating system without an operating system. The Linux you're building needs a **host** — an already-running Linux with a working compiler, core utilities, and disk space — to compile against, and against which the new system's early pieces link. The host isn't cheating; it's how all software births. Cross-compilation's whole art is the careful severance of host influence — chapter 13 (the cross-toolchain) is where that severance is engineered. This chapter supplies the host.
## Step 1 — The host system
Three valid hosts, in ascending realism:
- **A virtual machine** — QEMU/KVM or VirtualBox, 2+ CPUs, 4 GB RAM, **30+ GB disk**. The professional choice: snapshots let you rewind any mistake, and nothing you break is real.
- **A dedicated partition or spare machine** — the fullest experience; real GRUB, real hardware detection, real consequences.
- **A live distribution on real hardware** — possible but fragile; the live environment vanishes on reboot.
-
We use **Slackware or Slint** as the host throughout this handbook — its installer teaches partitioning by asking you to do it, its pkgtools leave the machinery visible, and it arrives with a complete development environment (gcc, make, binutils) already aboard. Any modern Linux works; the book's checks below tell you if yours qualifies.
+
We use **Slackware or Slint** as the host throughout this handbook — its installer teaches partitioning by asking you to do it, its pkgtools leave the machinery visible, and it arrives with a complete development environment (gcc, make, binutils) already aboard. Any modern Linux works; the checks below tell you if yours qualifies.
+
### The trap nobody warns about: your host's libc
+
+
**The host must run glibc.** LFS builds against GNU libc's headers and toolchain assumptions — an Alpine/musl or other non-glibc host will fail obscurely and early, in ways that look like compiler bugs but are actually libc mismatches. Check first:
+
+
```sh
+
ldd --version | head -1       # "ldd (GNU libc) 2.xx" = good; musl = wrong host
+
```
+
+
Docker images based on `alpine`, musl-based distros, and BusyBox-only environments are disqualified as hosts — fine as *targets* of curiosity, wrong as LFS mothers. WSL works for practicing tools but is a trap for the real build — no real partitions, no loop devices by default; the workaround (a loop-mounted disk *image* inside WSL) is documented below.
+
### Verify the host qualifies
The LFS book carries a `version_check.sh` ritual — the minimum versions the build requires. On your host, prove each:
```sh
bash --version | head -1          # >= 3.2
binutils: ld --version | head -1  # >= 2.13.1
bison --version | head -1
bzip2 --version
coreutils: chown --version | head -1  # >= 6.9
diffutils: diff --version | head -1
findutils: find --version | head -1   # >= 4.2.31
-
gawk --version | head -1              # >= 4.0.1 (gawk specifically, not mawk)
+
gawk --version | head -1              # >= 4.0.1 — gawk specifically, NOT mawk
gcc --version | head -1               # >= 5.2
grep --version | head -1
gzip --version
uname -r                              # kernel >= 5.4
m4 --version | head -1
make --version | head -1              # >= 4.0
patch --version | head -1
perl -V:version                       # >= 5.8.8
python3 --version                     # >= 3.4
sed --version | head -1
tar --version | head -1               # >= 1.22
-
texinfo: makeinfo --version | head -1
+
texinfo: makeinfo --version | head -1 # the one everyone forgets
xz --version
```
-
Every check must pass — the build's earliest packages lean on all of them. Slackware ships a clean pass out of the box; on Debian-family hosts `apt install build-essential gawk bison texinfo` closes the usual gaps.
+
Every check must pass — the build's earliest packages lean on all of them. Slackware ships a clean pass out of the box; on Debian-family hosts `apt install build-essential gawk bison texinfo` closes the usual gaps. Two subtleties: **gawk** is required, not mawk (Debian defaults to mawk — install gawk explicitly), and **texinfo's `makeinfo`** is the classic missing package — several builds fail silently without it, weeks of confusion for a one-package fix.
## Step 2 — The partition
-
The new system gets its own home — a dedicated partition it will fill:
+
The new system gets its own home — a dedicated partition it will fill. Before carving, one architectural decision: **BIOS/MBR or UEFI/GPT**. UEFI hosts want GPT (`gdisk`) and eventually an EFI System Partition; BIOS-era hosts take MBR (`fdisk`) and a simpler boot. For a VM you choose when creating the machine — this handbook covers both, with the GPT path primary.
```sh
# as root — find the target disk
fdisk -l
lsblk
-
# carve it — gdisk on GPT systems, fdisk on legacy
-
gdisk /dev/sda          # new partition, ~30G, type 8300 (Linux filesystem)
+
# carve it — gdisk on GPT, fdisk on MBR
+
gdisk /dev/sda          # new partition ~30G, type 8300 (Linux filesystem)
+
# UEFI builders add a small ESP: ~300M, type EF00, FAT32
-
# format it
+
# format the LFS partition
mkfs.ext4 /dev/sda3
+
# and the ESP if UEFI: mkfs.vfat /dev/sda1
# the staging ground
mkdir -pv /mnt/lfs
mount /dev/sda3 /mnt/lfs
```
-
**A swap partition helps** if your host is lean; an existing swap can be shared — `swapon /dev/sda2` joins it to the build environment. On a VM, a second virtual disk (`-hdb`/`virtio`) is cleanest — zero risk to the host's own disk.
+
**Why ext4 and not something exotic**: the build writes hundreds of thousands of small files — ext4's journaling and tooling (`e2fsprogs`, which you build *inside* LFS) are the boring, correct choice. Btrfs/XFS work but add variables nobody needs on a first build.
+
**A swap partition helps** if your host is lean — an existing swap can be shared: `swapon /dev/sda2`. On a VM, a second virtual disk is cleanest — zero risk to the host's own disk.
+
Make it persistent — the build reboots:
```sh
echo '/dev/sda3  /mnt/lfs  ext4  defaults  0 0' >> /etc/fstab
```
+
### The no-partition path: a loop-mounted image file
+
+
No spare partition? Build inside a **disk image file** — the WSL workaround and the safest experiment:
+
+
```sh
+
dd if=/dev/zero of=lfs.img bs=1G count=32 status=progress   # 32 GB sparse-able image
+
mkfs.ext4 lfs.img
+
mkdir -pv /mnt/lfs
+
mount -o loop lfs.img /mnt/lfs
+
```
+
+
The whole LFS system lives in one file — backup it with `cp`, snapshot with `cp --reflink` on CoW filesystems, delete when done. Slightly slower than a real partition; infinitely safer.
+
## Step 3 — $LFS and the sources tree
Everything hangs off one environment variable, `$LFS` — the mountpoint of the partition:
```sh
export LFS=/mnt/lfs
mkdir -pv $LFS/sources
chmod -v a+wt $LFS/sources        # world-writable + sticky, /tmp-style
```
Download the book's package manifest — `wget-list` (the ~90 tarballs) and `md5sums` (their checksums) — into `$LFS/sources`, then fetch:
```sh
cd $LFS/sources
wget --input-file=wget-list --continue --directory-prefix=$LFS/sources
-
# verify every tarball against its checksum before building
+
# verify every tarball against its checksum BEFORE building
pushd $LFS/sources && md5sum -c md5sums && popd
```
-
**Check every checksum before building.** A corrupt tarball discovered three chapters in is a weekend lost; `md5sum -c` catches it now. Where upstream ships GPG signatures, `gpg --verify` them too — the sources become the system's DNA.
+
**Check every checksum before building.** A corrupt tarball discovered three chapters in is a weekend lost; `md5sum -c` catches it now. Where upstream ships GPG signatures, `gpg --verify` them too — the sources become the system's DNA. `--continue` resumes partial downloads — the ~90-package fetch survives dropped connections. For bulk downloads `aria2c -i wget-list` parallelizes across mirrors; `wget` is the book's way and everywhere.
+
### The offline angle nobody plans for
+
+
Download *all* tarballs before starting, and keep `$LFS/sources` — mid-build network failures are common, and an LFS build with a dead network stalls forever waiting on a tarball. `rsync -a $LFS/sources/ backups/lfs-sources/` once at the start is a two-minute insurance policy.
+
## Step 4 — The toolchain staging area
```sh
mkdir -pv $LFS/tools      # where the cross-toolchain will live
ln -sv $LFS/tools /       # /tools symlink — the trick the whole build pivots on
```
-
That symlink is subtle and load-bearing: `/tools` resolves to `$LFS/tools`, so paths baked into early packages point at `/tools/...` — which resolves identically inside the chroot later (where `$LFS` *is* `/`). It's how the build escapes its host's filesystem while still being built by it.
+
That symlink is subtle and load-bearing — worth understanding before you make it. The cross-toolchain's early binaries are compiled with paths baked in as `/tools/...`. On the host, `/tools` resolves to `$LFS/tools` through this symlink. Later, inside the chroot — where `$LFS` *is* the root — `/tools` resolves to the *same* directory, same absolute path, no rewriting. It's how the build escapes its host's filesystem while still being built by it — a one-line filesystem judo move that makes the entire chroot transition seamless.
## Step 5 — The lfs user
-
Build as a dedicated user, never root — one mis-typed `make install` as root can overwrite your *host's* system directories:
+
Build as a dedicated user, never root — one mis-typed `make install` as root overwrites your *host's* `/usr` and the host dies mid-build. This has happened to everyone who skipped the warning.
```sh
groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
passwd lfs
# hand the staging ground to lfs
chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools}
chown -v lfs $LFS/sources
```
-
Then set its environment — login as `su - lfs` and write the two files that make every build correct:
+
Two details nobody thinks of: `-s /bin/bash` *must* be explicit — on Debian-family hosts new users default to dash, and dash is not bash (the builds require bash); `-k /dev/null` gives lfs an empty skeleton directory so no host dotfiles leak into its environment. And the UID you create now will own files inside the finished system until the book's cleanup stages normalize ownership — a small oddity you'll see in `ls -l` output later.
+
### The sterile environment
+
+
Login as `su - lfs` (the `-` matters — a full login shell) and write the two files that make every build correct:
+
```sh
cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF
```
-
`env -i` gives lfs a *sterile* environment — no inherited host variables leaking into the build.
+
`env -i` gives lfs a *sterile* environment — every login respawns bash with only HOME, TERM, and PS1: no inherited host variables, no leaked `LD_LIBRARY_PATH`, no distro-local `CFLAGS` quietly poisoning cross-compilation. `TERM` is kept so your terminal still renders correctly.
```sh
cat > ~/.bashrc << "EOF"
set +h                     # no hashing — always re-find binaries fresh
umask 022                  # sane default permissions
LFS=/mnt/lfs
LC_ALL=POSIX               # predictable locale for reproducible builds
LFS_TGT=x86_64-lfs-linux-gnu   # the target triplet — our system's name
PATH=$LFS/tools/bin:$PATH  # our toolchain first, host's second
MAKEFLAGS='-j$(nproc)'     # parallel builds across all cores
export LFS LC_ALL LFS_TGT PATH MAKEFLAGS
EOF
source ~/.bash_profile
```
-
Each line earns its place: `set +h` disables bash's binary-path hash cache — critical when freshly-built tools replace earlier ones mid-build; `LC_ALL=POSIX` makes builds byte-reproducible; `LFS_TGT` is the target triplet every cross-compiled package is built `--target` against; `MAKEFLAGS` the parallelism knob that turns hours into minutes.
+
Each line earns its place, and three of them defend against specific ghosts:
+
- **`set +h`** — bash caches binary locations in a hash table (`hash` shows it). Mid-build, your fresh `$LFS/tools/bin/gcc` replaces the host's gcc — but a hashed bash would keep running the *old* one. `set +h` forces PATH re-resolution every call. This single line prevents a class of "my new toolchain isn't being used" bugs that look like curses.
+
- **`LC_ALL=POSIX`** — builds that sort, grep, and awk differently under different locales produce *different systems*. POSIX locale makes every build byte-reproducible — the difference between a toolchain that works and one whose configure probes disagree with themselves.
+
- **`LFS_TGT`** — the GNU target triplet (`x86_64-lfs-linux-gnu`) naming your new system's architecture-vendor-OS — every `--target=$LFS_TGT` in the cross-toolchain builds *for* it; it's how `gcc` learns it is building a foreign system's compiler.
+
- **`MAKEFLAGS='-j$(nproc)'`** — parallel builds across every core. Hours become minutes — with one honest caveat: a handful of old packages have racy Makefiles; if a build fails weirdly with `-j`, retry serial before debugging anything else.
+
## Step 6 — The final checks
```sh
su - lfs
echo $LFS $LFS_TGT          # must print /mnt/lfs and the triplet
which gcc                   # host's gcc — until tools/ exists
mount | grep lfs            # partition still mounted
df -h $LFS                  # space confirmed
+
id                          # you are lfs — NOT root
```
Then, and only then, the build begins.
## What an SBU is
The book measures build costs in **SBUs** — *Standard Build Units*: the time the first package (binutils pass 1) takes on your machine, used as the unit for every other package's estimate. "This package takes 5 SBU" means five times your binutils-1 build. On a modern multi-core host an SBU is a few minutes; measure yours once and every estimate in the book becomes concrete.
## The two books — choose now
LFS ships two editions: **SysVinit** and **systemd**. The choice is your new system's PID-1 — made now because later chapters fork on it. For this handbook we follow **SysVinit** — matching Slackware/Slint's BSD-style rc scripts, and because `rc.M` is a file you can read while a systemd unit graph is not — with systemd's parallel path flagged wherever the books diverge. Either teaches the same machine.
## Where builds go wrong here
| Symptom | Almost always |
| --- | --- |
| `make install` wrote to /usr on the *host* | You were root — always build as lfs |
| Tools not found after install | `set +h` missing — bash cached old paths |
| Weird locale-dependent failures | `LC_ALL` unset — export `POSIX` |
| Host leaked into a build | `.bash_profile` lacking `env -i` — sterile env required |
| `/tools` empty inside chroot | The `/tools` symlink wasn't made on the host side |
| Build can't write $LFS | Partition unmounted after reboot — fstab entry |
| Tarball fails to extract | Checksum it *before* — corrupt download |
+
| Configure can't find libc | Host is musl-based — wrong host entirely |
+
| lfs's shell behaves oddly | Defaulted to dash — `-s /bin/bash` required |
+
| A package fails only with `-j` | Racy Makefile — retry `make` serial |
+
| `makeinfo` missing mid-build | texinfo — the forgotten requirement |
## Exercises
-
1. Run the full version-check roster on your host; note every version that would disqualify it.
+
1. Run the full version-check roster on your host; note every version that would disqualify it — and check `ldd --version` for the glibc trap.
2. Partition a scratch VM disk by hand — `gdisk`, `mkfs.ext4`, mount, `fstab`, reboot, verify it returns.
-
3. Download five real upstream tarballs (zlib, file, coreutils…) into `$LFS/sources`; `md5sum -c` them.
-
4. As `lfs`, deliberately break `$PATH` — then explain why `set +h` exists.
-
5. Time a trivial `./configure && make` and compute your SBU multiplier intuition.
+
3. Build the loop-image variant (`lfs.img`) alongside the partition — understand both paths.
+
4. Download five real upstream tarballs into `$LFS/sources`; `md5sum -c` them; then corrupt one deliberately and watch the check catch it.
+
5. As `lfs`, deliberately break `$PATH` — then explain why `set +h` exists.
+
6. Time a trivial `./configure && make` and compute your SBU intuition.
## Timeline
| Step | What you did |
| --- | --- |
-
| Host | Verified a qualifying Linux with dev tools |
+
| Host | Verified a glibc-based Linux with dev tools |
| Partition | Carved, formatted, mounted `/mnt/lfs`, persisted in fstab |
| Sources | ~90 tarballs, checksum-verified, in `$LFS/sources` |
| Staging | `$LFS/tools` + the `/tools` symlink |
| User | `lfs` with sterile env, `LFS_TGT`, `MAKEFLAGS`, `set +h` |
| Next | LFS I — the cross-toolchain: binutils → gcc → glibc |
## References
- linuxfromscratch.org — LFS book (SysV & systemd editions), chapters 2–4: host requirements, preparing a partition, packages & patches, final preparations
- The `version_check.sh` convention — the book's own host-qualification script
- wget-list / md5sums manifests shipped with each LFS release
## See also
- [[Linux]] — chapter 1: the landscape and the handbook map
- [[Speakup]] — the eyes-free Linux path
[[Category:Linux]]
[[Category:Operating systems]]
[[Category:Handbook]]
[[Category:LFS]]