Linux From Scratch

China's 75th National Day

Today, it's China's 75th Birthday. Let me join the celebration. And, today, I'm going to build up a customized Linux operating system by following Linux From Scratch, with the MOST up-to-date kernel 6.11.1.

1. Preparation

1.1 Create a Disk Image File

1
2
3
4
➜  repo dd if=/dev/zero of=./lfs.img bs=1M count=65536 
65536+0 records in
65536+0 records out
68719476736 bytes (69 GB, 64 GiB) copied, 114.926 s, 598 MB/s
1
2
➜  repo ll lfs.img
65G -rw-rw-r-- 1 lvision lvision 64G Oct 1 00:01 lfs.img

1.2 Format the Disk Image

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  repo mkfs.ext4 lfs.img
mke2fs 1.47.0 (5-Feb-2023)
Discarding device blocks: done
Creating filesystem with 16777216 4k blocks and 4194304 inodes
Filesystem UUID: 7cffdaf4-2fa9-4688-8269-5a60e637b887
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424

Allocating group tables: done
Writing inode tables: done
Creating journal (131072 blocks): done
Writing superblocks and filesystem accounting information: done

1.3 Mount the Disk Image

1
2
➜  repo sudo mount -o loop lfs.img /mnt/lfs
➜ repo
1
2
3
➜  lfs df -h /mnt/lfs
Filesystem Size Used Avail Use% Mounted on
/dev/loop14 63G 27G 34G 45% /mnt/lfs

1.4 Login As Root

From now on, we need to login as user root.

1
2
3
➜  repo su - root                          
Password:
root@lvision-MS-7C60:~#

1.5 Exports

1
2
root@lvision-MS-7C60:~# export LFS=/mnt/lfs
root@lvision-MS-7C60:~# export LFS_TGT=$(uname -m)-lfs-linux-gnu

1.6 Download Source

1
2
3
4
root@lvision-MS-7C60:~# mkdir -v $LFS/sources
mkdir: created directory '/sources'
root@lvision-MS-7C60:~# chmod -v a+wt $LFS/sources
mode of '/sources' changed from 0755 (rwxr-xr-x) to 1777 (rwxrwxrwt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
root@lvision-MS-7C60:/mnt/docker/repo# wget --input-file=wget-list-sysv --continue --directory-prefix=$LFS/sources
--2024-08-27 03:49:06-- https://download.savannah.gnu.org/releases/acl/acl-2.3.2.tar.xz
Resolving download.savannah.gnu.org (download.savannah.gnu.org)... 2001:470:142:5::200, 209.51.188.200
Connecting to download.savannah.gnu.org (download.savannah.gnu.org)|2001:470:142:5::200|:443... connected.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://mirrors.ocf.berkeley.edu/nongnu/acl/acl-2.3.2.tar.xz [following]
--2024-08-27 03:49:06-- https://mirrors.ocf.berkeley.edu/nongnu/acl/acl-2.3.2.tar.xz
Resolving mirrors.ocf.berkeley.edu (mirrors.ocf.berkeley.edu)... 2607:f140:0:32::70, 169.229.200.70
Connecting to mirrors.ocf.berkeley.edu (mirrors.ocf.berkeley.edu)|2607:f140:0:32::70|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 371680 (363K) [application/octet-stream]
Saving to: ‘/mnt/lfs/sources/acl-2.3.2.tar.xz’

acl-2.3.2.tar.xz 100%[====================================================================================================================================>] 362.97K --.-KB/s in 0.1s

2024-08-27 03:49:07 (3.07 MB/s) - ‘/mnt/lfs/sources/acl-2.3.2.tar.xz’ saved [371680/371680]

--2024-08-27 03:49:07-- https://download.savannah.gnu.org/releases/attr/attr-2.5.2.tar.gz
Connecting to download.savannah.gnu.org (download.savannah.gnu.org)|2001:470:142:5::200|:443... connected.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://nongnu.askapache.com/attr/attr-2.5.2.tar.gz [following]
--2024-08-27 03:49:07-- https://nongnu.askapache.com/attr/attr-2.5.2.tar.gz
Resolving nongnu.askapache.com (nongnu.askapache.com)... 50.87.145.190
Connecting to nongnu.askapache.com (nongnu.askapache.com)|50.87.145.190|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 492539 (481K) [application/x-gzip]
Saving to: ‘/mnt/lfs/sources/attr-2.5.2.tar.gz’

attr-2.5.2.tar.gz 100%[====================================================================================================================================>] 481.00K 2.13MB/s in 0.2s

......
sysvinit-3.10-consolidated-1.patch 100%[====================================================================================================================================>] 2.41K --.-KB/s in 0s

2024-08-27 03:51:25 (2.39 GB/s) - ‘/mnt/lfs/sources/sysvinit-3.10-consolidated-1.patch’ saved [2464/2464]

FINISHED --2024-08-27 03:51:25--
Total wall clock time: 2m 20s
Downloaded: 94 files, 519M in 1m 32s (5.67 MB/s)
You have new mail in /var/mail/root
root@lvision-MS-7C60:/mnt/docker/repo#

1.7 Creating a Limited Directory Layout in the LFS Filesystem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
root@lvision-MS-7C60:/mnt/lfs# mkdir -pv $LFS/{etc,var} $LFS/usr/{bin,lib,sbin}

for i in bin lib sbin; do
ln -sv usr/$i $LFS/$i
done

case $(uname -m) in
x86_64) mkdir -pv $LFS/lib64 ;;
esac
mkdir: created directory '/mnt/lfs/etc'
mkdir: created directory '/mnt/lfs/var'
mkdir: created directory '/mnt/lfs/usr'
mkdir: created directory '/mnt/lfs/usr/bin'
mkdir: created directory '/mnt/lfs/usr/lib'
mkdir: created directory '/mnt/lfs/usr/sbin'
'/mnt/lfs/bin' -> 'usr/bin'
'/mnt/lfs/lib' -> 'usr/lib'
'/mnt/lfs/sbin' -> 'usr/sbin'
mkdir: created directory '/mnt/lfs/lib64'
root@lvision-MS-7C60:/mnt/lfs# mkdir -pv $LFS/tools
mkdir: created directory '/mnt/lfs/tools'
root@lvision-MS-7C60:/mnt/lfs#

