Pull to refresh

LFS: Темная сторона силы. Часть 2

Reading time13 min
Views18K

Предисловие


Итак, в предыдущей статье мы начали собирать LFS, остановившись на том, что собрали временную систему, располагающую всем необходимым для дальнейшей сборки инструментарием.

Теперь мы будем собирать основную систему, выполняя по пути необходимые для работы настройки. Поскольку эта статья продолжает цикл об LFS, без особых предисловий приступим к делу.

Однако, прежде чем двигаться дальше, немного отойдем от классической схемы, предлагаемой авторами книги и сделаем вот что

$ su - lfs
$ wget http://roy.marples.name/downloads/dhcpcd/dhcpcd-6.7.1.tar.bz2 --directory-prefix=$LFS/sources
$ wget http://www.linuxfromscratch.org/blfs/downloads/7.7/blfs-bootscripts-20150304.tar.bz2 --directory-prefix=$LFS/sources
$ logout


Дело в том, что стандартная документация, касающаяся сборки базовой системы не описывает процесс настройки сети в случае, когда вы будете получать ip-адрес от DNS провайдера, или в случае, если динамический ip выдает вам ваш домашний роутер. Или в том случае, если Вы выполняете сборку под ВМ, имеющей доступ к сети через NAT.

Поэтому после того как мы соберем всё и вся, мы дополнительно установим и настроим DHCP-клиент, который позволит свеженькой системе получить ip сразу после перезагрузки и иметь таки доступ в сеть. Этот шаг относится уже к книге BLFS.

1. Chroot, Великий и Ужасный


Прежде всего снова сделаем пользователя root владельцем каталога $LFS/tools

$ su - root
# export LFS=/mnt/lfs
# chown -R root:root $LFS/tools


До этого он принадлежал пользователю lfs, но этот пользователь есть в хост-системе и его нет (там пока нет ни одного пользователя!) в системе которую мы собираем. Все дальнейшие действия будут выполнятся от root в том разделе, где собирается система. В этой связи нам необходимо решить следующие проблемы
  • Проблема #1 (основная): Все пути, которые мы будем использовать должны начинаться от корня реальной системы. Это жизненно не обходимо для нормальной работы всего собранного окружения и ядра системы.
  • Проблема #2 (сопутствующая): с учетом того, что пути установки бинарных файлов в собираемой системе и хост-системе совпадают (или почти совпадают), надо «отгородить» системные каталоги хоста и собираемой системы друг от друга.

Обе проблемы решает команда chroot, которая позволяет «на ходу» изменить корневой каталог системы. Поле выполнения chroot на раздел $LFS все пути к каталогам LFS будут начинаться с "/", то есть мы создаем видимость работы в корне собираемой системы. Те каталоги, что лежат выше $LFS (то есть каталога /mnt/lfs) будут напрочь отрезаны и доступа к ним не будет. Это замечательно — все действия, выполняемые нами от root не затронут хост-систему. Но нам понадобится доступ к VFS — виртуальной файловой системе хост-машины, образованную каталогами /dev, /proc, /sys и /run. Мы же помним об одной из базовых концепций unix — «всё есть файл». Нам понадобится доступ к оборудованию и системным ресурсам, а он производится посредством работы с объектами VFS.

Доступ к каталогам хост-системы из chroot возможен, если предварительно примонтировать необходимые каталоги к соответствующим каталогам LFS. Для этого выполним следующие операции. Создадим точки монтирования VFS

# mkdir -pv $LFS/{dev,proc,sys,run}


Создаем символьные устройства /dev/console и /dev/null

# mknod -m 600 $LFS/dev/console c 5 1
# mknod -m 666 $LFS/dev/null c 1 3


Ключ -m определяет права досутпа к файлу устройства; с — указывает на то, что устройство является символьным, а последующие цифры — мажорный и минорный номер устройства. Первый характеризует тип символьного устройства, второй — номер устройства в системе.

