Установка 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