Проектирование свойств системы

Операционная система, позволяющая задействовать все возможности компьютера, резко отличается от специализированного программного обеспечения огромным числом т. н. "вариантов использования" (use cases) и обширнейшими возможностями тонкой настройки для решения задач конкретного пользователя наилучшим способом. Достаточно сравнить какую-нибудь игровую приставку (например, PlayStation2) под управлением собственной операционной системы и её же под управлением Linux. Вычислительная и мультимедийная мощность такого компьютера весьма высока (известно, что именно компьютерные игры определяют сейчас ресурсопотребление персонального компьютера). Однако способы управления одной и другой системами настолько различны, что неподготовленный человек просто теряется при виде возможностей Linux: где? на какие кнопки нажимать? А кнопок-то и нет...

Можно попытаться описать операционную систему как большой и сложный универсальный инструмент решения любых задач. Предполагается, что пользователь, прочтя документацию, в которой описывается как работает система и как применять её в различных ситуациях, сможет решать и свои задачи. Правда, для этого ему придётся прочесть большую часть документации по системе (в том числе и технической) и перепрограммировать некоторые части системы сообразно своим нуждам. На такой подвиг способны немногие, времени это займёт немало, да и вероятность ошибки (которая тем выше, чем сложнее средства управления системой) при таком подходе недопустимо велика. Сами утилиты или службы Linux, каждую из которых можно "окинуть взором" и понять, что и как она умеет, и чего в ней не хватает, разрабатываются именно теми из пользователей, у которых хватает времени, знаний и навыков на такое полное освоение (см. лекцию Политика свободного лицензирования. История Linux: от ядра к дистрибутивам). Вывод: пользователь -- не разработчик, ему всё-таки важнее быстро и качественно решить задачу, чем долго и качественно улучшать инструмент решения.

Можно пойти обратным путём: попытаться предусмотреть все основные способы использования операционной системы на всех основных пользовательских задачах, и на каждый такой способ создать (запрограммировать) отдельную часть, управляемую "кнопкой" или утилитой. Эту часть обычно называют "решением" (solution), и в документации обычно пишут, что должно быть "на входе" системы, и что получается "на выходе" после применения решения. Если пользователь не умеет сам поставить задачу, или делает это в неопределённой форме ("хочу, чтобы был текст", "хочу, чтобы играла музыка"), этот способ работает на ура: та же игровая приставка -- это отличное решение крайне неопределённой задачи "хочу без толку потратить время". Однако, стоит пользователю захотеть чего-то конкретного, начинаются трудности. Трудности могут быстро стать непреодолимыми, как только для этого "конкретного" не окажется готового решения: внутренняя структура систем, ориентированных на "решения", столь сложна и столь плохо документирована, что сделать что-либо вручную, скорее всего, не удастся. Вывод: пользователь, понимающий суть собственных задач, -- не "клиент", он должен иметь возможность быстро и качественно решать задачи самостоятельно, а не выбирать то из готовых "решений", которое нанесёт меньше вреда.

Что же нужно идеальному -- достаточно подготовленному, чтобы действовать самостоятельно, и достаточно занятому, чтобы не переделывать системы -- пользователю? По-видимому, механизм, с помощью которого можно сформулировать и придать операционной системе все требуемые свойства, имея возможность описывать решение задачи по крайней мере на том же уровне конкретности, на котором было поставлено её условие. Большая часть других, не нужных для решения собственных задач пользователя, свойств должны быть "стандартными" и не требовать его вмешательства.

Профиль системы

Так возникает идея разделить систему на два подмножества: профиль и реализацию. Всё, что не потребует вмешательства пользователя, необходимо запрограммировать и использовать в готовом виде в качестве составных частей реализации. В Linux этому соответствуют программы и подпрограммы: ядро, модули, демоны, утилиты; используемые ими библиотеки и прочие разделяемые файлы и т. п. Реализация -- это монолитная, неизменяемая часть системы, устроенная по типу "решений" основных задач, только задачи эти, как правило, не совпадают с задачами пользователей, а только помогают решать их, являясь как бы строительным материалом, деталями и инструментами сборки "больших" решений.

Всё, чего может коснуться рука человека, из реализации переносится в профиль системы. Профиль задаёт поведение реализации на данных пользователя, и должен быть устроен так, чтобы пользователь мог его беспрепятственно изменять, если понадобится. С одной стороны, это может быть вариант "высокоуровневого программирования", когда пользователь описывает алгоритм решения и структуру используемых данных на некотором высокоуровневом языке (специализированном или общем, например, на shell). С другой стороны задание свойств может превращаться в указание модификаторов поведения, когда пользователь просто перечисляет необходимые параметры работы программы, которые изменяют её заранее известную, но достаточно общую функциональность.

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

профиль

Изменяемая часть системы, задающая её поведение во время работы.

Как проще всего создать профиль, если не всей системы, то хотя бы её компонента (программы)? Один из вариантов такой: снабдить программу функцией "сохранить настройки", тогда можно будет эту программу запустить, любым способом добиться её работоспособности, а после зафиксировать достигнутое состояние с помощью этой функции. При этом по началу совершенно неважно, как выглядят эти настройки: программа-то заработала, значит, цель достигнута (проницательный Мефодий немедленно заметил, что название функции "сохранить настройки" как-то подозрительно похоже на название кнопки).

Зачастую для того, чтобы собрать более или менее отвечающий требованиям пользователя профиль, задействуется больше ресурсов, чем для работы самой программы. Утилита автоматической настройки выглядит эдаким шаманом, или кудесником, который, задав всего несколько вопросов человеку, невесть как приводит систему в работоспособное состояние. Такая подсистема и называется-то "wizard", причём в русском переводе её отчего-то стесняются называть "шаманом", а величают уважительно "мастером".

Вот пример поведения обычного шамана-настройщика (пакет wvdial, заведующий модемным подключением к Internet):

[root@localhost root]# wvdialconf 
 Usage: wvdialconf <configfile-name>
        (create/update a wvdial.conf file automatically)
