| | {{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 land in `/tools`, and the build uses *them* — 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,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. |
| | |
| | ## 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 |
| | - `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]] |
| | |