1.8 Adding the LFS User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@lvision-MS-7C60:/mnt/lfs# groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
groupadd: group 'lfs' already exists
useradd: user 'lfs' already exists
root@lvision-MS-7C60:/mnt/lfs# chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools}
case $(uname -m) in
x86_64) chown -v lfs $LFS/lib64 ;;
esac
changed ownership of '/mnt/lfs/usr' from root to lfs
changed ownership of '/mnt/lfs/usr/bin' from root to lfs
changed ownership of '/mnt/lfs/usr/lib' from root to lfs
changed ownership of '/mnt/lfs/usr/sbin' from root to lfs
ownership of '/mnt/lfs/lib' retained as lfs
changed ownership of '/mnt/lfs/var' from root to lfs
changed ownership of '/mnt/lfs/etc' from root to lfs
ownership of '/mnt/lfs/bin' retained as lfs
ownership of '/mnt/lfs/sbin' retained as lfs
changed ownership of '/mnt/lfs/tools' from root to lfs
changed ownership of '/mnt/lfs/lib64' from root to lfs
1
2
root@lvision-MS-7C60:/mnt/lfs# su - lfs
lfs@lvision-MS-7C60:~$

2. Building the LFS Cross Toolchain and Temporary Tools

Please just strictily follow:

[!Note:] One key point to be emphasized: Entering Chroot for Chapter 7

1
2
3
4
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
1
2
3
4
5
6
7
8
9
10
root@lvision-MS-7C60:/mnt/lfs# mount -v --bind /dev $LFS/dev
mount -vt devpts devpts -o gid=5,mode=0620 $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
mount: /dev bound on /mnt/lfs/dev.
mount: devpts mounted on /mnt/lfs/dev/pts.
mount: proc mounted on /mnt/lfs/proc.
mount: sysfs mounted on /mnt/lfs/sys.
mount: tmpfs mounted on /mnt/lfs/run.
1
2
3
4
5
6
root@lvision-MS-7C60:/mnt/lfs# if [ -h $LFS/dev/shm ]; then
install -v -d -m 1777 $LFS$(realpath /dev/shm)
else
mount -vt tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm
fi
mount: tmpfs mounted on /mnt/lfs/dev/shm.
1
2
3
4
5
6
7
8
9
root@lvision-MS-7C60:/mnt/lfs# 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)" \
/bin/bash --login
(lfs chroot) root:/#

3. Building the LFS System

[!Note:] Two bugs MUST be fixed:

3.1 fatal error: getopt-cdefs.h: No such file or directory in LFS Chapter 8 - 8.35. Grep-3.11

The Bug:

1
2
3
4
5
In file included from grep.c:43:
../lib/getopt.h:84:10: fatal error: getopt-cdefs.h: No such file or directory
84 | #include <getopt-cdefs.h>
| ^~~~~~~~~~~~~~~~
compilation terminated.

The Solution:

1
(lfs chroot) root:/sources/grep-3.11# cp ../coreutils-9.5/lib/getopt-cdefs.h ./lib/