[root@localhost root]# wvdialconf .wvdialrc
 Scanning your serial ports for a modem.
 Port Scan<*1>: Scanning ttyS4 first, /dev/modem is a link to it.
  . . .
 ttyS4<*1>: Modem Identifier: ATI -- Xircom CardBus 10/100+Modem 56 (Revision 2.40)
  . . .
 ttyS4<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
 ircomm0<*1>: ATQ0 V1 E1 -- failed at 9600 and 19200 baud.
  . . .
 ircomm9<*1>: ATQ0 V1 E1 -- failed at 9600 and 19200 baud.
 Port Scan<*1>: LT0  
  . . .
 ttyS0<*1>: ATQ0 V1 E1 -- and failed too at 115200, giving up.
  . . .
 ttyS1<*1>: ATQ0 V1 E1 -- and failed too at 115200, giving up.
 Port Scan<*1>: S2   S3   S5   S6   S7   S8   S10  
  . . .
 Port Scan<*1>: USB11 USB12 USB13 USB14 USB15 
 Found a modem on /dev/ttyS4, using link /dev/modem in config.
 Modem configuration written to .wvdialrc.
 ttyS4<Info>: Speed 115200; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"

Кудесник wvdialconf

Даже ни о каких наводящих вопросах речи не зашло! Программа проверила более полусотни устройств, не модемы ли они, но нашла всего одно -- /dev/ttyS4. Его настройки определились автоматически (и хорошо, потому что Мефодий не знает, что такое "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"). Профиль (а wvdialrc создаёт именно профиль) лежит теперь в файле .wvdialrc, так что программа wvdial начнёт работать с модемом, нуждаясь только в пользовательских настройках (входное имя, пароль и т. п.).

Яркий пример того, как элементы реализации связываются профилем в единую подсистему для решения определённой задачи -- командная строка и сценарии командного интерпретатора. Здесь утилиты играют роль элементов реализации, их параметры -- роль "настроечной" части профиля, а способ их объединения в сценарий -- "программируемой" части профиля. Скажем, команда find /etc -type f 2> /dev/null | xargs -n1 file | cut -d: -f2 | sort | uniq -c задействует шесть утилит системы: командную оболочку, find, xargs, cut, sort и uniq, причём четыре из них запускаются с изменённым профилем(1). Командная оболочка дополнительно программируется для создания конвейера между командами.

Конфигурационный файл

Задание профиля с помощью командной строки -- метод далеко не всегда удобный. Даже при работе с самой командной строкой используется окружение для сохранения настроек, чтобы не задавать их всякий раз и для всякой команды. Что уж говорить о сложных системных службах, свойства которых должны сохраняться не от сеанса к сеансу, а постоянно (в том числе при перезагрузке системы). Вывод прост: профиль необходимо держать в файле, вроде того, что создаётся по команде "сохранить настройки".

Однако сам подход к хранению профиля в файле, при котором пользователь не может изменять этот файл напрямую, а пользуется "умными" конфигураторами, удобен только в случаях, когда настроек много, а цена ошибки невелика (например, при настройке внешнего вида рабочего стола). В общем случае довольно сложно задать поведение системы на основании описания (часто неявного) свойств того, что эта система должна получать в результате. Иными словами, из описания того, что должно получиться, далеко не всегда можно автоматически сделать вывод, как оно должно получаться.

конфигурационный файл

Текстовый файл, содержащий настройки какой-нибудь части системы (утилиты, демона и т. п.). Как правило, считывается ею при запуске. Типичный для Linux способ организации профиля.

Одним словом, если есть конфигурационный файл, то должны быть и средства редактирования этого файла. Учитывая то, что в Linux есть высокоразвитая система хранения и переработки (как ручной, так и автоматической) данных в текстовом виде, изобретать какой-то новый формат не лучше, чем изобретать новый велосипед. Тем более, что именно текст, разделённый на строки и слова, лучше всего подходит тогда, когда есть чёткое деление профиля на объекты управления и их свойства (например, настройки какого-нибудь демона и значения этих настроек). Вдобавок именно со структурированными текстами отменно управляются текстовые редакторы Linux: Vi, Emacs и др.

methody@localhost:~ $ cat .vimrc
 so $VIMRUNTIME/vimrc_example.vim
 " Some mappings
 map <F2> :wall!^M
 map! <F2> ^O:wall!^M
 " Tune up
 set shiftwidth=2 tabstop=8 history=200 viminfo='50
 set showmode showmatch showcmd ruler modeline
 set autoindent ignorecase smartcase
 set nohlsearch noincsearch
 set dir=/var/tmp
 set wildmode=list:longest,full
 set wildmenu
 " Colouring
 syntax on
 colorscheme desert

Настройки редактора vim

