Выбрать главу

22.3.7. Закрытие файловых дескрипторов

В системах Linux и Unix файловые дескрипторы, как правило, наследуются через системные вызовы exec() (и всегда наследуются через fork() и vfork()). В большинстве случаев такое поведение нежелательно, поскольку только разделяться должны только stdin, stdout и stderr. Программы, запускаемые привилегированным процессом, не должны иметь доступа к файлам через унаследованный файловый дескриптор. Поэтому очень важно, чтобы программы внимательно закрывали все файловые дескрипторы, к которым не должна получить доступ новая программа. Это может стать проблемой, если ваша программа вызывает библиотечные функции, которые открывают файлы и не закрывают их. Одним из методов закрытия файловых дескрипторов является закрытие всех файловых дескрипторов вслепую из дескриптора номер 3 (тот, который следует сразу за stderr) произвольным большим значением (скажем, 100 или 1024)[165]. В большинстве программ это обеспечивает закрытие всех надлежащих файловых дескрипторов[166].

Наиболее удобным способом является установка флага закрытия после выполнения для каждого файла, который программа оставляет открытым на длительный период времени (включая сокеты и файловые устройства), что предотвращает получение доступа к данным файлам новыми запускаемыми программами. Описание флага закрытия после выполнения можно найти в главе 11.

22.4. Запуск в качестве демона

При разработке программ, создаваемых для работы в качестве системных демонов, нужно очень внимательно проводить их становление как демонов для правильного определения всех деталей. Ниже приведен перечень тех обстоятельств, на которые необходимо обратить внимание.

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

2. Текущий каталог должен быть изменен на какой-либо подходящий. Это может быть корневой каталог, но никогда не может быть тот каталог, из которого была запущена программа. Если демон этого не сделает, то, возможно, он будет работать соответствующим образом, но это не позволяет удалить тот каталог, из которого он был активизирован, поскольку он остается текущим каталогом программы. Если это возможно, то неплохо применить chroot() на какой-то каталог. Причины обсуждались ранее в этой главе.

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

4. Затем программа должна вызвать fork(), а родительский процесс должен вызвать exit(), позволяя программе, запустившей демон (чаще всего командному процессору), продолжить работу.

5. Дочерний процесс, продолжающий работу, должен закрыть stdin, stdout и stderr, поскольку он не будет больше использовать терминал. Вместо повторного применения файловых дескрипторов 0, 1 и 2 лучше открывать эти файлы как /dev/null. Это гарантирует, что ни одна библиотечная функция, передающая отчеты о состоянии ошибок в stdout или stderr, не запишет эти ошибки в другие файлы, открытые демоном. При этом демон сможет запускать внешние программы, не беспокоясь об их выходных данных.

6. Для полного разъединения с терминалом, из которого был запущен демон, он должен вызвать setsid(), чтобы разместить его в собственной группе процесса. Это предотвращает получение сигналов при закрытии терминала, а также сигналов управления заданиями.

Библиотека С предлагает функцию daemon(), которая обрабатывает некоторые из перечисленных задач.

int daemon(int nochdir, in tnoclose);

Данная функция сразу осуществляет ветвление, и если оно прошло успешно, родительский процесс вызывает _exit() с кодом завершения 0. Затем дочерний процесс переходит в корневой каталог, если nochdir не является нулем, и перенаправляет stdin, stdout и stderr в /dev/null, если noclose не равен нулю. Перед возвратом в дочерний процесс она также вызывает setsid(). При этом унаследованные файловые дескрипторы все равно могут оставаться открытыми, поэтому в программах, использующих daemon(), необходимо следить за ними. Если возможно, в программе также нужно использовать chroot().

Часть IV

Библиотеки для разработки

Глава 23

Сопоставление строк

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

23.1. Универсализация произвольных строк

В главе 14 мы говорили о том, как с помощью функции glob() производится универсализация имен файлов, однако пользователи, знакомые с возможностями универсализации, нередко пытаются применить их и к другим разновидностям строк. Функция fnmatch() позволяет применять правила универсализации в отношении произвольных строк:

#include <fnmatch.h>

int fnmatch(const char * pattern, const char * string, int flags);

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

* Соответствует любой строке, включая пустую.
? Соответствует любому одиночному символу.
[ Начинает список символов для сопоставления или, если следующим символом является ^, то список символов для несовпадения. Весь список может совпадать, или не совпадать с одним символом. Список заканчивается знаком ].
\ Следующий символ будет интерпретироваться как литерал, а не как специальный символ.

На результаты универсализации влияет аргумент flags, и здесь он будет полезен, прежде всего, для универсализации имен файлов. Если вы не будете осуществлять универсализацию имен файлов, то вам, скорее всего, нужно будет присвоить аргументу flags значение 0.

FNM_NOESCAPE Обработка символа \ как обычного, а не специального символа.
FNM_PATHNAME Символы / в строке string не сопоставляются с последовательностью *, ?, или даже [/] в шаблоне pattern; сопоставление производится только с литералом, а не специальным символом /.
FNM_NOESCAPE Первый символ . в шаблоне pattern соответствует символу . в строке string только в том случае, если он является первым символом в строке string или если задано значение FNM_PATHNAME, а символ . в string непосредственно следует за символом \.
вернуться

165

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

вернуться

166

Еще одним способом закрытия всех файлов, открытых программой, является прохождение через каталог файловой системы процесса /proc, в котором перечислены все открытые файлы, и закрытие каждого из них. Каталог /proc/PID/fd (где PID —это pid текущего процесса) содержит символическую ссылку для каждого файлового дескриптора, открытого процессом. Имя каждой символической ссылки представляет собой файловый дескриптор, которому она соответствует. Считывая содержимое каталога, программа легко может закрыть все файловые дескрипторы, которые больше не нужны.