Skip to content
ALB Wiki

Diff — LFS Temporary System

Comparing rev 6 → rev 7

{{infobox
| title = LFS: The Temporary System & the Chroot
| chapter = Linux Handbook, chapter 4 — temp tools cross-compiled, then the walls go up
| stage | LFS book chapters 6–7 — building inside the not-yet-system
| milestone = chroot: the moment $LFS becomes /
| hardest_idea = The chroot pivot — the host vanishes, the target becomes root
| needs = Chapter 3's cross-toolchain complete and sanity-checked
| next = LFS III — the real system packages
}}
**Chapter 4 builds the temporary system** — the last packages compiled with the cross-toolchain, but now running *for* the future system and installed into it — and then performs the maneuver the whole setup was scaffolding: **chroot into `$LFS`**, where the partition becomes the root filesystem and the host ceases to exist for the build. After this chapter you're inside the system you're building — there's no longer any host to contaminate or be contaminated by.
## Why a temporary system at all
The cross-toolchain (chapter 3) *produces* code for LFS, but it *runs on the host* — linked against the host's glibc, invoking the host's coreutils, the host's bash. To build the real system packages we need build tools that run *inside* the future system — compiled for `x86_64-lfs-linux-gnu`, linked against the fresh `$LFS` glibc. That's the temporary system: ~15 packages (M4, Ncurses, Bash, Coreutils, Diffutils, File, Findutils, Gawk, Grep, Gzip, Make, Patch, Sed, Tar, Xz, Binutils pass 2, GCC pass 2) cross-compiled and installed into `$LFS` — the minimal toolkit needed to build everything else once we're inside.
## Pass 2 — binutils and gcc again, now for real
Binutils and GCC are built a **second time** — same tarballs, re-extracted fresh, but now `--host=$LFS_TGT` and linked against the new glibc. Pass 2's products install with `--prefix=/usr` under `DESTDIR=$LFS` — they land in `$LFS/usr`, part of the real system's layout, while `/tools` retires. The build uses *them* now — the pass-1 cross-toolchain's job is done.
```sh
cd binutils-*/build   # fresh tree, fresh build dir
../configure --prefix=/usr        \
             --build=$(../config.guess) \
             --host=$LFS_TGT      \
             --disable-nls        \
             --enable-shared      \
             --enable-gprofng=no  \
             --disable-werror     \
             --enable-64-bit-bfd  \
             --enable-new-dtags   \
             --enable-default-hash-style=gnu
make && make DESTDIR=$LFS install
# clean libtool archives that pin the cross path
rm -v $LFS/usr/lib/lib{bfd,ctf,ctf-nobfd,gprofng,opcodes,sframe}.{a,la}
```
GCC pass 2 is the same shape — full languages, threads enabled, libstdc++ built properly — and it ends with the critical `cc` symlink:
```sh
ln -svf gcc $LFS/usr/bin/cc   # packages call 'cc' by convention
```
## The temp tools — the same ritual, ~15 times
Each temp package is the same pattern — cross-configure (`--host=$LFS_TGT`), `make`, `make DESTDIR=$LFS install`. A representative entry:
```sh
cd coreutils-*/
./configure --prefix=/usr     \
            --host=$LFS_TGT   \
            --build=$(build-aux/config.guess) \
            --enable-install-program=hostname \
            --enable-no-install-program=kill,uptime
make
make DESTDIR=$LFS install
# FHS placement fix-ups the book carries for several packages
mv -v $LFS/usr/bin/chroot $LFS/usr/sbin
mkdir -pv $LFS/usr/share/man/man8
mv -v $LFS/usr/share/man/man1/chroot.1 $LFS/usr/share/man/man8/chroot.8
sed -i 's/"usr\/bin"/"usr\/sbin"/' $LFS/usr/bin/chroot
```
The pattern repeats for M4, Ncurses (with its terminfo database and the `libncursesw` wide-char variants), Bash, Diffutils, File, Findutils, Gawk, Grep, Gzip, Make, Patch, Sed, Tar, Xz — each with its own handful of configure flags and FHS fix-ups. The book's chapter 6 is a checklist; the *skill* being taught is the ritual itself — by package ten you can nearly write the configure line yourself.
## The chroot — the pivot
With the temporary system installed, `$LFS` now contains a self-sufficient Linux userspace: its own bash, its own coreutils, its own make, its own gcc. Enter it:
```sh
# as ROOT — back to root for the pivot
chroot "$LFS" /usr/bin/env -i    \
    HOME=/root TERM="$TERM"      \
    PS1='(lfs chroot) \u:\w\$ '  \
    PATH=/usr/bin:/usr/sbin      \
    MAKEFLAGS="-j$(nproc)"       \
    TESTSUITEFLAGS="-j$(nproc)"  \
    /usr/bin/bash --login
```
Everything that follows runs inside. The prompt reads `(lfs chroot)` — the filesystem root *is* the partition. `$LFS` no longer exists as a path; it's now `/`. The `/tools` symlink trick pays off here: binaries compiled to look in `/tools/...` find exactly the directory they were built for.
## But first — the virtual filesystems
A chrooted userspace needs the kernel's virtual filesystems or nothing works — `/proc`, `/sys`, `/dev` are kernel interfaces, not real directories. Before chrooting (or the book's equivalent ordering), they must be **bind-mounted into** `$LFS` from the host:
```sh
# as root — populate the device tree
mkdir -pv $LFS/{dev,proc,sys,run}
mount -v --bind /dev $LFS/dev
mount -v --bind /dev/pts $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
# shm + pts for modern packages
if [ -h $LFS/dev/shm ]; then mkdir -pv $LFS/$(readlink $LFS/dev/shm);
else mount -t tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm; fi
```
This is the step people skip and then wonder why `configure` can't detect the system. **Inside the chroot there is no `/dev/null` unless you bound it** — testsuites, shells, everything needs it. The mounts are *bind mounts* — the host's kernel interfaces showing through into the chroot — so the temp system can interrogate the real hardware it's building for.
## The pass-2 configure grammar — the deep read
```sh
# binutils pass 2 — built for the new system, installed INTO it:
../configure --prefix=/usr \
             --build=$(../config.guess) \
             --host=$LFS_TGT \
             --disable-nls \
             --enable-shared \
             --enable-gprofng=no \
             --disable-werror \
             --enable-64-bit-bfd \
             --enable-new-dtags \
             --enable-default-hash-style=gnu
make && make DESTDIR=$LFS install
rm -v $LFS/usr/lib/lib{bfd,ctf,ctf-nobfd,gprofng,opcodes,sframe}.{a,la}
```
`--host=$LFS_TGT` is the change — pass 2 *runs on* the new system; `--enable-shared` returns (now there's a libc); `--enable-64-bit-bfd` gives the full 64-bit object format. The `.a`/`.la` removal clears the static+libtool archives — the new system uses shared libs.
**gcc pass 2** — the same shape: `--host=$LFS_TGT`, `DESTDIR=$LFS`, all languages enabled, threads on, libstdc++ real — plus `ln -svf gcc $LFS/usr/bin/cc`, the `cc` convention every package expects.
## Ownership handover
Before chrooting the book changes ownership of the entire `$LFS` tree **from lfs to root** — inside the chroot, root is the builder:
```sh
chown --from lfs -R root:root $LFS/{usr,lib,var,etc,bin,sbin,tools}
case $(uname -m) in
  x86_64) chown --from lfs -R root:root $LFS/lib64 ;;
esac
```
The lfs user's job ends here; the rest builds as chroot-root. (And later, the finished system's files get normalized to their real owners.)
## The temp tools — the full roster and why each
The ~15 packages that constitute the temp system, and their jobs:
| Package | The job inside the chroot |
| --- | --- |
| M4 | Macro processor — autoconf's engine |
| Ncurses | Terminal library + terminfo database |
| Bash | The shell everything's written for |
| Coreutils | ls/cp/mv/sort/etc — the vocabulary |
| Diffutils | diff — patch's partner |
| File | magic-byte identification |
| Findutils | find/xargs |
| Gawk | The awk language |
| Grep | Pattern matching |
| Gzip | Compression — every tarball |
| Make | The build driver |
| Patch | Applies the book's diffs |
| Sed | Stream editing |
| Tar | Unpacking everything |
| Xz | The modern compressor |
| Binutils pass 2 | The real linker/assembler |
| GCC pass 2 | The real compiler + libstdc++ |
Each is the same cross-configure dance — `--host=$LFS_TGT`, `make`, `DESTDIR=$LFS install` — and several carry FHS fix-ups (binaries moved between `/usr/bin`/`/usr/sbin`/`/bin` for layout compliance). By the tenth you can nearly write the configure line yourself — that's the point.
## The chroot environment — the deep read
```sh
chroot "$LFS" /usr/bin/env -i    \
    HOME=/root TERM="$TERM"      \
    PS1='(lfs chroot) \u:\w\$ '  \
    PATH=/usr/bin:/usr/sbin      \
    MAKEFLAGS="-j$(nproc)"       \
    TESTSUITEFLAGS="-j$(nproc)"  \
    /usr/bin/bash --login
```
Every piece earns its keep: `env -i` wipes the host's environment — no leaked variables inside; `HOME=/root` sets the chroot's home; `PS1` marks the prompt `(lfs chroot)` so you *know where you are*; `PATH` resets to the new system's paths (no `/tools` — the temp tools are used implicitly); `--login` gives a full login shell. The `TESTSUITEFLAGS` parallelizes the package tests.
## The re-entry ritual — leaving and coming back
You'll exit the chroot (reboot, sleep, fix the host) and must re-enter — the ritual:
```sh
# after any reboot — re-mount + re-bind + re-chroot
mount /dev/sda3 $LFS
mount --bind /dev $LFS/dev && mount --bind /dev/pts $LFS/dev/pts
mount -t proc proc $LFS/proc && mount -t sysfs sys $LFS/sys
mount -t tmpfs tmpfs $LFS/run
[ -h $LFS/dev/shm ] && mkdir -pv $LFS/$(readlink $LFS/dev/shm)
chroot "$LFS" /usr/bin/env -i HOME=/root TERM="$TERM" \
    PS1='(lfs chroot) \u:\w\$ ' PATH=/usr/bin:/usr/sbin \
    /usr/bin/bash --login
```
Write it as `enter-lfs.sh` — you'll run it many times. The build spans reboots; the chroot door works both directions.
## The directory skeleton
Inside the chroot, the FHS skeleton is laid before packages:
```sh
mkdir -pv /{boot,home,mnt,opt,srv}
mkdir -pv /etc/{opt,sysconfig}
mkdir -pv /lib/firmware
mkdir -pv /media/{floppy,cdrom}
mkdir -pv /usr/{,local/}{include,src}
mkdir -pv /usr/lib/locale
mkdir -pv /usr/local/{bin,lib,sbin}
mkdir -pv /usr/{,local/}share/{color,dict,doc,info,locale,man}
mkdir -pv /usr/{,local/}share/{misc,terminfo,zoneinfo}
mkdir -pv /usr/{,local/}share/man/man{1..8}
mkdir -pv /var/{cache,local,log,mail,opt,spool}
mkdir -pv /var/lib/{color,misc,locate}
ln -sfv /run /var/run && ln -sfv /run/lock /var/lock
install -dv -m 0750 /root
install -dv -m 1777 /tmp /var/tmp
```
Then the essential first files — `/etc/hosts`, `/etc/passwd` and `/etc/group` (the temporary versions), plus the log files programs expect:
```sh
echo "127.0.0.1 localhost $(hostname)" > /etc/hosts
cat > /etc/passwd << "EOF"
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/dev/null:/usr/bin/false
daemon:x:6:6:Daemon User:/dev/null:/usr/bin/false
messagebus:x:18:18:D-Bus Message Daemon User:/run/dbus:/usr/bin/false
uuidd:x:80:80:UUID Generation Daemon User:/dev/null:/usr/bin/false
nobody:x:65534:65534:Unprivileged User:/dev/null:/usr/bin/false
EOF
# /etc/group: root, bin, daemon, sys, kmem, tape, tty, daemon again...
touch /var/log/{btmp,lastlog,faillog,wtmp}
chgrp -v utmp /var/log/lastlog
chmod -v 664 /var/log/lastlog
chmod -v 600 /var/log/btmp
```
## Where this chapter goes wrong
| Symptom | Cause |
| --- | --- |
| Inside chroot: `no such file` for basic tools | Virtual filesystems not bind-mounted — /dev empty |
| `configure` can't detect the system | `/proc`, `/sys` not mounted inside |
| Testsuites all fail | `/dev/null`, `/dev/pts` missing |
| Files owned by nobody/weird UIDs | Ownership handover skipped |
| `cc: command not found` inside | The `cc`→gcc symlink skipped in gcc pass 2 |
| chroot complains "failed to run command" | `/usr/bin/env` path wrong in the chroot command, or bash not installed |
| Host binaries leaking in | PATH not reset in the `env -i` chroot line |
## Exercises
1. Explain why `/dev` must be a bind mount rather than `mkdir`.
2. From inside the chroot, prove you're isolated: compare `cat /etc/hostname`, `ls /home`, `df` against the host's.
3. Find three packages in the temp set with FHS fix-ups; explain one.
4. Exit the chroot, re-enter it — write the full mount+chroot script from memory.
5. Break `PATH` inside chroot and observe which "commands" still work (builtins vs binaries).
## Timeline
| Step | Result |
| --- | --- |
| binutils/gcc pass 2 | The real toolchain, LFS-linked |
| ~15 temp packages | Coreutils, bash, make… installed to $LFS |
| Virtual FS binds | /dev /proc /sys /run visible inside |
| Ownership handover | lfs → root |
| chroot | $LFS becomes / — you're inside |
| Skeleton | FHS dirs, passwd/group, log files |
| next | LFS III — the real system packages |
## References
- linuxfromscratch.org — chapters 6–7: cross-compiling temporary tools; entering chroot
+
- **Hosted mirror**: [/dist/books/](/dist/books/index.html) — the official LFS book mirrored on this site (v10.0–13.1, SysV + systemd editions; CC BY-NC-SA 2.0 / MIT, hosted verbatim with attribution)
- `chroot(2)`, `mount --bind` — the kernel mechanisms
- Filesystem Hierarchy Standard — the skeleton being laid
## See also
- [[LFS Preparation]] · [[LFS Cross-Toolchain]] · [[Linux]]
[[Category:Linux]]
[[Category:LFS]]
[[Category:Handbook]]