Вот как выглядит конфигурационный файл для Vi, написанный Мефодием на основе взятого у Гуревича. Легко заметить, что файл состоит из команд режима командной строки Vi с комментариями (в отличие от большинства утилит Linux, в Vi комментарии начинаются на """). Символы "^O" и "^M" -- это именно соответствующие управляющие символы (вставленные в текстовый файл с помощью "^V", см. лекцию Текстовые редакторы). Такой конфигурационный файл легко понимать и изменять.

Как уже было замечено, набор переменных окружения составляет особенный профиль, к которому чувствительны все запускаемые программы -- в этом его достоинство. Задаются переменные окружения обычно в командном сценарии, который тоже можно рассматривать как конфигурационный файл). Например, во многих дистрибутивах используется конфигурационный файл .i18n для настройки языковых особенностей клавиатуры, языка вывода сообщений и т. п.(2):

methody@localhost:~ $ cat .i18n 
 LANG=ru_RU.KOI8-R
 LANGUAGE=ru_RU.KOI8-R
 SYSFONTACM=koi8-r
 SYSFONT=UniCyr_8x16
 DICTIONARY=russian
 MPAGE="-CKOI8-R"
 export DICTIONARY MPAGE

Файл настройки языковых особенностей

Однако хранить настройки специфической программы (не нужные всем остальным) в окружении -- не самое удачное решение: синтаксис, задающий переменную окружения, слишком прост (имя_переменной = значение), а самих переменных становится слишком много, поэтому при просмотре трудно выделить, какая из них к какой группе настроек относится. Если пытаться упаковать все настройки в значение одной переменной, это значение окажется трудночитаемым, и всё преимущество текстового формата сойдёт на нет. Например, стандартный конфигурационный файл утилиты ls (точнее, только её цветовых предпочтений) -- /etc/DIR_COLORS (его можно подменить личным файлом ~/.dir_colors) занимает около ста строк вместе с комментариями. Команда ls использует не этот файл, а создаваемую утилитой dircolors переменную LS_COLORS, значение которой -- 600-символьная строка безо всяких комментариев.

Если профиль слишком велик, держать его в одном конфигурационном файле -- значит, доставлять пользователю сомнительное удовольствие разбирать этот файл целиком даже при необходимости внести минимальное изменение. Методов борьбы с неудобочитаемостью несколько. Во-первых, уже известный по лекции Этапы загрузки системы механизм ". d": файл разделяется на несколько независимых друг от друга так, что редактировать приходится только один из файлов, а программа во время самонастройки считывает все.

Другой способ опирается на то, что изменения, которые пользователь вносит в профиль, как правило существенно меньше объёма всего профиля. Поэтому может быть выгодно хранить все настройки по умолчанию в каком-нибудь файле, менять который вообще не надо, а файл пользовательских настроек использовать как бы "поверх", изменяя профиль в соответствии с ними после того, как выставлен профиль по умолчанию. Дополнительная выгода такого способа -- в том, что пользователь всегда может подглядеть в "большой" файл, чтобы узнать как оформляется та или иная настройка. Например, утилита updfstab, которая изменяет содержимое /etc/fstab при появлении или удалении съёмного дискового носителя (например, лазерного диска), считывает данные из конфигурационного файла /etc/updfstab.conf. Сам этот файл состоит из единственной строки: include /etc/updfstab.conf.default, что приводит к чтению файла с настройками по умолчанию, где задан способ работы со многими съёмными устройствами системы. Если администратору нужно как-то изменить поведение updfstab в отношении определённого устройства, он копирует соответствующую группу настроек из updfstab.conf.default в updfstab.conf после строчки include... и исправляет их. То, что эти группы настроек читаются дважды, не играет особой роли: чтение коротких файлов выполняется быстро.

Наконец, третий способ сделать конфигурационный файл удобочитаемым -- это секционирование профиля, когда все настройки разбиваются на группы, каждой группе даётся собственное имя, и синтаксис конфигурационного файла проектируется так, чтобы границы групп хорошо различались при просмотре. В сущности, этот способ -- предок схемы ". d", где группе соответствует отдельный файл, однако нередки ситуации, когда разбиение на файлы неудобно (например, если группы не полностью независимы, поэтому может понадобиться редактировать их сразу и несколько). Конфигурационный файл утилиты дозвона wvdial, например, секционируется по адресату (провайдеру) плюс отдельная секция "по умолчанию". Сами секции отделяются друг от друга заголовками, заключёнными в квадратные скобки:

root@localhost:~> cat .wvdialrc 
 [Dialer Defaults]
 Modem = /dev/modem
 Baud = 115200
 Init1 = ATZ
 Init2 = ATQ0 L0 M4 V1 E1 S0=0 &C1 &D2 +FCLASS=0
 Auto DNS = on
 Modem Type = Analog Modem

 [Dialer hotspace]
 Phone = 0123456
 Username = fireman
 Password = Fire!Fire!
 TOnline = true

 [Dialer warlock]
 Phone = 0246813
 Username = cop-120
 Password = gimmethegun
 Force Address=10.0.0.120

Секционированный конфигурационный файл

Утилита wvdial обладает высокоразвитым искусственным интеллектом: она самостоятельно догадывается, какой именно тип идентификации используется на сервере. Например, "с той стороны" может оказаться терминал Linux, которому требуется сначала ввести обыкновенное входное имя и пароль, затем надо получить командную строку, затем запустить на сервере сетевой демон pppd, и только затем запустить pppd на собственной машине. Другой вариант: pppd на сервере уже запущен, а настройки "Username" и "Password" означают идентификационную информацию протокола CHAP, используемого pppd. Обо всём этом и о многом другом wvdial умеет догадываться, как wvdialconf умел определять, какое же из устройств -- модем.

Однако на любой искусственный интеллект найдётся непостижимая ему жизненная ситуация. На одном из серверов (секция "Dialer hotspace") тоже стоит программа с зачатками искусственного интеллекта, которая тоже пытается определить, каким способом хочет идентифицироваться позвонивший. Оттого эти два кудесника, созвонившись, всё ждут, пока кто-нибудь не проявит себя... Помогает настройка TOnline, которая заставляет wvdial немедленно использовать протокол ppp, на что сервер, подумавши "ах, ppp!", с облегчением запускает pppd. Остаётся вопросом: почему эта полезная настройка никак не отражена в документации (её нашёл в исходных текстах программы Гуревич)? Не потому ли, что пара wvdialconf-wvdial не по Linux-овски стремится всё делать за пользователя, а стало быть, пользовательская документация для разработчиков этой программы -- не главное?..

Идею чтения настроек по умолчанию можно развить чуть дальше. Оказывается, бывает удобно, когда описание настройки помещено не в руководство, а непосредственно в конфигурационный файл в виде комментариев. Тогда при изменении этой настройки пользователь сразу видит, что она из себя представляет, при этом отпадает необходимость сначала находить строчку в файле, а потом искать её же в руководстве. Такой способ оформления называется самодокументированием профиля и часто используется. Например, файл /etc/man.conf, управляющий работой команды man, оформлен в самодокументированном стиле:

methody@localhost:~ $ cat /etc/man.conf
  . . .
 # NOCACHE keeps man from creating cache pages ("cat pages")
 # (generally one enables/disable cat page creation by creating/deleting
 # the directory they would live in - man never does mkdir)
 #
 # NOCACHE
 # The command "man -a xyzzy" will show all man pages for xyzzy.
 # When CMP is defined man will try to avoid showing the same
 # text twice. (But compressed pages compare unequal.)
 #
 CMP             /usr/bin/cmp -s
  . . .

Самодокументированный конфигурационный файл

Мефодий, может быть, и не понял бы сразу, зачем команде man использовать утилиту cmp, однако в поясняющем комментарии написано: когда нужно показать несколько руководств разом, предварительно они сравниваются, и показываются только несовпадающие.

Если пойти ещё дальше, то можно создать несколько различных файлов с примерами настроек, чтобы пользователь мог взять один из них и довести до нужного ему состояния. Именно такую -- демонстрационную -- настройку Мефодий и включил в качестве настройки по умолчанию в свой .vimrc (в первой строке). Кстати, на самом деле профиль Vi весьма сложен, но большинство его настроек по умолчанию находятся в различных файлах дерева каталогов /usr/share/vim -- эдакая "схема. d/. d", где файлы профиля, соответствующие подгруппам настроек, лежат в подкаталогах, соответствующих группам. Включение определённого настроечного файла может происходить неявно: например, строчка colorscheme desert из .vimrc приводит к чтению /usr/share/vim/colors/desert.vim.

Конфигурационные файлы могут иметь довольно замысловатый синтаксис, если соответствуют сложным структурам данных (таковы, например, настройка irc-клиента irssi) или содержать в себе дополнительные средства самодокументиования (например, файл настройки текстового www-броузера lynx не просто хорошо документирован, но и размечен теми же средствами, какие используются в самом броузере для представления HTML).

Изменение конфигурационных файлов

Как правило, конфигурационный файл считывается программой при запуске, отражая, таким образом, её состояние на момент старта. Изменения настроек работающей программы в конфигурационном файле, как правило, не отражаются. Тому есть несколько причин: не стоит превращать файл, изредка редактируемый пользователем, в файл, изменение которого происходит постоянно; не стоит держать конфигурационный файл всегда открытым; тяжело, изменяя файл автоматически, не испортить структуру комментариев (интерпретируемых не машиной, а пользователем) и т. д. Впрочем, многие утилиты, особенно использующие графическую среду, могут записывать настройки в файл по окончании работы. Большинство конфигурационных файлов весьма удобно редактировать вручную, с помощью Vi или Emacs (для файлов более или менее похожих используется общая подсветка синтаксиса, а для наиболее популярных есть и собственные варианты подсветки).

В /etc хранятся настройки системных служб, в том числе настройки по умолчанию, настройки по умолчанию пользовательских утилит, профили командных интерпретаторов, а также настройки, используемые в процессе загрузки системы (например, modules.conf). Там же располагаются и стартовые сценарии, о которых рассказано в лекции Этапы загрузки системы. Чего не стоит искать в /etc, так это разнообразных примеров настройки той или иной службы. Считается, что пример -- это часть документации, и их стоит помещать, например, в /usr/share/doc/название_службы/examples.

Файлы, имеющие отношение к процессу досистемной загрузки, обычно лежат не там, а в /boot, это стоит иметь в виду, так как /boot/grub/menu.lst -- тоже часть профиля системы, хотя и довольно специфическая. В профиль системы входит содержимое каталогов etc из т. н. "песочниц", расположенных обычно в /var/lib.

Смысл термина "песочница" вот в чём. В Linux есть замечательный системный вызов chroot() и использующая его утилита chroot, формат командной строки которой chroot каталог команда. Эта утилита запускает команду, изменив окружение таким образом, что та считает каталог -- корневым. Соответственно, все подкаталоги каталога кажутся команде каталогами первого уровня вложенности, и т. д. Если необходимо во что бы то ни стало ограничить область действия некоторой утилиты (например, по причине её небезопасности), можно запускать её с помощью chroot. Тогда, даже имея права суперпользователя, эта утилита получит доступ только к каталогу и его подкаталогам, а /etc и прочие важные части системы окажутся в неприкосновенности. Сам каталог как раз и играет роль "песочницы", в которую утилиту "пустили поиграть", позволяя вытворять что угодно. Часто бывает, что в песочнице есть и свой каталог etc, содержащий необходимые для запуска утилиты (или системной службы) настройки. Вот этот-то etc из "песочницы" также входит в список каталогов, хранящих профиль системы.

В /etc могут находиться не только файлы, но и подкаталоги (особенно в стиле ". d") и целые поддеревья каталогов. Например, в некоторых дистрибутивах Linux используется подкаталог /etc/sysconfig. Этот каталог создаётся и заполняется файлами при установке системы или при запуске специального "конфигуратора" -- программы-кудесника, задающей наводящие вопросы. Некоторые стартовые сценарии, использующие полученные настройки, также лежат в этом каталоге или его подкаталогах. Если в системе есть каталог /etc/sysconfig, там должны оказаться настройки, относящиеся не к самим службам или утилитам, а к способу их запуска при загрузке, а также языковые и сетевые настройки, тип мыши и т. д.

Системные конфигурационные файлы

Подсистема учётных записей

Несколько конфигурационных файлов и способы работы с ними заслуживают отдельного рассмотрения. В первую очередь Мефодий заинтересовался системой учётных записей, о которой упоминалось сразу в нескольких предыдущих лекциях. Итак, существует два файла, доступных для чтения всем пользователям: /etc/passwd, хранящий учётные данные пользователей, и /etc/group, определяющий членство пользователей в группах (во всех, кроме группы по умолчанию):

methody@localhost:~ $ cat /etc/passwd
 root:x:0:0:System Administrator:/root:/bin/bash
 bin:x:1:1:bin:/:/dev/null
 daemon:x:2:2:daemon:/:/dev/null
 adm:x:3:4:adm:/var/adm:/dev/null
 lp:x:4:7:lp:/var/spool/lpd:/dev/null
  . . .
 nobody:x:99:99:Nobody:/var/nobody:/dev/null
 shogun:x:400:400:Лев Гуревич:/home/shogun:/bin/zsh
 methody:x:503:503:Мефодий Кашин:/home/methody:/bin/bash
methody@localhost:~ $ cat /etc/group 
 root:x:0:root
 bin:x:1:root,bin,daemon
 daemon:x:2:root,bin,daemon
 sys:x:3:root,bin,adm
 adm:x:4:root,adm,daemon,shogun
 wheel:x:10:root,shogun
  . . .
 proc:x:19:root,shogun
 shogun:x:400:
 methody:x:503:

Файлы /etc/passwd и /etc/group

Оба файла состоят из строк, поля которых разделяются двоеточиями. В файле passwd -- семь полей. Первое из них определяет входное имя пользователя -- то самое, что вводится в ответ на "login:". Второе поле раньше, в ранних версиях UNIX, использовалось для хранения ключа пароля. В Linux пользовательские пароли не хранятся нигде, ни в явном виде, ни в зашифрованном. Вместо этого хранится ключ (hash) пароля -- комбинация символов, однозначно соответствующая паролю, из которой, однако, сам пароль получить нельзя. Иными словами, существует алгоритм превращения пароля в ключ, а алгоритма превращения ключа в пароль нет. Когда пользователь регистрируется в системе, из введённого им пароля изготавливается ещё один ключ. Если он совпадает с тем, что хранится в учётной записи, значит, пароль правильный.

Авторы UNIX предполагали, что, раз пароль из ключа получить нельзя, ключ можно выставлять на всеобщее обозрение, однако выяснилось, что, узнав ключ, пароль можно попросту подобрать на очень мощной машине или в предположении, что пароль -- это английское слово, год рождения, имя любимой кошки и т. п. Если подбор пароля сопровождается неудачными попытками входа в систему, это отражается в системных журналах и может быть легко замечено. А завладев ключом, злоумышленник сможет заняться подбором втихомолку на каком-нибудь суперкомпьютере. В конце концов, ключ не нужен никому, кроме подсистемы идентификации, поэтому его, вместе с другими полями учётной записи, о которых знать всем не обязательно, из /etc/passwd перенесли в "теневой" файл учётных записей /etc/shadow. На месте ключа в Linux должна быть буква "x"; если там стоит что-то другое, идентификация пользователя не сработает, и он не сможет войти в систему.

Третье и четвёртое поля passwd -- идентификатор пользователя и идентификатор группы по умолчанию. Как уже говорилось в лекции Права доступа именно идентификатор пользователя, а не его входное имя, однозначно определяет пользователя в системе и его права. Вполне возможно создать несколько учётных записей с одинаковыми UID, которые различаются другими полями. Тогда при регистрации в системе под именами из этих записей, пользователи могут получать разные домашние каталоги и командные оболочки, разное членство в группах, но иметь одинаковый UID. Пятое поле отводится под "полное имя" пользователя; это единственное поле passwd, содержимое которого ни на что не влияет. Наконец, шестое и седьмое поля содержат домашний каталог и стартовый командный интерпретатор пользователя. Если седьмое поле пусто, подразумевается /bin/sh, а если его содержимое не встречается в файле /etc/shells, содержащего допустимые командные интерпретаторы, неизбежны трудности при идентификации пользователя.

Строки файла /etc/group состоят из четырёх полей, причём второе -- ключ группового пароля -- обычно не используется. Первое поле -- это имя группы, третье -- это идентификатор группы, а четвёртое -- это список входных имён пользователей, которые в эту группу входят (более точно -- для которых эта группа является дополнительной, так как группа по умолчанию указывается в /etc/passwd, хотя никто не мешает продублировать группу по умолчанию и в /etc/group). Таким образом, определение членства пользователя в группах зависит не от его UID, а от входного имени.

Упомянутый выше файл /etc/shadow, доступ к которому имеет только суперпользователь, также состоит из полей, разделяемых двоеточиями. Помимо входного имени и ключа пароля там указываются различные временные характеристики учётной записи: нижняя и верхняя граница времени жизни пароля, самой учётной записи, дата последнего изменения и т. п. Ключ пароля (второе поле) указывается в одном из поддерживаемых форматов, а если формат не опознан, вся учётная запись считается недействительной. Поэтому один из способов запретить регистрацию пользователя в системе -- добавить один символ (например, "!") прямо в поле ключа, отчего всё поле становится синтаксически неправильным. Вновь разрешить пользователю входить в систему можно, удалив из поля лишний символ. Именно так работает (с ключами "-L", lock, и "-U", unlock) утилита usermod, изменяющая учётную запись. С помощью этой утилиты можно отредактировать и все остальные поля как passwd, так и shadow.

Добавить и удалить пользователя или группу можно с помощью утилит useradd, userdel, groupadd и groupdel соответственно. Не стоит пользоваться текстовым редактором, так как он не гарантирует атомарности операции добавления/удаления, хотя бы потому, что изменению подлежат сразу два файла -- passwd и shadow. Даже если необходимо отредактировать /etc/passwd или /etc/group (например, для добавления пользователя в группу или удаления его оттуда), стоит запускать не просто редактор, а vipw или vigr (именно их поведение, позволяющее соблюсти атомарность, копирует утилита visudo, описанная ранее).

[root@localhost root]# useradd -g users -G proc,cdrom -c "Incognito" incognito
[root@localhost root]# id incognito
 uid=504(incognito) gid=100(users) groups=100(users),19(proc),22(cdrom)
[root@localhost root]# userdel -r incognito
[root@localhost root]# id incognito
 id: incognito: No such user

Добавление и удаление пользователя

Здесь был добавлен пользователь incognito, группа по умолчанию -- users, член групп proc и cdrom, полное имя -- "Incognito". Стоит заметить, что пароль для этой учётной записи установлен не был (чтобы создать пароль, стоило запустить команду passwd incognito), и, даже если бы пользователя тут же не удалили (userdel -r удаляет также и домашний каталог, и почтовый ящик), зарегистрироваться в системе под именем incognito было бы всё равно невозможно.

Подсистема идентификации

Подсистемой учётных записей пользуется подсистема идентификации, которая в Linux имеет модульную структуру и называется PAM (Pluggable Authentication Modules, т. е. Подключаемые Модули Идентификации). Идея PAM -- в том, чтобы унифицировать и, вместе с тем, сделать более гибкой любые процедуры идентификации в системе -- начиная от команды login и заканчивая доступом к файлам по протоколу, скажем, FTP. Для этого недостаточно просто написать "библиотеку идентификации" и заставить все программы её использовать. В зависимости от того, для чего производится идентификация, условия, при которых она будет успешной, могут быть более или менее строгими, а если она прошла успешно, бывает нужно выполнить действия, связанные не с определением пользователя, а с настройкой системы.

В большинстве дистрибутивов PAM обучен схеме ". d", и настройки каждой службы, которая использует идентификацию, лежат в отдельном файле:

[root@localhost root]# ls /etc/pam.d
 chpasswd           groupdel  other   system-auth                 userdel
 chpasswd-newusers  groupmod  passwd  system-auth-use_first_pass  usermod
 crond              login     sshd    user-group-mod
 groupadd           newusers  su      useradd

Подключаемые Модули Идентификации (PAM)

В PAM определено четыре случая, требующие идентификации: auth -- собственно идентификация, определение, тот ли пользователь, за кого он себя выдаёт, account -- определение, всё ли хорошо с учётной записью пользователя, password -- изменение пароля в учётной записи, и session -- дополнительные действия непосредственно перед или непосредственно после того, как пользователь получит доступ к затребованной услуге. Эти значения принимает первое поле любого файла настройки из pam.d, а в третьем поле записывается модуль, который проверяет какой-нибудь из аспектов идентификации. Второе поле определяет, как успех или неуспех проверки одного модуля влияет на общий успех или неуспех идентификации данного типа (например, required означает, что в случае неуспеха модуля проверка пройдена не будет). Четвёртое и последующие поля отведены под параметры модуля.

[root@localhost root]# cat /etc/pam.d/login 
 auth     include        system-auth
 auth     required       pam_nologin.so
 account  include        system-auth
 password include        system-auth
 session  include        system-auth
 session  optional       pam_console.so
[root@localhost root]# cat /etc/pam.d/system-auth
 auth     required       pam_tcb.so shadow count=8 nullok
 account  required       pam_tcb.so shadow
 password required       pam_tcb.so use_authtok shadow count=8 write_to=tcb
 session  required       pam_tcb.so

Настройка PAM для login

Такие настройки login обнаружил Мефодий на своём компьютере. Во всех четырёх случаях используется включаемый файл system-auth (к нему обращаются и другие службы), с некоторыми дополнениями: во время идентификации pam_nologin.so дополнительно проверяет, не запрещено ли пользователям регистрироваться вообще (как это бывает за несколько минут до перезагрузки системы), а перед входом в систему и после выхода из неё pam_console.so выполняет описанную в лекции Права доступа "передачу прав на владение устройствами" (и, соответственно, лишение пользователя этих прав).

Каталог /etc/pam.d -- замечательный пример того, как профиль определяет поведение системы. В частности, четыре первых строки из system-auth показывают, что в этом дистрибутиве используется не просто "теневой" файл паролей, а схема TCB, описанная в лекции Права доступа. (Как уже известно Мефодию, в этой схеме вместо общего для всех /etc/shadow задействованы файлы вида /etc/tcb/входное_имя/shadow, причём права доступа к ним устроены таким образом, чтобы при выполнении команды passwd можно было обойтись без подмены пользовательского идентификатора на суперпользовательский).

Подсистема системных журналов

Проста и остроумна в Linux подсистема ведения системных журналов -- демон syslogd, управляемый конфигурационным файлом /etc/syslog.conf и ". d"-каталогом /etc/syslog.d. Если какой-нибудь демон или служба желает сообщить системе о том, что наступило событие, которое стоит запомнить, у неё есть два пути. Во-первых, можно просто добавлять очередную запись в файл, который сам этот демон и открыл; этот файл будет журналом его сообщений. Во-вторых, можно воспользоваться системным вызовом syslog(), который переадресует текстовое сообщение специальному демону -- syslogd -- а уж тот разберётся, что с этим сообщением делать: записать в файл, вывести на 12-ю консоль или забыть о нём. Второй путь (централизованная журнализация) предпочтительнее почти всегда; исключение -- случай, когда сообщения по какой-то причине не могут быть текстовыми или этих сообщений предполагается посылать так много, что syslogd просто не справится.

Все события, сообщаемые syslogd, подразделяются горизонтально -- по типу службы (facility), с которой это событие произошло события, и вертикально -- по степени его важности (priority). Типов событий насчитывается около двадцати (среди них auth, daemon, kern, mail и т. п., а также восемь неименованных, от local0 до local7). Степеней важности всего восемь, по возрастанию важности: debug, info, notice, warning, err, crit, alert и emerg. Таким образом, каждое событие определяется парой значений, например, mail.err означает для syslogd событие, связанное с почтой, притом важности не меньшей err. Из таких пар (с возможной заменой типа или важности на "*", что означает "любые", или none, что означает "никакие") составляется конфигурационный файл /etc/syslog.conf:

[root@localhost root]# cat /etc/syslog.conf
 *.notice;mail.err;authpriv.err  /var/log/messages
 authpriv.*;auth.*               /var/log/security.log
 *.emerg                         *
 *.*                             /dev/tty12
 mail.info                       /var/log/maillog

Настройка системных журналов

В первом поле строки указываются профили сообщений, разделённые символом ";", а во втором -- хранилище сообщений (файл, терминал, есть способы отдавать их на обработку программе и пересылать по сети). В примере в файл /var/log/messages попадают все сообщения важности не меньшей, чем notice, за исключением сообщений типа mail и authpriv, которые попадают туда, только если имеют важность не ниже err. Сообщения типа authpriv и auth любой важности попадают в файл /var/log/security.log, а типа mail и важности не ниже info -- в файл /var/log/maillog. Сообщения типа emerg (наивысшей важности) выводятся на все терминалы системы, и, наконец, все сообщения выводятся на 12-ю виртуальную консоль.

Во многих системах используется основательно доработанный syslogd, позволяющий фильтровать сообщения не только по типу/важности, но и, например, по отправителю, задавать точные (а не "не меньшие") значения priority и т. п., однако такие доработки нужны для того, чтобы либо вести практически нефильтрованную журнализацию (получаются системные журналы совершенно нечитаемого объёма), либо отводить поток сообщений определённой службы в отдельный журнал, опять-таки, не для чтения, а для последующей обработки.

Стоит заметить, что каталог /etc/syslog.d в новых версиях syslogd предназначен для хранения не профильных конфигурационных файлов в стиле ". d", а сокетов, из которых демон может получать сообщения так же, как и из сети или в результате системного вызова syslog().

Выполнение действий по расписанию

Другой пример типичной для Linux службы, управляемой конфигурационным файлом, -- демон cron(3), регулярно выполняющий в заданное время заданные действия. Время от времени в системе необходимо обновлять разнообразные файлы, например, базы данных антивирусов (вирусов в Linux нет, а антивирусы есть!), базу данных whatis или список всех доступных на чтение файлов системы, locatedb (поискать по этому списку можно командой locate); нужно собирать статистику по работе системы, анализировать цельность системы (этим занимаются службы OSec, TripWire или AIDE) и производить множество других регулярных действий. Всем этим и занимается демон cron.

Конфигурационный файл демона cron называется /etc/crontab.

[root@localhost root]# cat /etc/crontab
 #minute (0-59),
 #|      hour (0-23),
 #|      |       day of the month (1-31),
 #|      |       |       month of the year (1-12),
 #|      |       |       |       day of the week (0-6 with 0=Sunday).
 #|      |       |       |       |       user
 #|      |       |       |       |       |       commands
 01      *       *       *       *       root    run-parts /etc/cron.hourly
 02      4       *       *       *       root    run-parts /etc/cron.daily
 22      4       *       *       0       root    run-parts /etc/cron.weekly
 42      4       1       *       *       root    run-parts /etc/cron.monthly

Настройка cron

Первые пять полей этого файла определяют время запуска команды: минуту, час, число месяца, месяц и день недели. Символ "*" означает, что соответствующая часть даты не учитывается. Шестое поле -- имя пользователя, от лица которого запускаются команда, указанная в остальных полях строки. Так, в примере команда run-parts /etc/cron.weekly будет запускаться в 4 часа 22 минуты каждое воскресенье (нулевой день) любого числа любого месяца.

Как видно из примера, обычно /etc/crontab невелик: чаще всего он состоит из почасового, подённого, понедельного и помесячного запуска специального сценария (в примере -- run-parts). Этот сценарии реализует упрощённую схему ". d", он попросту запускает отсортированные в лексикографическом порядке(4) сценарии из соответствующего каталога (например, из /etc/cron.daily):

[root@localhost root]# ls /etc/cron.daily 
 000anacron  logrotate  makewhatis  osec  stmpclean  updatedb

Сценарии, запускаемые ежедневно

Вот что происходит каждый день на машине Мефодия: запуск anacron и "прокручивание" системных журналов (об этом речь пойдёт далее), обновление базы whatis, проверка цельности системы с помощью osec, прореживание старых и неиспользуемых файлов в /tmp (утилита stmpclean) и, наконец, обновление базы locatedb.

Пользователям системы можно разрешить иметь собственные расписания, также обрабатываемые демоном cron. Эти расписания имеют тот же синтаксис, что и crontab, только шестое поле ("user") в них отсутствует. Редактировать пользовательские таблицы рекомендуется с помощью команды crontab -e (чтобы не подсунуть демону синтаксически неверный файл). Сами таблицы могут храниться, в зависимости от версии и настроек cron, в /var/spool/cron/crontabs, /var/spool/cron, /var/cron/tabs или ещё где-нибудь.

Служба anacron появилась в Linux-системах в то время, когда их начали активно использовать на персональных рабочих станциях. Такие станции, в отличие от серверов, не обязаны работать круглосуточно. Скорее всего, на ночь, на праздники и на время отпуска их выключают. Это значит, что все настройки cron надо менять в соответствии с графиком включений/выключений (иначе cron.daily никогда не выполнится в четыре часа ночи) -- или запускать отдельную службу, которая будет выполнять некоторые задачи не по расписанию, а потому что их давно уже пора запустить(5). Дополнительно anacron рассчитывает запуск задач так, чтобы не перегрузить компьютер работой, если их накопилось слишком много. Конфигурационный файл anacron называется /etc/anacrontab.

"Прокручивание" системных журналов

Ещё изучая работу syslog, Мефодий не расставался с мыслью, что файл, в котором записывается системный журнал, постоянно растёт. Это значит, что каков бы ни был размер файловой системы /var, она в конце концов заполнится журналами под завязку -- если как-то их не укорачивать. К сожалению, в Linux укоротить файл от начала, отрезав самые старые записи, нельзя, как нельзя и добавлять новые записи в начало файла. Эти операции легко реализовать с помощью копирования нужной области в новый файл и последующего переименования, но, во-первых, соблюсти атомарность таких составных операций нелегко, а во-вторых, они требуют удвоенного места в файловой системе на время работы (и, стало быть, каких-то аварийных процедур на случай нехватки места).

Поэтому в Linux принят другой, существенно менее ресурсоёмкий алгоритм, позволяющий избежать переполнения /var: т. н. "прокручивание" системных журналов. Суть алгоритма в следующем: когда настаёт пора укоротить журнал (например, раз в неделю или если файл журнала достиг определённого размера), этот файл переименовывают, и открывают новый пустой файл с тем же именем. Если хранить несколько (скажем, семь) переименованных старых файлов, с ними уже можно производить операцию "отбрасывания старого": самый старый -- седьмой -- файл удаляется, шестой переименовывается в седьмой, пятый -- в шестой, и т. д. до первого (моложе которого только текущий журнал), который переименовывается во второй. Только тогда можно переименовать текущий журнал в "первый старый", и открыть новый. Получается очередь устаревающих файлов, пополняемая с одной стороны и усекаемая с другой.

Как правило имя "первого старого" журнала получается добавлением к имени журнала суффикса ".1", второго -- ".2" и т. д.:

[root@localhost root]# ls -l /var/log/syslog/messages*
-rw-r-----  1 root adm 292654 Dec 15 14:01 /var/log/syslog/messages
-rw-r-----  1 root adm  34452 Dec 13 01:09 /var/log/syslog/messages.1.bz2
-rw-r-----  1 root adm  35892 Dec  6 09:38 /var/log/syslog/messages.2.bz2
-rw-r-----  1 root adm  60806 Nov 28 10:59 /var/log/syslog/messages.3.bz2
-rw-r-----  1 root adm  61063 Nov 21 10:47 /var/log/syslog/messages.4.bz2
-rw-r-----  1 root adm  60079 Nov 14 21:18 /var/log/syslog/messages.5.bz2

Системный журнал messages

Прокручиванием системных журналов занимается утилита logrotate, которая тоже управляется и конфигурационным файлом /etc/logrotate.conf и ". d"-каталогом /etc/logrotate.d/. Согласно настройкам, старые файлы можно сжимать упаковщиками bzip2 (как в примере) или gzip, можно задавать им определённые права доступа, можно посылать сигнал некоторой службе (чтобы она заметила подмену журнала, если она сама, а не syslogd занимается его пополнением) и т. п.

Конфигурационные файлы в домашнем каталоге

Немало конфигурационных файлов находится в домашнем каталоге пользователя. Этими файлами практически любая утилита может быть перенастроена по сравнению с обычным поведением, или поведением, задаваемым конфигурационным файлом из /etc. В Linux принято предоставлять пользователю возможность задавать профиль любого используемого им инструмента, начиная от простой утилиты и заканчивая графической подсистемой управления "рабочим столом" (см. об этом лекцию Графический интерфейс (X11)). Как правило, имена таких файлов или каталогов начинаются на ".", т. е. считаются скрытыми -- для того, чтобы не засорять выдачу ls. Если пользователю нужно работать не со своими файлами, а именно с настройками, он всегда может использовать ключ "-a" или "-A":

methody@localhost:~ $ ls
 bin  cat.info  cat.stderr  Documents  examples  grep.info  textfile  tmp
methody@localhost:~ $ ls -AF
 .alias          .bashrc     .emacs     .inputrc~       textfile  .Xauthority
 .bash_history   bin/        examples/  .lpoptions      tmp/      .xsession.d/
 .bash_logout    cat.info    grep.info  .pinerc         .viminfo
 .bash_profile   cat.stderr  .i18n      .pyhistory      .vimrc
 .bash_profile~  Documents/  .inputrc   .pythonstartup  .vimrc~
methody@localhost:~ $ rm .*~

Конфигурационные файлы в домашнем каталоге

Многие утилиты создают конфигурационный файл при запуске, если его в домашнем каталоге пользователя нет, поэтому со временем объём ls -A становится всё больше. Файл .lpoptions задаёт параметры подсистемы печати, .pinerc -- это настройки почтового клиента pine, .viminfo -- файл истории команд редактора Vi, а файл .Xauthority и каталог .xsession.d управляют запуском графической подсистемы X``, описанной в лекции Графический интерфейс (X11). Из файлов в примере некоторые вообще не являются "стандартными": так, .aliases и .i18n просто "втягиваются" стартовым командным сценарием bash, потому что упомянуты в нём явно; строго говоря, они могли бы называться и по-другому. Все конфигурационные, стартовые и прочие вспомогательные файлы принято делать скрытыми даже если никаких требований к их названиям нет.

Файл .pythonstartup (настройки интерпретатора языка программирования Python) выполняется потому, что имя этого файла задано в переменной окружения PYTHONSTARTUP. Мефодию пришлось дописать строку PYTHONSTARTUP="/home/methody/.pythonstartup"; export PYTHONSTARTUP в ~/.bash_profile и "C-i": complete в ~/.inputrc, чтобы достраивание заработало и в этом интерпретаторе. Ещё один файл, .pyhistory, используется в самом .pythonstartup:

methody@localhost:~ $ cat .pythonstartup 
 import atexit, os, readline, rlcompleter
 
 historyPath = os.path.expanduser("~/.pyhistory")
 
 def save_history(historyPath=historyPath):
    import readline
    readline.write_history_file(historyPath)
 
 if os.path.exists(historyPath):
    readline.read_history_file(historyPath)
 
 atexit.register(save_history)
 del os, atexit, readline, rlcompleter, save_history, historyPath

Стартовый файл интерпретатора Python

Подавляющее большинство конфигурационных файлов предназначено для того, чтобы их мог редактировать пользователь. Эти файлы часто имеют самодокументированный формат и/или сопровождаются руководством, нередко выделенным а отдельный от руководства по самой утилите документ.


(1) Эта команда определяет, файлы какого типа и в каком количестве содержатся в каталоге /etc.

(2) Обозначение "i18n" происходит от слова "internationalization", в котором 20 букв, т. е. "i", "n" и 18 букв между ними.

(3) Программисты имели в виду Хроноса, стихийное божество времени у древних греков. Уже сами греки часто называли так "владыку неба" титана Крона (отца богов-Кронидов и, среди прочих, Зевса, который впоследствии папу и других титанов заключил в Тартар, и стал владыкой неба сам). У древних римлян и Крон и Хронос почитались под именем Сатурна, божества неумолимого времени.

(4) То есть в таком порядке, в котором они были бы расставлены в словаре. Причём цифры предшествуют алфавитным знакам, а между собой сортируются по возрастанию, от 0 до 9. Отсюда "000anacron" -- такое имя обеспечит, чтобы этот сценарий был выполнен самым первым.

(5) Название этого демона пародирует cron с намёком на анахронизм, то есть несвоевременность выполнения заданий.