Настройка системы и окружения
В прошлый раз мы не просто так занимались регулярными выражениями и вообще обработкой текста как такового. Частично это уже упоминалось, а частично мы могли увидеть это на практике: большая часть информации, которая нам доступна из операционной системы, приходит сразу в двух видах: можно добыть эту информацию из системных вызовов - написать программу, которая пользуется функциями ядра, а можно добыть информацию в текстовом виде, чтобы прочитать её как люди, которым интересно эту информацию читать. Это проявляется во многих местах, в первую очередь мы это видели, когда осваивали интерфейс командной строки, во вторых мы мельком заглянули в файловые системы, которые называются sys и proc, еще dev. Пресловутый proc - это представленная в виде дерева каталогов информация о запущенных процессах. Идея в том, чтобы как можно больше информации и данных, которые мы собираемся использовать в виде текстовом файле, как это делается в proc или в sys.
$ cat /sys/class/power_supply/C1B4 - дерево процессов, касающихся батарейки, которая подключена к этому ноутбуку.
Но, на самом деле, это принцип более глобальный в том смысле, что когда мы организуем операционную систему по юниксовому принципу, мы стремимся как можно больше значащей информации представлять в виде текстовых файлов. Останется только аккуратно вот эту вот текстовую систему обписать разными утилитами, которые решают подзадачи, связанные с выведением системы в штатное состояние, запустит в фоне несколько компонент операционной системы, и получится полноценная операционная система. 40 лет назад она была бы вообще революционной и по сейчас большая часть этой идеологии выполняется. Таким образом, у нас этот прием «все есть файл и каждый файл должен быть текстовым» опирается на необходимость человеку этот файл прочитать. Тут возникает такой формат human readable - пригодный для чтения человеком.
Тут даже интереснее: если у нас по какой-то причине нечто не представляет собой текстового файла, у нас в операционной системе довольно много инструментов, способных превратить поток файлов в текстовый поток или в текстовый файл. Ну какой-нибудь типичный пример - это управление сетью: у нас есть внешнее устройство, которое отвечает за сеть, есть их настройки, эти настройки явно где-то лежат, наверняка есть какой-нибудь dev/ethernet, но, честно говоря, я их не знаю, даже не буду искать. Более традиционным является запуск команды, который выведет всю информацию в текстовом виде. Это данные интерфейсного уровня:
$ ls dev
а вот это вот уровень сетевой
$ ip add
Утилита ip еще и управляющая. С помощью этой утилиты можно менять конфигурацию системы и настраивать сетевые интерфейсы. Я, может быть, покажу, если успею как это связано с текстовостью всего.
Более того, в большинстве случаев вот такая выдача текстовая она стандартизована, чтобы ее можно было бы обработать с помощью инструментов обработки текстов, о которых мы говорили в прошлый раз: grep или sed.
$ ip add | sed -En ‘/^[0-9]+:/p’
- я написал регулярное выражение, запускал в режиме -E и -n, это значит выводить на стандартный вывод те строчки, которые соответствуют регулярным выражениям.
Теперь мы можем продолжить дальше и написать что-то в виде
$ ip add | sed -En ‘/^[0-9]+:/s/.*,(.*_UP)>.*/\1/p’
Обратите внимание, я пишу регулярные выражения уже почти не задумываясь, а вот читать их потом уже неудобно, не имея практики.
$ ip add | sed -En ‘/^[0-9]+:/s/.*,(.*_UP)>.*/\1/p \2.p’
- это был пример, который пришел просто в голову.
Ключ -i - inplace редактирование: прямо имеющийся файл обрабатываем и меняем в нем какие-то значения. Если мы хотим отредактировать содержимое файла о - мы не можем написать вот так:
$ sed ’s/1/0’ < o > o
В современном седе появился ключик -inplace, который позволяет отредактировать файл на месте:
$ sed -i ’s/1/0’ < o > o $cat o
Таким образом можем свести обработку текстовой информации к анализу человеком и обработки ее всякими штуками типа sed -i. А можно даже генератор: анализируя одни данные можно генерировать другие.
Human writable: идея в том, что такой текстовый файл должен быть еще и разумно организован, чтобы его мог человек написать. То есть вы садитесь, у вас есть документация, текстовый редактор и вы, согласно документации, пишите настроечный файл.
Где все эти текстовые файлы хранятся?
А давайте вспомним, что у нас есть ключик -а, который показывает все файлы, и когда мы этот ключик используем в нашей команде, выясняется, что у нас в домашнем каталоге куча файлов, которые начинаются на точку. Файл который содержит все ключи хостов, на которые мы заходили - текстовый. И чтобы мне его поправить, нужно удалить устаревший ключ. То есть всего лишь отредактировать его в текстовом редакторе.
Даже у суперпольователя в домашнем каталоге - у него ничего нет. А вот как бы не так: даже у суперпользователя в домашнем каталоге есть некоторое количество конфигурационных файлов, начинающихся на точку, которые традиционно считаются скрытыми. Они ничем не отличаются от других файлов, просто в шелле сказано, что файлы, которые начинаются на точку, не учитываются.
$ echo * tmp $ echo .*
- указали точку явно и все файлы вывелись.
У обычного пользователя таких файлов на точку будет гораздо больше, потому что в таком виде складываются настроечные файлы от многих приложений, которые просятся на запуск. Начиная от самых банальных файлов. Вот у вас есть программа и ей нужно где-то хранить конфиги - они хранятся в .(имя программы) Например, есть файл lpoptions, который хранит настройки подсистемы печати. вот . из таких вот понятных конфигурационных файлов: ну например файл, который называется inputrc - это настройки редактирования командной строки во всех программах, которые используют библиотеку GNU Readline: баш, питон работают используя ридлайн и еще куча программ, которые позволяют редактировать ввод. Настройки клавиш управляются inputrc. Чтобы изменить настройки клавиш, нужно его лишь доредактировать. От редактирования этого файла меняется поведение всех программ, которые используют функцию complition в GNU Readline.
Настроечные файлы
На самом деле, этих настроечных файлов очень много и синтаксис у них предельно разнообразный, потому что каждый автор программы волен настройки своей программы хранить в каком угодно виде. Есть несколько более менее стандартизованных видов настроечных файлов. В какой-то момент довольно быстро оказалось, что вот этих файлов, начинающихся на точку, их по несколько штук для каждой программы. Ну потому, что для того же редактора vim есть vimrc, viminfo, а есть еще куча разных файлов, которые лежали бы прямо тут, если бы не следующий шаг косвенности: а давайте сделаем целый каталог с настройками? .vim - файл с настройками, где хранятся все настройки. В vimrc есть самые нужные настройки, например, чтение дополнительных файлов, которые уже лежат в .vim
И таких подкаталогов с точкой в начале тут тоже достаточо много.
$ ls .elinks/
Что там хранится: настройки, а также всякие вспомогательные файлы, тоже, кстати сказать, текстовые. Кстати сказать, это наводит нас на мысль: является ли файл настройки составным компонентом приложения или нет? Наверное, настройки программы должны состоять из 2 частей. Вторая часть - то, что пользователь сам для себя настроил, и первая часть - настройки по умолчанию, и они должны быть как-то зашиты в приложение. Запомним это утверждение, мы к нему еще вернемся сегодня. А пока я вам покажу еще один вид настроечных файлов, выглядит он достаточно просто:
$ cat .dircolors
Если тип терминала вот такой, то можно раскрашивать вывод и в зависимости от типов файлов и их расширений, они раскрашивались в такие цвета. Вот и раскрылась тайна, почему в каталоге ls некоторые файлы зелененькие, а некоторые синенькие.
Утилита grep тоже может раскрашивать, но в отличии от ls она не читает конфигурационный файл. Потому что если каждый раз открывать grep и читать при этом какой-то файл - это будет довольно долго.
Таким образом можем настраивать разные наши утилиты, которые используют переменные окружения, используя переменные окружения.
Поскольку любые команды в линуксе выполняются шеллом, все, что мы принимаем за настроечный файл - это шелл скрипт. Оно будет проверять и в зависимости от результатов применять переменные окружения.
В .bashrc и .bash_profile можно вставить такие настройки и они будут работать.
Тем не менее, если программа сама - шелл скрипт или запускалка приложения - шелл скрипт. Вполне логично, что шелл скрипт всасывает настроечный файл, который является шелл скриптом и дальше запускается.
Обратите внимание, что, когда я собирался редактировать все эти настроечные файлы, я не глядя запускал редактор vim. Это не только потому что vim - самый лучший в мире редактор и все такое, а еще и потому что vim действительно очень хороший редактор, когда вам надо очень быстро произвести модификацию структурированных текстов. Регулярные выражения и последовательности команд в виме превращены в команды. Редактор, вроде вима или седа очень удобен именно потому что представляет собой что-то вроде микропрограммирования. Даже когда мы нажимаем клавишу перехода в виме мы вводим очень хорошую программу редактирования текста. Никто не мешает вам пользоваться другими текст редакторами, поиск с заменой есть почти везде. Но в комплекте все вместе все возможности того же вима сильно превышают то, что может быть нам предложено другими текстовыми редакторами, за исключением емакса.
Обычному пользователю можно знать, что существует такая штука, как mcedit - он же синенький - обычный текстовый редактор, который поддерживает некоторые команды. Умеет раскрашивать подсветку синтаксиса, форматировать, может в регулярные выражения, он неудобнее в том смысле, что все эти команды существенно дольше вводить. Если вы админ и вам быстро-быстро нужно что-то переставить, нажать и поехали дальше - вим в этом плане гораздо удобнее.
Чтобы закончить эту тему: что делать, когда у нас получается объем вот этих настроечных данных уже не хьюман райтабл - то есть, его уже невозможно написать руками, можно только прочитать. Вопрос интересный. Только что мы видели 2 способа выхода из такого положения:
- Организовать иерархический каталог таких файлов;
- Вместо этого заводить специальный файл, неважно в каком виде, где хранятся данные, и специальную утилиту, которая из этого файла будет извлекать данные. То есть, фактически, сделать базу данных и интерфейс к ней.
База данных и интерфейс сильно напоминает пресловутый реестр в виндовсе, но, как было замечено Арсением, в реестре очень много записей вообще нечитаемого вида, а мы все-таки хуман ридабл формат стараемся сохранять.
Смотрите:
$ dconf list /org/gtk/
Нет никакого корня org/gtk, это один большой файл конфигураций gtk, но у них есть интерфейс, который может представлять настройки в текстовом режиме.
Все это лежит в недрах вот этого файла - .config/dconf/user
На самом деле, даже этот просто банальный переход хранения файлов все равно приводит к разнообразию точек-файлов в домашнем каталоге, поэтому люди договорились, чтобы конфиги лежали в каталоге .конфиг. И их там опять МНОГО.
Короче говоря, множество настроечных файлов лежат в каталоге конфиг и дальше подконфиги и дальше и дальше и дальше.
Раз уж пошла такая пьянка, если некий фридесктопный стандарт предписан хранение настроек в папке .конфиг, то этот же стандарт дает советы, где хранить не настроечные файлы, а, скажем, временные файлы приложений: кэши, сгенерированные вспомагательные файлы и так дальше. Вот мы видели, что программа elinks хранит у себя в каталоге .elinks какие-то букмарки, куки, но они не являются настройками.
А если у вас есть какие-то генераты, которыми вы не дорожите - они хранятся в каталоге .cache, вот они не обязаны быть текстовыми.
Давайте теперь совсем чуть-чуть, потому что это все таки больше относится к архитектуре системы, поговорим о том, как наша система вообще превращается в операционную систему. Вот у нас загрузилось ядро. Что происходит дальше? Запускается процесс init, который отец всех процессов, и по определенным правилам другие процессы запускает. По каким правилам и как выглядит запуск процессов?
Написали процесс init. В нем на языке си напишем, какие процессы запускать. Для тех, кто немножко разбирается в системном администрировании, это звучит совершенно дико - сценарий на шелле написан. Потому что довольно быстро у создателей юникс появилась идея, что, раз язык управления - шелл, и раз все админские задачи выполняем, запуская шелл, то старт системы должен быть на шелле. Что такое старт системы - запуск некоторых программ, которые должны что-то настроить, и запуск каких-то программ (демонов), которые должны обеспечивать разные системные службы. И, в принципе, операционную систему можно сконструировать таким образом, чтобы было у нее много бинарников и много вокруг них обмазано кода на шелле который бы бинарники эти запускал.
Достоинства шелла:
Оно ничего дополнительного, кроме того, что мы знаем не задает. У нас есть системные службы - почитаем мануал и будем знать, как ими пользоваться. И после этого будем знать, как устроена операционная система, потому что все, что нам останется - определить запуск этих служб и их порядок. Так что древний юникс выглядел так: там был каталог, в нем скрипт. rc, который запускал нужные программы.
Поскольку шелл предназначен для написания системных сценариев и он очень удобно заточен под запуск программ, анализ их результатов, передачу между ними текстовых файлов - второе достоинство такой организации системы - можно прочесть шелл сценарий и просто понять, потому что программа на си будет выглядеть хуже.
Третье достоинство - вам придется все равно что-то подобное делать для вашей операционной системы, потому что все программы на шелле имеют похожий процесс управления. Какие подсовывать переменные окружения, какие передавать параметры - все равно запуск конкретной системной службы требует дополнительных действий и эти действия логично написать на шелле. Таким образом получаем очень хорошую схему.
Давайте добьем тему настроечных файлов. Дело в чем: в самом начале сказали, что у службы/прогрмаммы/приложения должно быть два настроечных файла - общий для всех, неизменяемый, и личный, который пользователь сам себе настроил.
Общие лежат в каталоге etc.
Идея в том, чтобы разделить настроечные файлы на два типа и без необходимости не смешивать одно с другим. Потому что он большой! Такая же точно ситуация с подкаталогами.
Что еще содержится в каталоге etc, помимо того то там лежат настроечные файлы всего подряд?
- профиль системы - описание тех действий и вещей, которые нужно проделать, чтобы система вышла в штатный режим
(возможно это относится к недостаткам) информация, необходимая, чтобы перенастраивать файл по ходу. Воткнулось устройство - исчезло, появилась сеть - исчезла. Действия которые делать после каких-то событий должны где-то лежать. Самое правильное место - каталог etc.
- компоненты системы.
В чем недостатки хранения всего и воздействия на все со стороны сценариев, положенных в etc? - об этом поговорим в самом конце.
И тем не менее, есть одна проблема, которую никто раньше не решал, а решать ее стоит. Давайте посмотрим, кому на запись доступен каталог etc - это root.
А есть здесь один файл, который мы можем редактировать? Ну разумеется нет.
Человек без определенных прав ничего испортить себе не может. Разве что может удалить все свои файлы. А испортить саму операционную систему невозможно просто по определению хотя бы потому, что доступ к эти файлам ему запрещен. Таким образом, на самом деле, любая модификация всей это ерунды возможна с правами только суперпользователя.
Возникает, соответственно, вопрос: а когда мы запускаем программы, они от чьего лица запускаются? Ну, видимо, от лица суперпользователя. А хорошо ли это? Зачем нам подсистему печати запускать от суперпользователя? Не лучше ли запускать все эти службы со спецправами спецпользователя? Служба почты запускается от лица пользователя post, подсистема печати - от лица lp пользователя. Ну и так далее. А как это контролировать? Да очень просто: у нас же есть команда su, значит сценарий, который запускается от лица программы, запускается как su - от лица такого-то пользователя запусти такую-то программу.
Еще один недостаток использования шелл сценариев, чтобы разворачивать всю систему - скорость. Система запускается один раз, но запускается она 3 минуты.
Если штука - оперативная перенастройка системы, например, реакция на флешку, открытие крышки ноутбука, то реакция на такие события должна быть очень быстрая с точки зрения пользователя. Доли секунды. Не факт, что все сценарии будут работать так быстро.
Запуск сценария - это запуск шелла, запуск нескольких утилит, внесение каких-нибудь коррективов... Короче говоря, можно подозревать, что наличие большого количества шелл сценариев постоянно запускаемых приводит к тому, что система работает недостаточно быстро.
Последняя тема на сегодня: давайте посмотрим, как до определенных пор развивалась и где застопорилась вот эта последовательность превращения ядра и утилит в работающую ос.
Сначала был скрипт /etc/rc. Один. Совершенно разумная идея - сначала запустили ядро, потом такую-то службу, другую службу, потом, разумеется, как настоящий ленивый администратор вы написали скрипт и сказали: "дорогой init, запусти этот скрипт."
Какой недостаток? Ну, он все больше и больше. В конце концов, возникла идея - распилить на две части - сделать его параметризуемым и выполнять из него еще один скрипт rc.conf, который будет иметь вид "переменная-значение, переменная-значение", … . Написан он тоже, разумеется, на шелле. Долго ли коротко ли, шутка эта росла и выросла до тех пор, когда это стал такой огроменный шелл скрипт, там получилось огромное количество запусков служб и, собственно, rc.conf тоже получился огромный.
Что делать дальше? Вводить понятие умолчание.
Следующий акт усложнения схемы состоял в том, что появилось 2 настроечных файла: тот, который админ правит и дефолт файл, где написано все остальное. Тут произошла флуктуация, которую надо упомянуть, но очень аккуратно, а именно: в старом юниксе было такое понятие "уровни выполнения": последовательная загрузка системы могла идти несколькими путями. Это были уровни выполнения, которые друг друга сменяли и для каждого из которых надо было писать шелл сценарий.
Следующий шаг - очень важный и остроумный шаг, который надолго сделал эту схему запуска системы запуском шелл скриптов очень эффективным: запуск с помощью схемы .d.
Заменяем каталогом, где лежит много файлов или сценариев.
В чем смысл? Смысл в том, что в etc хранится файл bashrc и bashrc.d , который засасывается из файла bashrc.
Чем эта схема хороша? Тремя вещами сразу:
- Вы пилите один большой монолит на много существенно мелких кусочков, каждый из которых выполняет собственное предназначение.
Вообще говоря, вот этим вот .d файлы не обязаны принадлежать одному и тому же пакету. Вы ставите какой-нибудь криптопро, у него есть какие-нибудь настроечные файлы, и появляется по схеме .d маленький скриптик, который дополнительно запускается при запуске bashrc, добавляя в path вот эту вот ерунду. Когда вы его удаляете - удаляется только скрипт.
- Следует из 2. Пусть мы сохранили монолитную схему. Добавление и удаление куска превращала в автоматическое редактирование шелл скрипта. Добавлять нужно было автоматически редактируемую шелл-программу, чтобы она после этого продолжала работать, поэтому, как только мы имеем дело с линуксом, который состоит из пакетов, который не может трогать чужие файлы, мы сталкиваемся с такой вот схемой.
В общем, это очень круто. Но это еще не все!
Потому что когда вы планируете запуск системы, вам бы, может быть, хотелось, чтобы эти стартовые сценарии, написанные на шелле, запускались в определенном порядке. Таким образом, этот стартовый сценарий должен был быть не просто каким-то шелл скриптом, а еще иметь строгое место в области запуска.
В добавок к этому, вы еще, может быть, захотите останавливать эти службы, причем не в том порядке, в котором вы их запускали.
К этому добавляется задача написать сценарий, который останавливает эту службу. И вот, в каталоге /etc/init.d стали складывать вот такие сценарии, которые одновременно и стартный и стопный: если вызовем этот сценарий с параметром старт - служба запустится, с параметром стоп - служба остановится. Интерфейс у старт-стопных сценариев одинаковый, содержимое - разное. Все это - скрипты на шелле.
Для того, чтобы обеспечить порядок запуска и порядок останова, применяется очень простая штука:
$ vim /etc/rc
- уровни пользования тут появляются
$ ls /etc/rc5.d
голубенькое
Потому что это символьные ссылки. В этом каталоге лежат те же файлы, но у них есть префикс К и С: С - стартные сценарии, эти сценарии выполняются в лексикографическом порядке, диктуемым этим префиксом, а буква К - kill - стопные сценарии. Это значит лишь запуск сценария с параметром стоп или старт. Это всего лишь символьные ссылки на скрипты.
Первое глубокомысленное соображение: все сценарии запускаются подряд. Когда их много - это долго. Да, мы договорились, в каком порядке запускать, но, вообще говоря, можно договорится и о других вещах, например о том, что они частично независимы друг от друга. Мы можем сделать следующее: собраться с духом и договориться о том, не как они нумеруются, а какой сценарий от какого зависит, и поставить такие поля прямо в шелл скрипт, сказать - депендс он такие-то и реквайерс такие-то, из значащих комментариев выстраивать дерево запуска и далее запускать.
Мы можем и дальше развивать эту идею, но - последняя глубокомысленная мысль на сегодня- даже если мы пропишем эти зависимости и получится аккуратный способ запускать эти штуки в параллели, у нас останется проблема запуска этих штук в контексте.
Не дай бог эта штука будет происходить динамически.
У нас все еще не решена по-человечески проблема, с правами какого пользователя запускать какую службу и, вообще говоря, совершенно непонятно, зачем в 70е придумано понятие демон. У нас есть терминал, группа процессов, сразу ей можно посылать сигналы, но если это часть компонентов операционной системы, надо от всего нашу службу отвязать. То есть выполнить некую демонизацию, такой дабл форк. Внимание, вопрос: если их все равно так много, зачем в каждый из них вот это имплементить?
Задача развертывания и функционирования операционной системы схемой со скриптами .d решается на 5, если это статическое развертывание, почти на 5, если там возникает необходимость запуска и перезапуска наших слов, и, в общем, достаточно плохо решается, если у нас идет быстрая разработка новых служб. Если они не предназначены, чтобы вычитывать код на безопасность, если у нас идет быстрый девелопмент, то получившееся окружение таки способом запускается не очень хорошо. Поэтому нужен принципиально другой подход к этому делу, о котором поговорим в следующий раз.