ДЕНИС ПЕПЛИН
Стартовые скрипты в FreeBSD
Знакомство с системой зачастую начинается с ее установки и настройки, причем эти два процесса обычно совмещены. Для систем UNIX настройка сводится к созданию текстовых файлов конфигурации. В FreeBSD основная работа идет над файлом /etc/rc.conf, который является частью системы стартовых скриптов /etc/rc*.
Настроить систему после установки можно путем запуска /stand/sysinstall или вручную. Оба способа хороши, а первый к тому же лишен недостатка большинства программ автоматической настройки: уже заданные параметры не перезаписываются, а добавляются в конец файла /etc/rc.conf, где их можно затем отредактировать под собственные нужды или вообще удалить, чтобы вернуться к настройкам по умолчанию.
Многие, казалось бы, сложные вещи, которые могут поставить новичка в тупик, в FreeBSD делаются путем добавления пары строк в этот файл. Чтобы не быть голословным, приведу пример настройки шлюза в Интернет для локальной сети с частными адресами (192.168.0.0/24) на внутреннем интерфейсе и NAT на внешнем, кэширующим DNS-сервером и SSH для удаленного управления всем этим хозяйством. Хотя это не самый простой пример, конфигурация умещается всего на 11 строчках:
sshd_enable="YES"
ifconfig_rl0="inet 123.123.123.2 netmask 0xfffffff0"
ifconfig_rl1="inet 192.168.0.1 netmask 0xffffff00"
hostname="example.com"
defaultrouter="123.123.123.1"
firewall_type="OPEN"
firewall_enable="YES"
gateway_enable="YES"
natd_enable="YES"
natd_interface="rl0"
named_enable="YES
Такая лаконичность достигается благодаря тому, что параметры по умолчанию хранятся в /etc/defaults/rc.conf, а строки из /etc/rc.conf лишь изменяют и дополняют эти параметры. Редактировать файл с параметрами по умолчанию не нужно (это только усложнит последующие обновления), но вот просмотреть его хотя бы раз будет полезно.
Строки rc.conf означают не что иное, как установку переменных Bourne shell (/bin/sh). Когда в процессе загрузки системы init запускает скрипт /etc/rc, одним из первых его действий становится установка параметров настройки:
if [ -r /etc/defaults/rc.conf ]; then
. /etc/defaults/rc.conf
source_rc_confs
elif [ -r /etc/rc.conf ]; then
. /etc/rc.conf
fi
В данном случае перезаписыванием параметров по умолчанию занимается функция source_rc_confs (расположенная в /etc/defaults/rc.conf), которая и включает /etc/rc.conf, /etc/rc.conf.local или любые другие файлы из списка, определенного в переменной rc_conf_files. Конструкция эта могла бы быть несколько проще, но используемая функция уже содержит «защиту от дурака», поскольку некоторые пользователи считают удобным копировать /etc/defaults/rc.conf в /etc/rc.conf, что без такой защиты привело бы к зацикливанию.
Включение в rc.conf команд нежелательно. Хотя такой метод и работает, процесс загрузки будет нарушен, ведь обычно в rc.conf ничего, кроме установки переменных не происходит, все действия выполняются позже стартовыми скриптами. При правильной настройке (установка переменных) порядок запуска сервисов и установки параметров системы определяется не положением строк в rc.conf, которое может быть произвольным, а содержимым скриптов.
Начиная с FreeBSD 5.x, содержимое это поменялось почти полностью: из NetBSD были заимствованы скрипты «нового поколения» /etc/rc.d. После некоторой паузы, потребовавшейся на тестирование новых скриптов, старые были убраны. Наиболее важные из оставшихся в /etc скриптов и файлы конфигурации:
- rc – запускается init при загрузке системы.
- rc.shutdown – запускается init при останове системы.
- rc.subr – часть системы, импортированной из NetBSD.
- rc.firewall – предназначен для настройки ipfw и natd.
- rc.conf – содержит настройки системы, переписывающие параметры по умолчанию.
- rc.conf.local – специфичные для хоста настройки. Если используется этот файл, rc.conf может быть общим для группы хостов.
- rc.local – все еще поддерживается для запуска локальных сервисов. Вместо него рекомендуется использовать скрипты /usr/local/etc/rc.d.
Для редактирования предназначены только скрипт rc.firewall и файлы конфигурации. Ситуация, в которой потребовалось бы приспосабливать к своей системе остальные скрипты, возникнуть не должна (по крайней мере, теоретически). Если она все же возникла – сообщите об этом разработчикам.
Принципиальное отличие скриптов нового поколения – поддержка параметров запуска. По сравнению со скриптами из /usr/local/etc/rc.d список этих параметров существенно расширен. К минимальному набору (start|stop) добавились:
- restart – последовательно stop и start.
- reload – перегружает процесс, посылая ему сигнал.
- status – показывает статус процесса.
- poll – ждет завершения процесса.
- rcvar – показывает соответствующие переменные rc.conf.
Параметры status, reload и poll реализуются только в том случае, если скрипт запускает какой-либо процесс.
Введение параметров запуска существенно облегчает задачу запуска и рестарта отдельных сервисов без перезагрузки всей системы. Конечно, это было возможно и раньше, но рекомендация «перегрузиться на всякий случай» теперь имеет под собой гораздо меньше оснований.
Перезагрузка после настройки системы нужна для того, чтобы проверить правильность внесенных в файлы конфигурации изменений. Когда стартовые скрипты представляют собой нечто монолитное, разделенное только по общей функциональности, воспользоваться ими для старта сервисов без перезагрузки можно разве что в качестве практического пособия «как это устроено». В этом случае запуск сервисов распадается на два этапа: внесение изменений в конфигурационный файл, чтобы все работало и после перезагрузки, и запуск программ вручную.
Проблема в том, что второй этап никак не проверяет правильность первого: может возникнуть ситуация, когда вручную все запущено правильно, а в конфигурационном файле допущена ошибка. Если это так, то при любой последующей перезагрузке соответствующий сервис не стартует, и придется все исправлять (хорошо, если вы будете в этот момент рядом, а не в отпуске, например).
К тому же запуск вручную не всегда тривиален: к примеру, для запуска NFS сервера в FreeBSD 4.x так, как это происходит при установке параметра nfs_server_enable=«YES» в rc.conf, потребуются следующие команды:
# portmap
# mountd -r
# nfsd
# rpc.statd
В FreeBSD 5.x достаточно добавить эту переменную и выполнить команду:
# /etc/rc.d/nfsd start
Очевидно, это гораздо проще и к тому же проверена на правильность соответствующая переменная. В примере с NFS в обеих случаях сначала необходимо подготовить файл /etc/exports (man exports(5)).
Другие параметры также достаточно интересны: например, status поможет узнать, какие процессы запущены в данный момент, а rcvar – познакомиться со скриптами поближе:
# for i in /etc/rc.d/*; do $i status 2>/dev/null; done
# for i in /etc/rc.d/*; do echo $i; $i rcvar 2>/dev/null; echo; done
Для стартовых скриптов системы, в отличие от локальных скриптов в /usr/local/etc/rc.d, очень важен порядок запуска. Локальные скрипты не имеют механизма реализации зависимостей и определения порядка запуска. Они стартуют в алфавитном порядке и если необходимо, чтобы какой-то скрипт стартовал раньше, нужно «повысить» его место по алфавиту. Обычно перед именем скрипта добавляют пару-тройку нулей.
Зависимости и определение порядка запуска в системных стартовых скриптах FreeBSD 5.x реализуются с помощью rcorder(8).
Эта программа использует специальные ключевые слова, содержащиеся в каждом скрипте: REQUIRE, PROVIDE, BEFORE, KEYWORD. Для оболочки строки, содержащие эти слова, являются комментариями. Вот пример блока ключевых слов /etc/rc.d/nfsd (все пробелы обязательны):
# PROVIDE: nfsd
# REQUIRE: mountd
# KEYWORD: FreeBSD NetBSD
Этот скрипт предоставляет сервис nfsd, требует предварительного запуска mountd и работает в FreeBSD и NetBSD.
Должен быть хотя бы один скрипт без ключевого слова REQUIRE, чтобы rcorder мог определить первый стартовый скрипт. Например, /etc/rc.d/devd содержит следующий блок:
# PROVIDE: devd
# BEFORE: disks
# KEYWORD: FreeBSD
Ключевое слово BEFORE помогает определить последующие скрипты (в которых анализируется строка «# PROVIDE: disks»).
В FreeBSD правильную последовательность скриптов выдает следующая команда:
# rcorder -k FreeBSD -s nostart /etc/rc.d/* 2>/dev/null
В полученном списке по именам скриптов можно выделить три группы. Скрипты с именами в верхнем регистре служат только для определения основных зависимостей. Это своеобразные «контрольные точки», которые определяют набор зависимостей на отдельных этапах загрузки.
Таких скриптов всего четыре: NETWORKING, SERVERS, DAEMON, LOGIN (man rc(8)). Они используются только rcorder, для sh это всего лишь набор комментариев. Остальные скрипты делятся по признаку наличия или отсутствия расширения «.sh». Если расширение присутствует, скрипт запускается в текущей оболочке.
В локальных скриптах поддержка параметров реализуется с помощью конструкции case. Параметр start предполагает запуск программы, stop – отправку ему сигнала TERM. Любой другой параметр – вывод краткой справки по параметрам. Шаблон локального скрипта выглядит так (man rc(8)):
#!/bin/sh -
case "$1" in
start)
/usr/local/sbin/prog -d && echo -n " prog"
;;
stop)
kill `cat /var/run/prog.pid` && echo -n " prog"
;;
*)
echo "unknown option: $1 - should be "start" or "stop"" >&2
;;
esac
Реализация таким методом большего числа параметров потребует соответствующего числа ключевых слов в case и множества довольно тривиального и одинакового для всех скриптов кода (более сложного, чем в приведенном примере). Видимо, разработчикам NetBSD такое решение изящным не показалось и вся функциональность стартовых скриптов нового поколения была реализована в отдельном скрипте (/etc/rc.subr). Вот шаблон простейшего скрипта «нового поколения».
#!/bin/sh
#
# PROVIDE: prog
# REQUIRE: before_prog
# BEFORE: after_prog
# KEYWORD: FreeBSD
. /etc/rc.subr
name="prog"
rcvar=`set_rcvar`
command="/usr/local/bin/prog"
load_rc_config $name
run_rc_command "$1"
Функционально этот скрипт превосходит предыдущий за счет добавления новых параметров (и к тому же, благодаря параметрам rcorder, он «знает свое место»). Визуально он делится на четыре части: ключевые слова, включение /etc/rc.subr, переменные и запуск функций из включенного rc.subr. Между переменными и запуском функций rc.subr могут быть включены собственные функции скрипта. В скрипт могут быть помещены переменные argument_cmd. Слово argument означает параметр запуска. Если запустить любой из стартовых скриптов без параметров, он выдаст информацию примерно в таком виде:
Usage: /etc/rc.d/prog [fast|force](start|stop|restart|rcvar|reload|status|poll) |
Параметрами скрипта могут быть start, faststart, forcestart, stop, faststop... и соответственно переменная argument_cmd на самом деле означает набор start_cmd, stop_cmd...
Получить примерное представление о том, какие на самом деле используются переменные, поможет следующая команда:
$ cat /etc/rc.d/[a-z]* | egrep "^[a-z]+_?[a-z]+=.*$" | awk -F= "{print $1}" | sort -u | less
Не менее интересен может быть просмотр переменных вместе с их значениями:
$ cat /etc/rc.d/[a-z]* | egrep "^[a-z]+_?[a-z]+=.*$" | sort -u | less
В man rc.subr(8) дано описание всех используемых функций и переменных.
Задаваемые в стартовых скриптах переменные используются функцией run_rc_command. Вот список этих переменных в сокращенном виде:
- name – имя скрипта. Указывается в обязательном порядке.
- rcvar – значение переменной, имя которой помещено в rcvar, берется из rc.conf и проверяется с помощью checkyesno. Истинными считаются значения «YES», «TRUE», «ON» или «1». Ложными «NO», «FALSE», «OFF» или «0».
- command – полный путь к команде. Переменная не нужна, если для каждого поддерживаемого параметра указана переменная argument_cmd.
- command_args – опциональные аргументы и/или директивы оболочки.
- command_interpreter – командный интерпретатор, с помощью которого запускается command.
- extra_commands – дополнительные параметры запуска.
- pidfile – полный путь к файлу PID. Если эта переменная установлена, для проверки процесса используется check_pidfile $pidfile $procname. Иначе, если установлена переменная command, используется check_process $procname.
- procname – имя процесса для проверки. По умолчанию – procname.
- required_dirs – проверка на наличие директории перед стартом программы.
- required_files – проверка на читаемость файлов.
- required_vars – проверка переменной с помощью checkyesno.
- ${name}_chdir – каталог, в который нужно войти перед запуском command, если нет – ${name}_chroot.
- ${name}_chroot – каталог для запуска chroot(8) перед запуском command.
- ${name}_flags – эта переменная используется для перезаписи соответствующей переменной из rc.conf.
- ${name}_nice – устанавливает уровень nice(1) перед запуском command.
- ${name}_user – пользователь, под которым запускается command. Если установлена переменная ${name}_chroot, используется chroot(8), иначе – su(1).
- ${name}_group – группа для запуска command в chroot.
- ${name}_groups – разделенный запятыми список групп для запуска в chroot.
- argument_cmd – перезаписывает действие по умолчанию.
- argument_precmd – выполняется перед argument_cmd или действием по умолчанию, которое выполняется только в случае успешного завершения argument_precmd.
- argument_postcmd – выполняется, если argument_cmd или действие по умолчанию успешно выполнены.
- sig_stop – сигнал, который посылается процессу для его завершения вместо SIGTERM.
- sig_reload – сигнал, который посылается процессу для перезагрузки вместо SIGHUP.
Префикс fast означает отсутствие проверки запущенных процессов перед запуском нового и установку переменной rc_fast=YES, а префикс force – установку переменной rc_force=YES и игнорирование статуса завершения argument_precmd.
В отличие от локальных скриптов, для работы скрипта «нового поколения» в rc.conf должна быть помещена переменная, имя которой определяется значением rcvar. Кроме того, туда же можно поместить и дополнительные переменные, например ${name}_flags.
В целом с переходом на новые скрипты настройка системы в rc.conf не изменилась (совместимость оставлена с целью минимизации проблем при обновлении системы). Возможностей по перенастройке системы на ходу явно прибавилось, и к тому же стартовые скрипты теперь еще лучше подходят в качестве инструмента изучения системы.