/dev/console обеспечивает работу системы с терминалом, /dev/null чаще всего используется для получения данных из пустого файла, или для перенаправления вывода «в никуда».

Теперь специальным образом смонтируем виртуальную файловую систему. Такое монтирование часто называют «биндингом» — при его выполнении содержимое монтируемой директории отображается на каталог, указанный в качестве точки монтирования

# mount -v --bind /dev $LFS/dev


Теперь наша временная система будет иметь доступ к файлам устройств. Монтируем остальные узлы VFS

# mount -vt devpts devpts $LFS/dev/pts -o gid=5,mode=620
# mount -vt proc proc $LFS/proc
# mount -vt sysfs sysfs $LFS/sys
# mount -vt tmpfs tmpfs $LFS/run


/dev/pts — файловая система, обеспечивающая доступ к псевдотерминалам. Она монтируется с идентификатором gid=5 группы tty c правами доступа 620 (-rw- -r- ---).

/proc — виртуальная файловая система, содержащая информацию о процессах, запущенных в системе.

/sys — виртуальная файловая система, добавляющая в пространство пользователя информацию об устройствах и драйверах, присутствующих в системе

/run — виртуальная файловая система, предназначенная для создания временных файлов на ранних этапах загрузки системы. Раньше подобные файлы создавались в /dev, однако по инициативе Ленарда Поттеринга, в связи с продвижением systemd для указанной цели был добавлен дополнительный каталог.

Кроме того, в некоторых хост системах в /dev могут существовать символические ссылки на /run/shm — временные файлы для доступа к разделяемой памяти. Проверяем их наличие на нашем хосте, и при необходимости создаем в во временной системе

$ if [ -h $LFS/dev/shm ]; then
>   mkdir -pv $LFS/$(readlink $LFS/dev/shm)
> fi


Теперь после «чрута» мы получим структуру, схематично показанную на нижеследующем рисунке.

Дерево каталогов LFS по отношению к хост-системе


Собственно теперь выполняем вход во временную систему

# chroot "$LFS" /tools/bin/env -i \
>    HOME=/root                  \
>    TERM="$TERM"                \
>    PS1='\u:\w\$ '              \
>    PATH=/bin:/usr/bin:/sbin:/usr/sbin:/tools/bin \
>    /tools/bin/bash --login +h


Команда меняет корневой каталог системы на $LFS и выполняет две команды — /tools/bin/env — создает новое «чистое» пользовательское окружение, присваивая некоторые переменные: HOME — положение домашнего каталога; TERM — тип терминала; PS1 — формат приглашения командной строки; PATH — список путей к исполняемым файлам; /tools/bin/bash --login +h — запускает новый экземпляр командной оболочки, причем запуск выполняется как вход в отдельный пользовательский сеанс с отключением хеширования путей к исполняемым файлам. Заметьте — мы используем программы (env и bash) собранные нами на предыдущем этапе.

После выполнения команд мы получаем следующий результат
I have no name!:/# ls
dev  proc  run	sources  sys  tools

Корень системы успешно изменился, а в приглашении командной строки стоит предупреждение «у меня нет имени!», которое возникает из-за отсутствия в системе файла /etc/passwd. Пусть это не смущает вас, файл мы скоро создадим.

Примечание: Сборка системы может занять продолжительное время, так что команды монтирования VFS и chroot можно объединить в скрипт, дабы упростить вход в собираемую систему после выключения компьютера.

2. Создание необходимых каталогов и файлов


Теперь нам необходимо сформировать дерево каталогов нашей системы