3.2 i386-pc vs. x86_64-efi in LFS Chapter 8 - 8.64. GRUB-2.12

In order to build GRUB, please strictly follow: BLFS Chapter 5 - GRUB-2.12 for EFI instead.

3.2.1. Build x86_64-efi Instead of i386-pc

Do NOT use the following for i386-pc:

1
2
3
4
./configure --prefix=/usr          \
--sysconfdir=/etc \
--disable-efiemu \
--disable-werror

Instead, use the following for x86_64-efi:

1
2
3
4
5
6
7
./configure --prefix=/usr        \
--sysconfdir=/etc \
--disable-efiemu \
--enable-grub-mkfont \
--with-platform=efi \
--target=x86_64 \
--disable-werror

After you obtained the following message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*******************************************************
GRUB2 will be compiled with following components:
Platform: x86_64-efi
With devmapper support: No (need libdevmapper header)
With memory debugging: No
With disk cache statistics: No
With boot time statistics: No
efiemu runtime: No (not available on efi)
grub-mkfont: No (need freetype2 library)
grub-mount: No (need fuse or fuse3 libraries)
starfield theme: No (No build-time grub-mkfont)
With libzfs support: No (need zfs library)
Build-time grub-mkfont: No (need freetype2 library)
Without unifont (no build-time grub-mkfont)
With liblzma from -llzma (support for XZ-compressed mips images)
With stack smashing protector: No
*******************************************************

we do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unset TARGET_CC &&
make
......
TARGET_OBJ2ELF= sh genmod.sh moddep.lst gcry_whirlpool.module build-grub-module-verifier gcry_whirlpool.mod
make[3]: Leaving directory '/sources/grub-2.12/grub-core'
make[2]: Leaving directory '/sources/grub-2.12/grub-core'
Making all in po
make[2]: Entering directory '/sources/grub-2.12/po'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/sources/grub-2.12/po'
Making all in docs
make[2]: Entering directory '/sources/grub-2.12/docs'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/sources/grub-2.12/docs'
Making all in util/bash-completion.d
make[2]: Entering directory '/sources/grub-2.12/util/bash-completion.d'
../../config.status --file=grub:grub-completion.bash.in
config.status: creating grub
make[2]: Leaving directory '/sources/grub-2.12/util/bash-completion.d'
make[1]: Leaving directory '/sources/grub-2.12'

3.2.2. efibootmgr and FreeType2 Preferrably Enabled

From BLFS Chapter 5 - efibootmgr-18, some required or recommended libraries are also recommended by me to install.

From BLFS Chapter 10 - FreeType-2.13.3, those recommended libraries are also recommended by me to install.

4. GRUB and UEFI Configuration

4.1 Find UUID of File lfs.img

1
2
3
4
➜  lfs sudo blkid ./lfs.img

[sudo] password for lvision:
./lfs.img: UUID="174bc795-5e06-4c0b-979f-7eccfca1ab10" BLOCK_SIZE="4096" TYPE="ext4"

4.2 Configure /etc/fstab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(lfs chroot) root:/boot# cat /etc/fstab
# Begin /etc/fstab

# file system mount-point type options dump fsck
# order

UUID=174bc795-5e06-4c0b-979f-7eccfca1ab10 / ext4 defaults 1 1
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run tmpfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
tmpfs /dev/shm tmpfs nosuid,nodev 0 0
cgroup2 /sys/fs/cgroup cgroup2 nosuid,noexec,nodev 0 0

# End /etc/fstab

4.3 Generate Configuration File /boot/grub/grub.cfg for GRUB

1
2
3
4
5
6
7
8
(lfs chroot) root:/boot# grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.11.1-lfs-12.2
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done

Let's take a look at the generated /boot/grub/grub.cfg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(lfs chroot) root:~# cat /boot/grub/grub.cfg
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

### BEGIN /etc/grub.d/00_header ###
if [ -s $prefix/grubenv ]; then
load_env
fi
if [ "${next_entry}" ] ; then
set default="${next_entry}"
set next_entry=
save_env next_entry
set boot_once=true
else
set default="0"
fi

if [ x"${feature_menuentry_id}" = xy ]; then
menuentry_id_option="--id"
else
menuentry_id_option=""
fi

export menuentry_id_option

if [ "${prev_saved_entry}" ]; then
set saved_entry="${prev_saved_entry}"
save_env saved_entry
set prev_saved_entry=
save_env prev_saved_entry
set boot_once=true
fi

function savedefault {
if [ -z "${boot_once}" ]; then
saved_entry="${chosen}"
save_env saved_entry
fi
}

function load_video {
if [ x$feature_all_video_module = xy ]; then
insmod all_video
else
insmod efi_gop
insmod efi_uga
insmod ieee1275_fb
insmod vbe
insmod vga
insmod video_bochs
insmod video_cirrus
fi
}

