Установка sinit в NetBSD
Содержание
Введение
Существует концепция минимального демона инициализации, выдвинутая автором библиотеки musl libc Ричем Фелькером (Rich Felker), согласно которой минимальный процесс init должен лишь вызывать при запуске скрипт /etc/rc.init, усыновлять осиротевшие процессы и вызывать скрипт /etc/rc.shutdown при получении сигналов, инициирующих завершение работы. Эта концепция была воплощена в демоне sinit, исходные тексты которого распространяются проектом suckless.org, через репозиторий git.suckless.org/sinit/.
В NetBSD терминалы запускаются не при помощи rc-скриптов, а самим демоном init, который руководствуется списком терминалов из файла /etc/ttys. В статье Запуск getty в NetBSD с помощью daemontools описано, как снять функцию запуска терминалов с демона init и возложить её на плечи daemontools.
После настройки запуска консолей с помощью daemontools становится возможным заменить init из NetBSD на sinit и получить операционную систему с максимально простой и надёжной системой инициализации.
Хотя демон init в NetBSD уже достаточно прост и при его замене на sinit мы потеряем, например, возможность запуска NetBSD в однопользовательском режиме, можно попытаться сделать это не из каких-то практических соображений, а скорее из чистого любопытства и любви к искусству.
Подготовка пакета
В pkgsrc нет пакета с sinit и мне пришлось подготовить его самостоятельно. Наиболее подходящий раздел для этого пакета - sysutils. Первая задача - найти в интернете источник, из которого можно скачать архив с исходными текстами. Два найденных git-репозитория git.suckless.org/sinit и git.2f30.org/sinit/ не позволяют скачать архив. Пришлось воспользоваться зеркалом на github.com/mathumani/sinit/. В репозитории нет меток версий и нет никаких веток, кроме ветки master, поэтому пришлось сослаться на хэш-сумму последней фиксации, соответствующей версии 1.1. Для скачивания исходных текстов я создал файл sysutils/sinit/Makefile со следующим содержимым:
# $NetBSD$
DISTNAME= sinit
PKGNAME= ${DISTNAME}-1.1
CATEGORIES= sysuitls
MASTER_SITES= ${MASTER_SITE_GITHUB:=mathumani/}
GITHUB_PROJECT= sinit
GITHUB_TAG= 28c44b6b94a870f2942c37f9cfbae8b770595712
В качестве сопровождающего пакета я указал свой почтовый ящик, а в качестве ссылки на официальный сайт привёл страницу git.suckless.org/sinit/. Судя по файлу лицензии в репозитории, это лицензия MIT. Описание пакета взял из файла README. В итоге, дописал в файл sysutils/sinit/Makefile следующее:
MAINTAINER= vladimir@stupin.su
HOMEPAGE= https://git.suckless.org/sinit/
COMMENT= sinit is a simple init
LICENSE= mit
Также добавил более длинное описание в файл sysutils/sinit/DESCR:
sinit is a simple init. It was initially based on Rich Felker's minimal init.
Файл sysutils/sinit/Makefile завершил подключением системы pkgsrc:
.include "../../mk/bsd.pkg.mk"
Теперь можно скачать архив с исходными текстами и создать файл distinfo с хэш-суммами архивного файла:
# make fetch
# make distinfo
Поскольку программа написана на языке C, но у неё нет сценария конфигурирования, добавим в файл sysutils/sinit/Makefile следующие строчки:
NO_CONFIGURE= yes
USE_LANGUAGES= c
Теперь можно попытаться собрать пакет:
# make
Оказывается, для сборки sinit нужен GNU Make, но использовать его мне не хочется. Единственное, что не получается сделать без его помощи - сгенерировать файл config.h из файла config.def.h. Но достичь этого можно простым копированием файла. Я не стану копировать файл, а просто переименую его:
post-extract:
${MV} ${WRKSRC}/config.def.h ${WRKSRC}/config.h
Следующее, что мы сделаем, это избавимся от файла config.mk, в котором определены переменные с настройками для сборки файла sinit.c. Большинство настроек определяется через переменные системы pkgsrc. Единственные две переменные, которые потребуются и которых нет в pkgsrc - это переменные MANPREFIX и VERSION. Итак, удаляем директиву include в начале файла Makefile, добавим значение переменной VERSION и заменим $(MANPREFIX) на $(PREFIX)/man:
$NetBSD$
--- Makefile.orig 2018-03-26 16:48:09.000000000 +0000
+++ Makefile
@@ -1,7 +1,6 @@
-include config.mk
-
OBJ = sinit.o
BIN = sinit
+VERSION = 1.1
all: $(BIN)
@@ -11,10 +10,10 @@ $(BIN): $(OBJ)
$(OBJ): config.h
install: all
- mkdir -p $(DESTDIR)$(PREFIX)/bin
- cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
- mkdir -p $(DESTDIR)$(MANPREFIX)/man8
- sed "s/VERSION/$(VERSION)/g" < $(BIN).8 > $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8
+ mkdir -p $(DESTDIR)$(PREFIX)/sbin
+ cp -f $(BIN) $(DESTDIR)$(PREFIX)/sbin
+ mkdir -p $(DESTDIR)$(PREFIX)/man/man8
+ sed "s/VERSION/$(VERSION)/g" < $(BIN).8 > $(DESTDIR)$(PREFIX)/man/man8/$(BIN).8
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
Кроме этого я поменял каталог установки bin на sbin, т.к. исполняемый файл не предназначен для использования непривилегированным пользователем.
Сгенерировать заплатку можно с помощью утилиты pkgdiff, перенаправив её вывод в файл sysutils/sinit/patches/patch-Makefile и подредактировав результат так, чтобы в ней фигурировали имена файлов Makefile и Makefile.orig без предшествующих каталогов, как это показано выше.
Аналогичным образом я внёс одно небольшое изменение в исходный текст sinit.c, удалив из него две бесполезные директивы include. Не знаю, повлияло ли это как-то на конечный результат. Получившуюся заплатку я поместил в каталог sysutils/sinit/patches/patch-sinit.c:
$NetBSD$
--- sinit.c.orig 2018-03-26 16:48:09.000000000 +0000
+++ sinit.c
@@ -1,10 +1,8 @@
/* See LICENSE file for copyright and license details. */
-#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
-#include <stdlib.h>
#include <unistd.h>
#define LEN(x) (sizeof (x) / sizeof *(x))
Чтобы заплатки без проблем применялись при сборке, нужно сгенерировать их хэш-суммы. Сделать это можно с помощью одной из следующих команд:
# make makepatchsum
# make mdi
Кроме результатов сборки я хочу поместить в пакет файлы LICENSE и README. Для этого я добавил в Makefile дополнительное правило:
post-install:
${INSTALL_DATA} ${WRKSRC}/LICENSE ${DESTDIR}${PREFIX}/share/doc/${PKGBASE}
${INSTALL_DATA} ${WRKSRC}/README ${DESTDIR}${PREFIX}/share/doc/${PKGBASE}
Чтобы установка этих файлов сработала, нужно создать каталог. Сделать это можно с помощью такой опции:
INSTALLATION_DIRS= share/doc/${PKGBASE}
В уже упомянутом выше файле config.h находятся настройки с командами, которые sinit должен выполнить при запуске, перезагрузке или выключении системы. Команду перезагрузки sinit выполняет при получении сигнала INT, а команду выключения - при получении сигнала USR.
Файл /etc/rc, при помощи которого фактически запускается вся система, в NetBSD не имеет бита исполнимости. Есть и другие обстоятельства, которые вскрылись в процессе попыток воспользоваться sinit по прямому назначению, по которым файл /etc/rc нельзя использовать напрямую. Никакого другого подходящего готового файла в системе я не нашёл, поэтому в качестве команды запуска решил прописать пока не существующий файл /etc/rc.init, который будет описан ниже. В качестве команд для перезагрузки и выключения системы пропишем /sbin/reboot и /sbin/poweroff. Для этого добавим в файл sysutils/sinit/Makefile следующие правила замены фрагментов текста с помощью утилиты sed:
.if "${OPSYS}" == "NetBSD"
SUBST_CLASSES+= config.h
SUBST_STAGE.config.h= post-patch
SUBST_MESSAGE.config.h= Fixing config.h.
SUBST_FILES.config.h+= config.h
SUBST_SED.config.h= -e 's:/bin/rc.init:/etc/rc.init:g'
SUBST_SED.config.h+= -e 's:/bin/rc.shutdown", "reboot:/sbin/reboot:g'
SUBST_SED.config.h+= -e 's:/bin/rc.shutdown", "poweroff:/sbin/poweroff:g'
.endif
Правила применяются только для операционной системы NetBSD, поскольку в других системах команды могут быть другими.
Честно говоря, я не совсем понимаю, зачем в sinit предусмотрена обработка сигналов INT и USR1, т.к. эти команды можно вызвать и напрямую. Я бы, пожалуй, просто вырезал бы этот функционал из sinit.
Вернёмся к файлу /etc/rc.init. Запустить систему, в которой демоны getty уже запускаются с помощью daemontools, как это описано в статье Запуск getty в NetBSD с помощью daemontools, у меня получилось с помощью такого файла /etc/rc.init:
#!/bin/sh
exec >/dev/constty 2>&1
/bin/sh /dev/MAKEDEV -u all
/bin/sh /etc/rc autoboot
Как видно, первым делом в этом файле выполняется перенаправление вывода последующих команд в консоль /dev/constty. Вместо него можно также использовать и /dev/console, но поскольку в NetBSD в конфигурации по умолчанию первая строчка в файле /etc/ttys использует именно консоль /dev/constty, я воспользовался именно ей. В демоне init, поставляемом в составе NetBSD, пробуются оба устройства, но предпочтение отдаётся /dev/constty.
Также демон init, при отсутствии устройств в каталоге /dev, самостоятельно запускает скрипт /dev/MAKEDEV. Поскольку sinit задумывался как минимальная реализация init, этот функционал я перенёс в скрипт /etc/rc.init.
Ну и в последней строчке скрипта запускается скрипт /etc/rc с аргументом autoboot, который соответствует многопользовательскому режиму загрузки. init из состава NetBSD можно запустить с опцией -s и тогда он будет загружаться в однопользовательском режиме без передачи скрипту /etc/rc дополнительных аргументов. Если init запущен в обычном режиме или переходит в обычный режим после однопользовательского, то скрипту /etc/rc передаётся аргумент autoboot.
Этот скрипт я поместил в каталог sysutils/sinit/files. Кроме того, в этот же каталог я поместил текстовый файл INSTALL с примером установки sinit вместо init из NetBSD:
How to install sinit instead of NetBSD own init:
mv /sbin/init /sbin/init.bak
cp @PREFIX@/sbin/sinit /sbin/init
cp @PREFIX@/share/examples/@PKGBASE@/rc.init /etc/rc.init
chmod +x /etc/rc.init
Как видно, в этом файле есть шаблоны подстановки вида @PREFIX@ и @PKGBASE@. Для их обработки я добавил в файл sysutils/sinit/Makefile следующие правила замены:
SUBST_CLASSES+= install
SUBST_STAGE.install= post-patch
SUBST_MESSAGE.install= Fixing INSTALL.
SUBST_FILES.install+= INSTALL
SUBST_VARS.install+= PREFIX PKGBASE
Файлы rc.init и INSTALL будем устанавливать в каталог share/examples/sinit. Добавим этот каталог в опцию INSTALLATION_DIRS для создания каталога перед установкой файлов:
INSTALLATION_DIRS= share/doc/${PKGBASE} share/examples/${PKGBASE}
Для установки файлов rc.init и INSTALL доработаем в файле sysutils/sinit/Makefile цели post-extract и post-install
post-extract:
${MV} ${WRKSRC}/config.def.h ${WRKSRC}/config.h
${CP} ${FILESDIR}/INSTALL ${WRKSRC}
post-install:
${INSTALL_DATA} ${WRKSRC}/LICENSE ${DESTDIR}${PREFIX}/share/doc/${PKGBASE}
${INSTALL_DATA} ${WRKSRC}/README ${DESTDIR}${PREFIX}/share/doc/${PKGBASE}
${INSTALL_DATA} ${WRKSRC}/INSTALL ${DESTDIR}${PREFIX}/share/examples/${PKGBASE}
${INSTALL_DATA} ${FILESDIR}/rc.init ${DESTDIR}${PREFIX}/share/examples/${PKGBASE}
Файл rc.init устанавливается из каталога sysutils/sinit/files напрямую в каталог share/examples/sinit, а файл INSTALL устанавливается в два этапа, между которыми к нему применяются правила подстановки в нём шаблонов @PREFIX@ и @PKGBASE@.
Теперь можно попробовать обновить файл sysutils/sinit/PLIST и собрать пакет:
# make stage-install
# make print-PLIST > PLIST
# make package
Первая команда может ругнуться на несоответствие списка файлов будущего пакета содержимому файла sysutils/sinit/PLIST, но если следующие команды выполнились успешно, то мы получим готовый пакет.
Установка
Установим пакет с помощью команд:
# cd /usr/pkgsrc/sysutils/sinit
# make install
Я пользуюсь собственным сборочным сервером, настройка которого описана в статье Настройка сборочного сервера NetBSD, с помощью которого поддерживаю собственный репозиторий с готовыми двоичными пакетами, собранными с нужными мне опциями. Поэтому я установил готовый пакет из этого репозитория:
# pkgin update
# pkgin -y install sinit
Остаётся воспользоваться командами, описанными нами в файле INSTALL:
# mv /sbin/init /sbin/init.bak
# cp /usr/pkg/sbin/sinit /sbin/init
# cp /usr/pkg/share/examples/sinit/rc.init /etc/rc.init
# chmod +x /etc/rc.init
Перезагружаем систему:
# reboot
Если возникнут проблемы с загрузкой системы, нужно воспользоваться загрузочным диском, смонтировать корневой каталог системы в режиме чтения-записи и удалить файл sbin/init и/или заменить его на резервную копию из файла sbin/init.bak. В моём случае последовательность команд была такой:
# mount -o rw /dev/dk0 /mnt
# cd /mnt/sbin
# rm init
# mv init.bak init
# reboot