# mkdir -pv /{bin,boot,etc/{opt,sysconfig},home,lib/firmware,mnt,opt}
# mkdir -pv /{media/{floppy,cdrom},sbin,srv,var}
# install -dv -m 0750 /root
# install -dv -m 1777 /tmp /var/tmp
# mkdir -pv /usr/{,local/}{bin,include,lib,sbin,src}
# mkdir -pv /usr/{,local/}share/{color,dict,doc,info,locale,man}
# mkdir -v  /usr/{,local/}share/{misc,terminfo,zoneinfo}
# mkdir -v  /usr/libexec
# mkdir -pv /usr/{,local/}share/man/man{1..8}

# case $(uname -m) in
> x86_64) ln -sv lib /lib64
>         ln -sv lib /usr/lib64
>         ln -sv lib /usr/local/lib64 ;;
> esac

# mkdir -v /var/{log,mail,spool}
# ln -sv /run /var/run
# ln -sv /run/lock /var/lock
# mkdir -pv /var/{opt,cache,lib/{color,misc,locate},local}


Каталоги, создаваемые командой mkdir по-умолчанию имеют права доступа 755 (drwx r-x r-x). Команда install используется в данном случае для создания каталогов со специфическим занчением аттрибутов доступа: 0750 (drwx r-x ---) для /root — домашнего каталога суперпользователя; 1777 (drwx rwx rwx) для /tmp и /var/tmp — каталоги для размещения временных файлов, к которым должен быть доступ у всех без исключения пользователей системы. Однако необходим запрет на перемещение файлов другого пользователя, содержащихся во временном каталоге. Для этого взводится так называемый sticky bit, указывающий на то, что удаление файлов во временной директории разрешено лишь их владельцу.

В случае, если собирается 64-разрядная система, необходимо создать символические ссылки, ведущие в каталог /lib содержащий системные библиотеки. Кроме того, некоторые программы используют жестко заданные пути к необходимым компонентам системы. Пока мы работаем во временной системе эти пути будут отличатся (например все исполняемые файлы пока что расположены в каталоге /tools/bin) поэтому необходимо создать символические ссылки, исправляющие это несоответствие

# ln -sv /tools/bin/{bash,cat,echo,pwd,stty} /bin
# ln -sv /tools/bin/perl /usr/bin
# ln -sv /tools/lib/libgcc_s.so{,.1} /usr/lib
# ln -sv /tools/lib/libstdc++.so{,.6} /usr/lib
# sed 's/tools/usr/' /tools/lib/libstdc++.la > /usr/lib/libstdc++.la
# ln -sv bash /bin/sh


Традиционно в линуксах список монтированных файловых систем располагался в файле /etc/mtab. Однако современные ядра используют для этого виртуальную файловую систему /proc. Создадим символическую ссылку

# ln -sv /proc/self/mounts /etc/mtab


для программ, продолжающих использовать /etc/mtab.

Теперь нам необходимо создать файл /etc/passwd, содержащий список пользователей, зарегистрированных в системе.

# cat > /etc/passwd << "EOF"
> root:x:0:0:root:/root:/bin/bash
> bin:x:1:1:bin:/dev/null:/bin/false
> daemon:x:6:6:Daemon User:/dev/null:/bin/false
> messagebus:x:18:18:D-Bus Message Daemon User:/var/run/dbus:/bin/false
> nobody:x:99:99:Unprivileged User:/dev/null:/bin/false
> EOF


используя для этого перенаправление стандартного ввода в файл, так как во временной системе у нас нет текстового редактора. Этот текстовый файл имеет следующий формат — каждая его строка содержит информацию о пользователе в виде

<login>:<password>:<UID>:<GID>:<GECOS>:<home>:<shell>


  • login — имя пользователя в системе
  • password — хеш пароля, раньше хранившийся здесь, но теперь вынесенный в отдельный файл /etc/shadow, недоступный для чтения обычным пользователем. Вместо него теперь ставится заполнитель «x»
  • UID — идентификатор пользователя — число от 0 до 232 — 1. Собственно, основное назначение данного файла — это сопоставление логина и идентификатора пользователя.
  • GID — идентификатор группы по-умолчанию, в которую включен пользователь
  • GECOS — информационное поле, хранящее дополнительную информацию о пользователе. Оно не имеет четкого синтаксиса и по сути может содержать любые комментарии к данной учетной записи
  • home — абсолютный путь к домашнему каталогу пользователя
  • shell — командная оболочка, используемая в сеансе данного пользователя


