LXF170:Дистрострой: сделаем сами
|
|
|
Собираем дистрибутив Linux
Вооружась подготовленными инструментами, Дмитрий Кузнецов завершает дело построения минимального дистрибутива Linux.
На прошлом уроке мы обзавелись инструментарием для сборки дистрибутива. Теперь настало время применить его на деле!
Сборка компонентов
Вроде бы в Linux, где почти все ПО открыто, не должно быть ничего проще и стандартнее, чем собирать компоненты дистрибутива из исходников. Увы, это не так. Детали сборки могут серьезно отличаться от проекта к проекту: разнятся методы получения исходного кода, форматы архивов, системы конфигурирования и т. д. К этому стоит отнестись с пониманием – слишком уж разнообразны проекты, и по масштабу, и по назначению: от ядра современной ОС до мелкой библиотечки, реализующей всего пару полезных функций. Но решаемые при сборке задачи одинаковы для любого ПО. Это позволяет сформулировать общую схему:
1 Получение исходных кодов (например, загрузка архива).
2 Подготовка исходных кодов (например, их распаковка в рабочий каталог).
3 Конфигурирование (например, запуск ./configure с нужными параметрами).
4 Компиляция (например, запуск make).
5 Установка (например, запуск make install).
Детальные инструкции по сборке конкретного проекта вы узнаете только из его документации. Это единственно верный путь!
Загрузчик Grub
Самый известный и развитый загрузчик для ПК –Grub. Он используется большинством совеменных дистрибутивов Linux, в том числе и Ubuntu. Соберем его, действуя по нашей схеме.
1 Исходные коды Grub можно загрузить с сайта gnu.org в виде архива в формате tar.xz: wget -nd -P /root/work/download/ ftp://ftp.gnu.org/gnu/grub/grub-1.99.tar.xz.
2 В данном случае требуется только распаковать архив в рабочий каталог:
tar -C /root/work/src -xvf /root/work/download/grub-1.99.tar.xz
cd /root/work/src/grub-1.99/
3 Сконфигурировать Grub просто: задайте нужный компилятор C в переменной окружения CC и запустите скрипт configure с параметром prefix, указывающим каталог для установки (обычно /usr).
На беду, у ключа prefix в Grub двойное назначение: во-первых, он указывает место установки (каталог, куда складывать результаты сборки по команде make install); а во-вторых, компоненты Grub ищут друг друга по этому пути, работая в составе дистрибутива. Итак, получается два варианта сборки. Первый вариант:
» При сборке указываем prefix=/root/work/src/_install_grub. Make install складывает результаты сборки в /root/work/src/_install_grub (в /usr ничего не затирается). Но в файловой системе дистрибутива мы вынуждены разместить Grub тоже в /root/work/src/_install_grub (иначе компоненты не смогут найти друг друга).
Вариант должен работать, но очень хочется разместить загрузчик в /usr. Второй вариант:
» При сборке указываем prefix=/usr. Make install складывает результаты сборки в /usr, затирая системный Grub.
Тут уж совсем все плохо. Поэтому предлагается путь, совмещающий достоинства обоих вариантов:
» Берем два дерева исходных кодов Grub.
» В первом дереве собираем Grub с prefix=/usr (только make install не делаем), во втором дереве собираем Grub с prefix=/root/work/src/_install_grub (make install тоже не делаем).
» В первом дереве заменяем все Makefile’ы на соответствующие из второго дерева и вызываем make install.
Получаем: make install складывает результаты сборки в /root/work/src/_install_grub, но в файловой системе дистрибутива мы можем разместить загрузчик в /usr.
Конфигурирование с ключом prefix=/usr:
CC=/root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc ./configure --prefix=/usr
4 Командуем CC=/root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc make.
5 Итак, до цели – один шаг. Осталась одна проблема, обходной путь для решения которой был обещан в разделе «Настройка». Как установить Grub, сконфигурировнный с ключом prefix=/usr, в каталог /root/work/src/_install_grub? Обычная команда make install установит его в /usr и затрет файлы системного загрузчика.
Порассуждаем логически. Суть установки в простом копировании файлов в нужный каталог. Вся логика этого сосредоточена в Makefile’ах. Значит, нужно их откорректировать должным образом. Но при конфигурировании с ключом prefix=/root/work/src/_install_grub нужные Makefile’ы создаются автоматически. Использование их при установке решает поставленную задачу.
Создадим Makefile’ы для установки Grub в локальный каталог:
mkdir /root/work/src/grub-1.99_temp/
tar -C /root/work/src/grub-1.99_temp/ -xvf /root/work/download/grub-1.99.tar.xz
cd /root/work/src/grub-1.99_temp/grub-1.99/
CC=/root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc ./configure –prefix=/root/work/src/_install_grub
Заменим Makefile’ы (их всего шесть) в основном дереве исходных кодов. Можно копировать по одному вручную, но удобнее сделать это из командной строки:
cd /root/work/src/grub-1.99_temp
find ./grub-1.99/ | grep «Makefile$» | xargs -I {} -t cp --remove-destination {} .{}
Наконец-то можно установить Grub в локальный каталог:
cd /root/work/src/grub-1.99/
make install
Действительно, в каталоге /root/work/src/_install_grub лежит готовый загрузчик.
Описанный трюк с заменой Makefile’ов не стоит считать универсальным приемом. Нет никаких гарантий, что он сработает для другого ПО или даже других версий Grub.
Ядро Linux
1 Исходные коды ядра уже загружены, о чем за нас позаботился crosstool-ng. Архив в формате tar.xz лежит здесь: /root/work/download/linux-3.2.25.tar.xz.
2 Нужно распаковать архив в рабочий каталог:
tar -C /root/work/src/ -xvf /root/work/download/linux-3.2.25.tar.xz
cd /root/work/src/linux-3.2.25/
3 Во-первых, в файле Makefile корневого каталога исходных кодов нужно установить значения переменных ARCH и CROSS_COMPILE (строки 195 и 196) следующим образом:
ARCH ?= x86_64
CROSS_COMPILE ?= /root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-.
Первая определяет целевую архитектуру собираемого ядра. Вторая настраивает систему сборки на применение нужного инструментария, путем добавления ее значения как префикса при вызове инструментов сборки. Например, в данном случае имя компилятора из cc превратится в /root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc. Отредактировать Makefile можно вручную в любом текстовом редакторе или с помощью команд:
sed -i 's/^ARCH.*?=.*$/ARCH ?= x86_64/' ./Makefile
sed -i 's/^CROSS_COMPILE.*?=.*$/CROSS_COMPILE ?= \/root\/work\/files\/toolchain\/result\/bin\/x86_64-unknown-linux-gnu-/' ./Makefile
Во-вторых, нужно создать файл .config, с помощью которого задается конфигурация ядра Linux. К счастью, в исходных кодах для каждой из поддерживаемых архитектур есть примеры конфигураций. Как и в случае с crosstool-ng, удобно взять один из них за основу, а затем немного подкорректировать в случае необходимости. Но здесь не придется искать нужный файл настройки в дереве исходных кодов и копировать его вручную. Для этого есть специальная команда make defconfig.
Она создаст файл .config в текущем каталоге на основе стандартной конфигурации для данной архитектуры, которая уже была задана ранее в переменной ARCH.
Для изменения конфигурации ядра Linux служит специальная интерактивная система, похожая на ту, что используется в crosstool-ng. Запустить ее можно командой make menuconfig.
Правок всего три.
» В разделе Processor type and features включите Build-in kernel command line, а затем в Build-in kernel command string введите root=/dev/sda1 rootfstype=ext4. Эта строка описывает параметры, которые ядро будет использовать при загрузке. Здесь достаточно указать расположение и тип корневой файловой системы.
» Как видно из предыдущего пункта, используется файловая система ext4, поэтому ее нужно включить: раздел File systems, параметр The Extended 4 (ext4) filesystem.
» В разделе Kernel hacking отключите Stack utilization instrumentation. Это избавит от назойливых сообщений о состоянии стека. Они бывают полезны при отладке, но иначе только раздражают.
Осталось закрыть систему конфигурирования (дважды нажать Esc), согласившись с сохранением изменений в файле .config. Конфигурация ядра готова.
4 Компилируем: make.
5 Готовый образ ядра Linux представляет собой единственный файл /arch/x86/boot/bzImage, поэтому установка не требуется.
Прикладное ПО и init
Прикладных программ великое множество. Даже если ограничиться лишь основными утилитами командной строки, традиционными для любого дистрибутива Linux, описание их сборки заняло бы не один десяток страниц. К счастью, есть прекрасный проект busybox. Он реализует функциональность init, shell и еще более чем 300 программ командной строки Linux. Даже удивительно, что все это умещается в единственном исполняемом файле объемом 2,5 МБ. Некоторые программы слегка урезаны – например, может отсутствовать поддержка некоторых параметров. Но надо быть уж очень большим фанатом консоли, чтобы ощущать эти неудобства. Очевидно, что в данном случае вполне разумно использовать busybox. Необходимо только сделать два замечания:
» Синтаксис файла inittab, необходимого для init, в busybox немного отличается от его классического аналога.
» Утилита grep из busybox не устраивает один из скриптов в Grub. Исследования показали, что все дело в параметре -x. Поэтому grep придется собрать отдельно. Возможно, в будущих версиях busybox эта проблема будет исправлена.
1 Исходные коды busybox можно загрузить с сайта проекта в виде архива в формате tar.bz2: wget -nd -P /root/work/download/ www.busybox.net/downloads/busybox-1.20.2.tar.bz2.
2 Распаковка архива в рабочий каталог:
tar -C /root/work/src/ -xjvf /root/work/download/busybox-1.20.2.tar.bz2
cd /root/work/src/busybox-1.20.2/
На сайте проекта для каждой версии busybox можно найти патчи (на момент написания статьи для версии 1.20.2 был доступен только один), которые желательно загрузить и применить к распакованным исходным кодам:
wget -nd -P /root/work/download/ www.busybox.net/downloads/fixes-1.20.2/busybox-1.20.2-kernel_ver.patch
patch -p1 </root/work/download/busybox-1.20.2-kernel_ver.patch
3 Конфигурирование busybox и ядра Linux аналогичны: взять стандартную конфигурацию, открыть интерактивную систему конфигурирования и изменить несколько параметров:
make defconfig
make menuconfig
Как всегда, правок немного.
» В разделе Busybox Settings > Build Options установить параметр Building BusyBox as a static binary (no shared libs). Он означает, что busybox нужно собрать в виде единственного исполняемого файла. Все поддерживаемые программы будут реализованы в виде символических ссылок на него.
» Там же, параметру Cross Compiler prefix установить значение /root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-. Его смысл аналогичен переменной CROSS_COMPILE в ядре Linux.
» В разделе Network utilities > inetd отключить Support RPC services. Для сборки с этим параметром в используемом инструментарии не хватает заголовочных файлов. Поскольку эта возможность не нужна, проще ее отключить, чем модернизировать инструментарий.
Как и в случае конфигурирования ядра Linux, выйдя из интерактивной системы и согласившись с сохранением изменений в .config, получаем готовую конфигурацию busybox.
4 Компиляция: make.
5 Установка: make install.
В одном из параметров настройки местом установки был указан каталог ./_install. Действительно, был создан /root/work/src/busybox-1.20.2/_install с готовым busybox.
Осталось собрать grep. К счастью, это настолько просто, что не потребует никаких пояснений. Все бы программы так собирались!
1 Получаем исходные коды:
wget -nd -P /root/work/download/ ftp://ftp.gnu.org/gnu/grep/grep-2.10.tar.xz
2 Готовим исходные коды:
tar -C /root/work/src/ -xvf /root/work/download/grep-2.10.tar.xz
cd /root/work/src/grep-2.10/
3 Конфигурируем:
CC=/root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc ./configure --prefix=/root/work/src/_install_grep
4 Компилируем:
CC=/root/work/files/toolchain/result/bin/x86_64-unknown-linux-gnu-cc make
5 Устанавливаем: make install.
Установка выполнена в каталог /root/work/src/_install_grep.
Заготовка корневой ФС из crosstool-ng
Как уже говорилось ранее, в Linux всем прикладным программам необходима стандартная библиотека языка C. Без нее не обходится ни один дистрибутив. К счастью, она входит в состав нашего инструментария, поэтому с ее сборкой возиться не придется, об этом уже позаботился crosstool-ng. Собранную для целевой платформы версию можно найти здесь: /root/work/files/toolchain/result/x86_64-unknown-linux-gnu/sysroot/. Как нетрудно догадаться по названию, содержимое каталога sysroot служит отличной заготовкой для корневой файловой системы нового дистрибутива.
Минимальный набор стартовых скриптов
Каталог /etc любого современного дистрибутива Linux содержит море сложнейших скриптов и файлов настройки. С виду кажется, что разобраться в них невозможно. Но стоит погрузиться в изучение, как станет ясно, что большинство из них отвечают за настройки некой прикладной программы. Нет программы, нет и соответствующих файлов. Для минимального дистрибутива, описываемого в данной статье, хватит трех. И их содержимое так примитивно, что пояснения могут показаться излишними.
Создание рабочего каталога:
mkdir /root/work/files/etc_files
cd /root/work/files/etc_files
Сами файлы можно создать в любом текстовом редакторе или, как показано ниже, с помощью команд echo.
» Файл /etc/inittab, который конфигурирует init.
echo '::sysinit:/etc/rcS' >> ./inittab
echo 'tty1::respawn:/bin/sh' >> ./inittab
echo 'tty2::respawn:/bin/sh' >> ./inittab
echo 'tty3::respawn:/bin/sh' >> ./inittab
echo '::restart:/sbin/init' >> ./inittab
echo '::ctrlaltdel:/sbin/reboot' >> ./inittab
Смысл понятен, детали синтаксиса прояснит документация.
» Файл /etc/rcS, указанный с inittab как стартовый скрипт.
echo '#!/bin/sh' >> ./rcS
echo 'mount -av' >> ./rcS
echo 'mdev -s' >> ./rcS
echo 'echo «Hello, my micro linux!»' >> ./rcS
Этот обычный скрипт командной оболочки выполняет три действия:
> Монтирует файловые системы, указанные в файле /etc/fstab.
> Создает в каталоге /dev все необходимые файлы устройств. Здесь это делается с помощью программы mdev из busybox, хотя в больших дистрибутивах Linux обычно используется специальная система udev, которая требует отдельной сборки и настройки.
> Выводит приветствие.
Поскольку он будет запускаться, дадим ему на это права:
chmod 777 ./rcS
» Файл fstab. Строго говоря, он не обязателен. Но трудно удержаться от соблазна всего двумя строчками получить доступ к содержимому файловых систем proc и sysfs, которые дают огромное количество полезной информации о работающий системе.
echo -e 'proc\t/proc\tproc\tdefaults\t0\t0' >> ./fstab
echo -e 'sysfs\t/sys\tsysfs\tdefaults\t0\t0' >> ./fstab
Все вместе
Итак, необходимые компоненты готовы. Осталось собрать из них корневую файловую систему будущего дистрибутива. Это делается простым копированием полученных компонентов в один каталог. Первым делом нужно скопировать заготовку корневой файловой системы, созданную crosstool-ng, в рабочий каталог:
cp -r /root/work/files/toolchain/result/x86_64-unknown-linux-gnu/sysroot/ /root/work/files/
Теперь туда можно добавить все остальные компоненты.
» Busybox
cp -r /root/work/src/busybox-1.20.2/_install/* /root/work/files/sysroot/
» Grep (программы из пакета grep должны заменить соответствующие символические ссылки busybox, поэтому при копировании нужно указать ключ remove-destination):
cp -r --remove-destination /root/work/src/_install_grep/* /root/work/files/sysroot/
» Загрузчик Grub нужно копировать в каталог /usr, как было указано при сборке:
cp -r /root/work/src/_install_grub/* /root/work/files/sysroot/usr/
» Стартовые скрипты и конфигурационные файлы
cp -r /root/work/files/etc_files/* /root/work/files/sysroot/etc
» Образ ядра Обычно располагается в каталоге /boot. Нужно создать его и скопировать туда bzImage, переименовав в соответствии с требованиями Grub в vmlinuz-3.2.25:
mkdir /root/work/files/sysroot/boot
cp /root/work/src/linux-3.2.25/arch/x86/boot/bzImage /root/work/files/sysroot/boot/vmlinuz-3.2.25
Осталось создать пустые каталоги /proc, /sys и /dev для монтирования файловых систем и создания файлов устройств:
cd /root/work/files/sysroot/
mkdir proc sys dev
Корневая файловая система готова.
Запуск готового дистрибутива
Итак, можно считать, что дистрибутив Linux собран. Надо бы проверить его работоспособность и настроить загрузчик. Для этого нужено компьютер или раздел на жестком диске. Но свободный компьютер для экспериментов мало у кого найдется, а чтобы уверенно выполнять такие трюки на отдельном разделе жесткого диска своей рабочей машины, нужен немалый опыт. Идеальное решение – виртуальная машина (ВМ): делаешь все что угодно, а если что-то не вышло, всегда можно удалить файл с образом жесткого диска и попробовать еще раз. Осталось выбрать эту ВМ.
Виртуальная машина KVM
Технологии виртуализации в последние годы довольно бурно развиваются, поэтому выбирать есть из чего. Но виртуализация не является темой данной статьи, поэтому хотелось бы найти решение попроще. Тут внимание сразу привлекает KVM (Kernel Virtual Machine), ВМ ядра Linux. Она в ядре, а ядро уже есть; значит, с ней будет меньше всего проблем. Здесь следует отметить, что попасть в ядро давно мечтали все. Многие годы шла изнурительная борьба за это «место под солнцем». Но появилась KVM – и в два счета обошла старичков. Победа, видимо, была одержана компактностью: для работы KVM достаточно загрузить один модуль ядра и установить QEMU для виртуализации ввода/вывода. Правда, такое решение работает только на современных процессорах Intel и AMD с аппаратной реализацией технологии поддержки виртуализации. Но эта проблема с каждым годом теряет свое значение. Проверить наличие у процессора данной функциональности можно так (если выводится ok, то поддерживает):
[ «`egrep '^flags.*(vmx|svm)' /proc/cpuinfo`» != “” ] && echo “ok”
Установить KVM просто:
apt-get install kvm qemu
Теперь загрузите нужный драйвер, и можно приступать к работе. Для процессоров AMD скомандуем modprobe kvm-amd, для процессоров Intel – modprobe kvm-intel. Если при загрузке драйвера выдается ошибка Operation is not supported [Операция не поддерживается], вероятно, в BIOS отключена виртуализация.
Установка ОС
Пора разобраться в работе инсталлятора ОС. Непосвященным эта задача может показаться неподъемной. Уж очень большое впечатление производит программа, способная превратить груду микросхем в современный компьютер. Однако все эти чудеса обеспечиваются ПО, входящим в состав дистрибутива ОС, а процедура установки сводится к подготовке жесткого диска и простому копированию на него нужных файлов. Кто бы мог подумать, что таинственный образ инсталятора ОС так легко развенчать? Далее вся его работа будет подробно рассмотрена и проделана вручную.
Первым делом нужен сам жесткий диск, на который будет устанавливаться ОС. Для ВМ его роль играет обычный файл, называемый образом жесткого диска. Для работы с ними в QEMU есть специальная программа. Вот как с ее помощью можно создать образ жесткого диска размером 1 ГБ:
qemu-img create /root/work/files/sysroot.img 1G
Это образ самого простого типа (raw-формат), то есть обычный файл, заполненный нулями. С таким же успехом для его создания можно было бы воспользоваться командой dd.
Итак, диск есть. Как с ним работать? С реальными жесткими дисками все было бы понятно: в Linux они представлены файлами в каталоге /dev. А с образами? Хорошо бы уметь и с ними работать аналогично. Это возможно с помощью технологии сетевого блочного устройства (Network Block Device, NBD). Она дает возможность работать с удаленными жесткими дисками через сеть TCP/IP. Сервер – компьютер, который предоставляет доступ к своему жесткому диску. На нем работает прикладная серверная программа, принимающая запросы из сети и транслирующая их в локальные дисковые операции. Клиент – компьютер, желающий получить доступ к удаленному жесткому диску. На нем ядро Linux (для этого оно должно быть собрано с параметром CONFIG_BLK_DEV_NBD) создает в каталоге /dev набор файлов nbdN, которыми будут представляться удаленные диски. Как все это поможет в работе с образами? В QEMU есть специальная программа qemu-nbd, позволяющая представить образ жесткого диска виртуальной машины удаленным жестким диском. Следовательно, можно подключить образ к файлу, например, /dev/nbd0, и работать с ним как с обычным жестким диском. Если NBD в ядре Linux собрана в виде модуля (например, так сделано в Ubuntu 12.04 LTS), нужно не забыть его загрузить:
modprobe nbd max_part=16
qemu-nbd -c /dev/nbd0 /root/work/files/sysroot.img
partprobe /dev/nbd0
Теперь с помощью обычных средств для разметки дисков можно создать разделы. В данном случае достаточно простейшего варианта: один большой первичный раздел, занимающий все доступное пространство. Нужно только учесть, что в начале диска необходимо оставить 32,3 КБ для установки загрузчика.
parted /dev/nbd0 mklabel msdos
parted -a cylinder /dev/nbd0 mkpart primary 32.3k 1024
parted /dev/nbd0 set 1 boot on
В каталоге /dev появился новый файл nbd0p1. Это и есть только что созданный раздел. Для полной готовности к работе осталось только отформатировать его. Какую файловую систему выбрать? Любую. Главное, чтобы ядро при загрузке смогло ее опознать и смонтировать корневую файловую систему. Описанная выше конфигурация ядра требует ext4:
mkfs.ext4 /dev/nbd0p1
Теперь туда нужно записать корневую файловую систему. Как это сделать? Тут тоже все стандартно для пользователя Linux: смонтировать раздел в какой-нибудь каталог (например, можно создать для этой цели /root/work/files/mnt) и скопировать туда нужные файлы.
mkdir /root/work/files/mnt
mount -t ext4 /dev/nbd0p1 /root/work/files/mnt/
cp -r /root/work/files/sysroot/* /root/work/files/mnt
Образ готов. Осталось только подчистить «хвосты»: размонтировать раздел, освободить файл nbd0 и выгрузить модуль nbd.
umount /root/work/files/mnt/
qemu-nbd -d /dev/nbd0
modprobe -r nbd
Настройка загрузчика
Последний штрих – настроить и установить загрузчик. Если сейчас попробовать загрузить KVM с образом жесткого диска sysroot.img, ничего не получится. BIOS безуспешно попробует найти загрузчик, после чего процесс загрузки остановится. Можно проверить: kvm -m 512 /root/work/files/sysroot.img.
Действительно, дальше бесконечного созерцания сообщения Booting from Hard Disk [Загружается с жесткого диска]... продвинуться не получается.
К счастью, у KVM есть средство вообще обойтись без загрузчика: параметр kernel. Он позволяет указать внешний файл образа ядра Linux, вместо того, чтобы доверять его поиск загрузчику на жестком диске виртуальной машины:
cp /root/work/src/linux-3.2.25/arch/x86/boot/bzImage /root/work/files/bzImage
kvm -m 512 /root/work/files/sysroot.img -kernel /root/work/files/bzImage
Загрузилась! Появилось приглашение командной строки. Теперь можно настроить Grub, чтобы в дальнейшем обходиться без внешнего ядра (внимание! Следующие команды даются в ОС, которая запущена на KVM):
/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg
/usr/sbin/grub-install /dev/sda
sync
Итак, загрузчик настроен, можно закрыть виртуальную машину (просто закрыть окно, команда sync не даст потерять данные) и попробовать запустить ее без параметра kernel:
kvm -m 512 /root/work/files/sysroot.img
Загружается с жесткого диска!
Заключение
Спасибо всем, кто дочитал до конца. Хочется верить, что труд, вложенный в написание этой статьи, не пропал даром. Надеюсь, многие узнали что-то новое про внутреннее устройство Linux и стали чувствовать себя более уверенно в работе с любым дистрибутивом. А это, в свою очередь, послужит надежным фундаментом для поддержания интереса и дальнейшего углубления в удивительный и захватывающий мир открытого ПО. Кроме того, дистрибутив, сборка которого подробно описана в статье, не так уж бесполезен, как может показаться на первый взгляд. Например, он может служить прекрасным испытательным полигоном для экспериментов с ядром Linux. |