if [ x$feature_default_font_path = xy ] ; then
font=unicode
else
insmod ext2
search --no-floppy --fs-uuid --set=root 174bc795-5e06-4c0b-979f-7eccfca1ab10
font="/usr/share/grub/unicode.pf2"
fi

if loadfont $font ; then
set gfxmode=auto
load_video
insmod gfxterm
fi
terminal_output gfxterm
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=5
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=5
fi
### END /etc/grub.d/00_header ###

### BEGIN /etc/grub.d/10_linux ###
menuentry 'GNU/Linux' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-174bc795-5e06-4c0b-979f-7eccfca1ab10' {
load_video
insmod gzio
insmod ext2
search --no-floppy --fs-uuid --set=root 174bc795-5e06-4c0b-979f-7eccfca1ab10
echo 'Loading Linux 6.11.1-lfs-12.2 ...'
linux /boot/vmlinuz-6.11.1-lfs-12.2 root=/dev/loop14 ro
}
submenu 'Advanced options for GNU/Linux' $menuentry_id_option 'gnulinux-advanced-174bc795-5e06-4c0b-979f-7eccfca1ab10' {
menuentry 'GNU/Linux, with Linux 6.11.1-lfs-12.2' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.11.1-lfs-12.2-advanced-174bc795-5e06-4c0b-979f-7eccfca1ab10' {
load_video
insmod gzio
insmod ext2
search --no-floppy --fs-uuid --set=root 174bc795-5e06-4c0b-979f-7eccfca1ab10
echo 'Loading Linux 6.11.1-lfs-12.2 ...'
linux /boot/vmlinuz-6.11.1-lfs-12.2 root=/dev/loop14 ro
}
menuentry 'GNU/Linux, with Linux 6.11.1-lfs-12.2 (recovery mode)' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.11.1-lfs-12.2-recovery-174bc795-5e06-4c0b-979f-7eccfca1ab10' {
load_video
insmod gzio
insmod ext2
search --no-floppy --fs-uuid --set=root 174bc795-5e06-4c0b-979f-7eccfca1ab10
echo 'Loading Linux 6.11.1-lfs-12.2 ...'
linux /boot/vmlinuz-6.11.1-lfs-12.2 root=/dev/loop14 ro single
}
}

### END /etc/grub.d/10_linux ###

### BEGIN /etc/grub.d/20_linux_xen ###
### END /etc/grub.d/20_linux_xen ###

### BEGIN /etc/grub.d/25_bli ###
if [ "$grub_platform" = "efi" ]; then
insmod bli
fi
### END /etc/grub.d/25_bli ###

### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###

### BEGIN /etc/grub.d/30_uefi-firmware ###
if [ "$grub_platform" = "efi" ]; then
fwsetup --is-supported
if [ "$?" = 0 ]; then
menuentry 'UEFI Firmware Settings' $menuentry_id_option 'uefi-firmware' {
fwsetup
}
fi
fi
### END /etc/grub.d/30_uefi-firmware ###

### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###

### BEGIN /etc/grub.d/41_custom ###
if [ -f ${config_directory}/custom.cfg ]; then
source ${config_directory}/custom.cfg
elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
source $prefix/custom.cfg
fi
### END /etc/grub.d/41_custom ###
(lfs chroot) root:~#

4.4 Generate UEFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(lfs chroot) root:~# # Create a FAT filesystem image
dd if=/dev/zero of=/boot/efi.img bs=1M count=10
mkfs.fat /boot/efi.img

# Create the mount point if it doesn't already exist
mkdir -p /boot/efi

# Mount the image to simulate an EFI System Partition
mount -o loop /boot/efi.img /boot/efi
10+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.0157083 s, 668 MB/s
mkfs.fat 4.2 (2021-01-31)
(lfs chroot) root:~#

and then:

1
2
3
(lfs chroot) root:/boot# grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB --no-nvram --removable
Installing for x86_64-efi platform.
Installation finished. No error reported.

Now, you can see the following file:

1
2
(lfs chroot) root:/# ls /boot/efi/EFI/BOOT/BOOTX64.EFI 
/boot/efi/EFI/BOOT/BOOTX64.EFI```

4.5 Clear Up

  • Exit chroot
  • unmount efi.img

5. Demonstration

5.1 QEMU

1
➜  lfs sudo qemu-system-x86_64 -drive file=./lfs.img,format=raw,if=virtio -kernel /mnt/lfs/boot/vmlinuz-6.11.1-lfs-12.2 -append "root=/dev/vda rw rootdelay=5" -m 2048

LFS QEMU Logged In