В качестве разделителя полей используется двоеточие. Наш файл содержит описание суперпользователя root, а так же специальных пользователей bin, daemon, messagebus от имени которых запускаются некоторые программы и сервисы.

Отдельное значение имеет непривилегированный пользователь nobody — от его имени мы будем запускать тесты некоторых собираемых компонентов системы. Так как у нас пока что нет доступа к утилите useradd этого пользователя, равно как и других, мы создаем вручную.

Вручную создаем и необходимый набор пользовательских групп в файле /etc/group

# cat > /etc/group << "EOF"
> root:x:0:
> bin:x:1:daemon
> sys:x:2:
> kmem:x:3:
> tape:x:4:
> tty:x:5:
> daemon:x:6:
> floppy:x:7:
> disk:x:8:
> lp:x:9:
> dialout:x:10:
> audio:x:11:
> video:x:12:
> utmp:x:13:
> usb:x:14:
> cdrom:x:15:
> adm:x:16:
> messagebus:x:18:
> systemd-journal:x:23:
> input:x:24:
> mail:x:34:
> nogroup:x:99:
> users:x:999:
> EOF


Каждая строчка описывает отдельную группу в формате

<group name>:<group passwd>:<GID>:<users list>


  • group name — символьное имя группы
  • group passwd — пароль группы — содержит «x» ибо данное поле устарело и не используется в настоящее время
  • GID — идентификатор группы
  • users list — список пользователей-участников группы, разделенный запятыми


Кратко опишу назначение каждой из групп

  • root — группа суперпользователей с правами root. В нормальных условиях сюда входит лишь сам пользователь root
  • bin, sys — эти группы сохранились как исторически существовавшие, некоторые программы не работают, если их нет в системе
  • kmem — группа доступа к памяти ядра
  • tape — группа доступа к нодам устройств
  • tty — группа доступа к терминальным устройствам (в том числе и консоль)
  • daemon — группа запуска непривилегированных демонов
  • floppy, cdrom, disk — группы доступа к различным типам дисковых накопителей
  • lp — группа доступа к принтеру, исторически «сидевшему» на параллельном порту LPT.
  • dialout — группа доступа к устройствам, выполняющим «дозвон» при сетевом соединении (модемы)
  • audio, video — доступ к мультимедиа устройствам
  • usb — группа доступа к шине USB
  • adm — группа доступа к просмотру системных логов в каталоге /var/log
  • messagebus — группа доступа к шине передачи сообщений между устройствами D-BUS
  • systemd-journal — доступ к журналам (логам) systemd
  • input — доступ к устройствам пользовательского ввода
  • mail — доступ к сервисам работы с электронной почтой
  • nogroup — пустая группа
  • users — обычные, непривилегированные, пользователи системы


Теперь можно выполнить повторный вход в пользовательский сеанс

exec /tools/bin/bash --login +h


и, вуаля, теперь системе известно имя пользователя, от имени которого выполнен вход

root:/# 


Инициализируем некоторые, необходимые системе файлы логов, назначая соответствующие права

root:/#  touch /var/log/{btmp,lastlog,wtmp}
root:/#  chgrp -v utmp /var/log/lastlog
root:/#  chmod -v 664  /var/log/lastlog
root:/#  chmod -v 600  /var/log/btmp


3. Долгий и нудный процесс сборки GNU-окружения...


Данный этап — наиболее трудоемкий и продолжительный. Вам предстоит вручную собрать все необходимые пакеты GNU-окружения — тот минимальный набор программ, позволяющий получить работоспособную linux-систему.

Надо сказать, по мере продвижения по списку собираемых пакетов нарастает нервное напряжение — список довольно обширен, а операции достаточно однообразны. В какой-то момент в голове возникает вопрос — а на черта оно мне надо вообще??? Тут главное собраться с мыслями и усилием воли преодолеть уныние — всё начатое необходимо доводить до конца.

Последовательность сборки пакетов построена так, что на текущем этапе удовлетворены все зависимости, необходимые для его выполнения. Так что порядок сборки нарушать нельзя.

Сборка каждого пакета сопровождается подробной инструкцией, предусматривающей в общем случае

  • наложение патчей на исходный код пакета. Все необходимые патчи выкачаны нами и расположены в каталоге /sources
  • правку исходников, выполняемую утилитой sed. Тут желательно вникнуть в смысл регулярных выражений, определяющих шаблон правки, так как бездумный ввод команд наверняка приведет к ошибкам. А sed не выводит сообщений об ошибках — если вы ошиблись при вводе шаблона, то операция может не выполнится или выполнится неверно, что будет фатально для процесса сборки.
  • конфигурирование. Отдельное внимание префиксам директорий — при установке все исполняемые бинарники, библиотеки и документация должны оказаться на своих местах.
  • копирование двоичных файлов, назначение прав файлам и директориям, создание символических ссылок. Некоторые ссылки могут вести к пока что не существующим точкам ФС, так что синтаксис команд создания симлинков следует понимать буквально и не допускать самодеятельности при построение путей. Следуем инструкции неукоснительно!


Ну и наконец самое важное — тесты! При сборке временной системы мы игнорировали тесты, во-первых из-за того, что нам были недоступны средства тестирования, во-вторых в тестах не было необходимости. Теперь же, когда мы собираем окончательные версии системного ПО, мы обязаны всесторонне проверить результат сборки на предмет правильного функционирования. Особенно это касается компилятора, стандартных библиотек и вспомогательных средств сборки ПО (ассемблер и компоновщик).

Не стоит пропускать тестирование если в инструкции есть пометка о том, что оно должно быть выполнено. Тестирование отнимает много времени, например сборка GCC с выполнением тестов происходит в течение 63 SBU, что на моей машине составляет около 150 минут. Но у нас нет другого выхода.

Инструкция содержит замечания о том, какие тесты могут не проходить с указанием причин. После выполнения тестов в обязательном порядке сверяем полученный результат с ожидаемым по инструкции. Эталонные логи всех проводимых тестов для версии LFS 7.7 лежат по этой ссылке

В качестве примера приведу выжимку из своего лога тестирования GCC

Краткий журнал тестирования GCC
=== g++ Summary ===

# of expected passes 88501
# of unexpected successes 2
# of expected failures 443
# of unsupported tests 3058
/sources/gcc-build/gcc/testsuite/g++/../../xg++ version 4.9.2 (GCC)

— === gcc Summary ===

# of expected passes 106352
# of expected failures 252
# of unsupported tests 1422
/sources/gcc-build/gcc/xgcc version 4.9.2 (GCC)

=== libatomic tests ===
— === libatomic Summary ===

# of expected passes 54
=== libgomp tests ===

Running target unix

=== libgomp Summary ===

# of expected passes 693
=== libitm tests ===

Running target unix

=== libitm Summary ===

# of expected passes 26
# of expected failures 3
# of unsupported tests 1
=== libstdc++ tests ===

— === libstdc++ Summary ===

# of expected passes 9925
# of expected failures 41
# of unsupported tests 233

Compiler version: 4.9.2 (GCC)
Platform: x86_64-unknown-linux-gnu


который можно сравнить с образцом

Официальный лог сборки GCC от авторов LFS
=== gcc Summary ===

# of expected passes 106401
# of expected failures 252
# of unsupported tests 1404
/sources/gcc-build/gcc/xgcc version 4.9.2 (GCC)

make[4]: Leaving directory '/sources/gcc-build/gcc'
— === g++ Summary ===

# of expected passes 88501
# of unexpected successes 2
# of expected failures 443
# of unsupported tests 3058
/sources/gcc-build/gcc/testsuite/g++/../../xg++ version 4.9.2 (GCC)

— === libstdc++ Summary ===

# of expected passes 9835
# of expected failures 41
# of unsupported tests 278
make[5]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libstdc++-v3/testsuite'
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libstdc++-v3/testsuite'
Making check in python
— === libgomp Summary ===

# of expected passes 693
make[5]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libgomp/testsuite'
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libgomp/testsuite'
make[4]: Entering directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libgomp'
true DO=all multi-do # make
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libgomp'
— === libitm Summary ===

# of expected passes 26
# of expected failures 3
# of unsupported tests 1
make[5]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libitm/testsuite'
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libitm/testsuite'
make[4]: Entering directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libitm'
— === libatomic Summary ===

# of expected passes 54
make[5]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libatomic/testsuite'
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libatomic/testsuite'
make[4]: Entering directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libatomic'
true DO=all multi-do # make
make[4]: Leaving directory '/sources/gcc-build/x86_64-unknown-linux-gnu/libatomic'
— === g++ Summary ===

# of expected passes 88501
# of unexpected successes 2
# of expected failures 443
# of unsupported tests 3058
/sources/gcc-build/gcc/testsuite/g++/../../xg++ version 4.9.2 (GCC)

— === gcc Summary ===

# of expected passes 106401
# of expected failures 252
# of unsupported tests 1404
/sources/gcc-build/gcc/xgcc version 4.9.2 (GCC)

=== libatomic tests ===
— === libatomic Summary ===

# of expected passes 54
=== libgomp tests ===

Running target unix

=== libgomp Summary ===

# of expected passes 693
=== libitm tests ===

Running target unix

=== libitm Summary ===

# of expected passes 26
# of expected failures 3
# of unsupported tests 1
=== libstdc++ tests ===

— === libstdc++ Summary ===

# of expected passes 9835
# of expected failures 41
# of unsupported tests 278

Compiler version: 4.9.2 (GCC)
Platform: x86_64-unknown-linux-gnu


Мне повезло — я получил результаты, хорошо совпадающие с предлагаемыми авторами. Чего и вам желаю.

4. Повторный вход в систему, очистка отладочной информации, удаление временной системы


Итак, вы собрали все пакеты. Поздравляю — большая и нуднейшая часть пути пройдена вами. Теперь перелогинимся в нашей новой системе

# logout
# chroot $LFS /tools/bin/env -i            \
>    HOME=/root TERM=$TERM PS1='\u:\w\$ ' \
>    PATH=/bin:/usr/bin:/sbin:/usr/sbin   \
>   /tools/bin/bash --login


перезапуская /tools/bin/bash с включенным хешированием, и убирая из путей поиска исполняемых файлов каталог /tools/bin. Отрезаем отладочную информацию

# /tools/bin/find /{,usr/}{bin,lib,sbin} -type f \
>    -exec /tools/bin/strip --strip-debug '{}' ';'


Чистим временный каталог от файлов, оставшихся после тестирования

# rm -rf /tmp/*


повторно логинимся, запуская уже «боевой» bash

# chroot "$LFS" /usr/bin/env -i              \
>    HOME=/root TERM="$TERM" PS1='\u:\w\$ ' \
>    PATH=/bin:/usr/bin:/sbin:/usr/sbin     \
>    /bin/bash --login


Теперь нам больше не нужна временная система — удаляем её

# rm -rf /tools


Фух… основные трудности позади. Нам осталось настроить нашу систему для загрузки, собрать ядро, сконфигурировать сеть. Но об этом я напишу в заключительной статье.

Окончание следует...
Tags:
Hubs:
Total votes 19: ↑18 and ↓1+17
Comments4

Articles