учебники, программирование, основы, введение в,

 

Технологические интерфейсы

Функции и утилиты для работы с системным журналом
Под системным журналом в стандарте POSIX-2001 понимается некое средство хранения и/или отображения данных, предназначенных для изучения системным администратором. Средство это, разумеется, зависит от реализации. Это может быть обычный локальный файл (например, /var/log/messages), список рассылки, удаленный сетевой ресурс и т.д.
Для работы с системным журналом стандарт предлагает функции записи  сообщений (syslog()), установки фильтра (маски журналируемых сообщений, setlogmask()) и других параметров журналирования (openlog()) и, наконец, завершения работы с системным журналом (closelog()) (см. Средства чтения системного журнала, как и все, что связано с администрированием, в стандарт POSIX-2001 не входят.
#include <syslog.h>

void syslog (int priority,
const char *message, ... /* аргументы */);

int setlogmask (int maskpri);
void openlog (const char *ident,
int logopt, int facility);

void closelog (void);
Сообщение, которое помещает в системный журнал функция syslog(), включает заголовок и тело.
В заголовок входит по крайней мере временной штамп и идентифицирующая цепочка символов.
Тело  сообщения формируется из аргумента  message, играющего роль формата, и следующих за ним необязательных аргументов аналогично тому, как если бы использовалась функция printf(), только допускается один дополнительный спецификатор преобразования – %m, не требующий аргумента и осуществляющий вывод текущего значения переменной errno.
Значение аргумента  priority формируется как побитное ИЛИ флагов двух видов, задающих, соответственно, уровень серьезности и источник сообщения.
Уровень серьезности может принимать следующие значения.
LOG_EMERG
Катастрофическая ситуация.
LOG_ALERT
Ситуация, требующая немедленного вмешательства (например, повреждение системной базы данных).
LOG_CRIT
Опасная ситуация (например, ошибки в работе аппаратуры).
LOG_ERR
Сообщение об ошибке.
LOG_WARNING
Предупреждающее сообщение.
LOG_NOTICE
Ситуация, не являющаяся ошибочной, но, возможно, требующая специальных действий.
LOG_INFO
Информационное сообщение.
LOG_DEBUG
Отладочное сообщение.
Из источников сообщений стандартизован только один (естественно, являющийся подразумеваемым) – пользовательский процесс (флаг LOG_USER). Зарезервированы флаги для системных источников, имена которых говорят сами за себя (LOG_KERN, LOG_MAIL, LOG_NEWS, LOG_UUCP, LOG_DAEMON, LOG_AUTH, LOG_CRON, LOG_LPR) и для абстрактных локальных сущностей (LOG_LOCAL0 – LOG_LOCAL7).
Функция setlogmask() в качестве результата возвращает предыдущую и устанавливает новую маску журналируемых сообщений вызывающего процесса. В системный журнал будут помещаться только те сообщения, уровень серьезности которых присутствует в маске, заданной аргументом  maskpri. Для формирования этого аргумента по одному уровню серьезности стандартом предусмотрен макрос LOG_MASK (pri). Для задания маски, включающей несколько уровней, нужно взять побитное ИЛИ подобных выражений.
Если значение maskpri равно нулю, текущая маска остается неизменной. Подразумевая маска является «полной», она специфицирует журналирование всех событий.
Функция openlog() устанавливает значения атрибутов журналирования вызывающего процесса, влияющие на последующие обращения к syslog(). Аргумент  ident задает идентифицирующую цепочку, фигурирующую в заголовках всех сообщений. Аргумент  logopt специфицирует опции журналирования. Он формируется как побитное ИЛИ следующих флагов.
LOG_PID
Записывать вместе с сообщением  идентификатор процесса.
LOG_CONS
Выдавать сообщение на системную консоль, если его не удается поместить в журнал.
LOG_NDELAY
Немедленно открыть системный журнал (обычно открытие откладывается до записи первого сообщения).
LOG_ODELAY
Отложить открытие  системного журнала до первого вызова syslog() (выбор между немедленным или отложенным открытием способен повлиять на распределение файловых дескрипторов).
LOG_NOWAIT
Не ждать завершения процессов, которые могли быть порождены в ходе журналирования  сообщения. Эту опцию следует использовать в процессах, которые уведомляются о завершении потомков сигналом SIGCHLD.
Аргумент  facility устанавливает подразумеваемое значение для источника тех сообщений, где он (источник) не указан явным образом.
Функции openlog() и syslog() могут открывать (расходовать) файловые дескрипторы. Функция closelog() закроет их.
Вообще говоря, вызывать openlog() до syslog() и setlogmask() не обязательно.
К рассматриваемой прикладной области можно отнести служебную программу logger:
logger цепочка_символов ...
которая неким неспецифицированным образом сохраняет сообщение, содержащее заданные аргументыцепочки символов. Предполагается, что со временем это сообщение прочитает системный администратор.
Подобная возможность полезна для выдачи диагностических сообщений  неинтерактивными приложениями. Например, программа, запущенная в пакетном режиме, может уведомить системного администратора об отсутствии какого-либо файла:
logger $LOGNAME $(date) : не удалось прочитать файл report.txt
Рассмотрим пример применения функций для работы с системным журналом
Результатом работы этой программы может быть строка, показанная на

Привлечь внимание системного администратора к возникшим проблемам можно не только помещая сообщения в системный журнал, но и выдавая их в стандартный протокол и/или на системную консоль. Для этого служит функция fmtmsg() (#include <fmtmsg.h>
int fmtmsg (long classification,
const char *label,
int severity, const char *text,
const char *action,
const char *tag);
Листинг 9.4. Описание функции fmtmsg().Функция fmtmsg() конструирует отформатированное сообщение, включая в него все аргументы, кроме первого.
Аргумент  classification определяет источник и способ отображения сообщения. Он формируется как сумма констант, по одной из каждого класса. Классификация верхнего уровня определяет источник проблемы. В этот класс входят константы MM_HARD (аппаратура), MM_SOFT (программное обеспечение), MM_FIRM (программно-аппаратные средства). Сообщения, порожденные программным обеспечением, могут дополнительно классифицироваться константами MM_APPL (приложение), MM_UTIL (служебная программа), MM_OPSYS (операционная система). Проблемы классифицируются также по признаку нейтрализуемости – соответственно, MM_RECOVER и MM_NRECOV.
Если сообщение предполагается направить в стандартный протокол, к значению аргумента  classification следует прибавить константу MM_PRINT; вывод на системную консоль задает константа MM_CONSOLE. Возможно одновременное указание обеих констант.
Константа MM_NULLMC означает отсутствие классификационного компонента (естественно, ее значение равно нулю).
Аргумент  label специфицирует первый из пяти компонентов выдаваемого сообщения. Он, как и classification, определяет источник сообщения, но делает это по-своему. Указуемая цепочка символов должна состоять из двух полей, разделенных двоеточием. Первое поле состоит не более чем из десяти байт, второе – из четырнадцати.
Аргумент  severity характеризует серьезность проблемы. Стандартом POSIX-2001 предусмотрены следующие уровни серьезности.
MM_HALT
В приложении встретилась серьезная ошибка, его работа остановлена. В выводимую цепочку вставляется текст «HALT».
MM_ERROR
В работе приложения обнаружена ошибка. В выводимую цепочку вставляется текст «ERROR».
MM_WARNING
При работе приложения возникла необычная ситуация, возможно, являющаяся ошибочной и требующая внимания. В выводимую цепочку вставляется текст «WARNING».
MM_INFO
Информация о ситуации, не являющейся ошибочной. В выводимую цепочку вставляется текст «INFO».
MM_NOSEV
Данная константа обозначает отсутствие у сообщения  уровня серьезности.
Аргумент  text в свободной форме описывает ситуацию, приведшую к генерации сообщения.
Аргумент  action также в свободной форме описывает первый шаг по нейтрализации ошибки. Перед цепочкой, на которую указывает action, в сообщение вставляется префикс «TO FIX:».
Аргумент  tag служит ссылкой на документацию по выявленной проблеме.
На работу функции fmtmsg() влияет переменная окружения  MSGVERB, которая определяет, какие из пяти возможных компонентов сообщения будут выданы в стандартный протокол (на консоль всегда выдается полное сообщение). Значение этой переменной в общем случае состоит из пяти ключевых слов – label, severity, text, action, tag – разделенных двоеточиями. Если какие-то ключевые слова отсутствуют, соответствующие компоненты сообщения в стандартный протокол выданы не будут. Если переменная MSGVERB отсутствует в окружении, имеет пустое или некорректное значение, сообщение выдается целиком.
Возможные результаты функции fmtmsg() устроены необычным образом. Константа MM_OK обозначает полный успех, MM_NOTOK – полную неудачу, MM_NOMSG – невозможность выдать сообщение в стандартный протокол, MM_NOCON – невозможность вывода на консоль.
Приведем не очень серьезный пример применения функции fmtmsg()).
#include <stdio.h>
#include <fmtmsg.h>

int main (void) {
if (fmtmsg (MM_SOFT + MM_OPSYS + MM_RECOVER
+ MM_PRINT + MM_CONSOLE, "POSIX:fmtmsg",
MM_INFO, "Отсутствует функция fmtmsg()",
"Установите функцию fmtmsg()
или не пользуйтесь ею\n",
"См. functions/fmtmsg.html") != MM_OK) {
perror ("FMTMSG");
return (1);
}

    return 0;
}
Листинг 9.5. Пример использования функции fmtmsg().В результате выполнения приведенной программы в стандартный протокол и на системную консоль будет выдано следующее сообщение (см..
POSIX:fmtmsg: INFO: Отсутствует функция
fmtmsg() TO FIX: Установите функцию fmtmsg()
или не пользуйтесь ею
См. functions/fmtmsg.html
Листинг 9.6. Возможные результаты выполнения программы, использующей функцию fmtmsg(). (Читателю предлагается самостоятельно поэкспериментировать с этой программой, варьируя значение переменной окружения  MSGVERB.

Функции для работы с базой данных учетной информации о пользователях
Мы продолжаем рассматривать функции, находящиеся на стыке пользовательских и административных средств.
Стандартом POSIX-2001 предусмотрен набор функций для работы с базой данных учетной информации о пользователях. Эти функции реализуют последовательный просмотр учетных записей (getutxent()), поиск в базе (getutxid(), getutxline()), модификацию или добавление записей (pututxline()), возврат к началу (setutxent()) и завершение работы с базой (endutxent())).
#include <utmpx.h>

struct utmpx *getutxent (void);

struct utmpx *getutxid (
const struct utmpx *id);

struct utmpx *getutxline (
const struct utmpx *line);

struct utmpx *pututxline (
const struct utmpx *utmpx);

void setutxent (void);

void endutxent (void);
Листинг 9.7. Описание функций для работы с базой данных учетной информации о пользователях. Центральную роль для описываемого набора функций играет структура типа utmpx, которая, согласно стандарту, должна содержать по крайней мере следующие поля.
char     ut_user [];    
/* Входное имя пользователя         */
char     ut_id [];      
/* Неспецифицированный              */
/* инициализационный идентификатор  */   
/* процесса (например, первое поле  */
/* в строке файла inittab)          */
char     ut_line [];    
/* Имя устройства                   */
pid_t     ut_pid;        
/* Идентификатор процесса           */
short     ut_type;       
/* Тип записи                       */
struct timeval ut_tv;  
/* Время создания записи            */
В зависимости от типа записи (элемент ut_type) определяется подмножество полей, содержащих осмысленные значения. Для пустых записей (тип  EMPTY) таких полей нет вообще. Для типов  BOOT_TIME (идентифицирует время загрузки системы), OLD_TIME (время изменения показаний системных часов), NEW_TIME (показания системных часов после изменения) имеет смысл только время создания записи (элемент ut_tv). Записи  типа  USER_PROCESS идентифицируют пользовательские процессы и содержат полезную информацию в элементах ut_user (входное имя пользователя), ut_id, ut_line, ut_pid и ut_tv. Почти такое же подмножество полей имеет смысл для типа  LOGIN_PROCESS (по стандарту он идентифицирует лидера сеанса вошедшего в систему пользователя): ut_user (зависящее от реализации имя входного процесса), ut_id, ut_pid, ut_tv. Наконец, для типов  INIT_PROCESS (идентифицирует процесс, порожденный системным процессом init) и DEAD_PROCESS (по стандарту он идентифицирует лидера сеанса, завершившего выполнение) имеют смысл лишь элементы ut_id, ut_pid и ut_tv.
Разумеется, реальные размеры массивов, являющихся элементами структуры, можно узнать, применяя к ним на целевой платформе функцию sizeof().
Функция getutxent() читает очередную запись из базы данных учетной информации о пользователях. Если база данных еще не открыта, она открывается. При достижении конца базы данных выполнение функции завершается неудачей и возвращается пустой указатель. Нормальным результатом является указатель на копию прочитанной записи, размещенную в структуре типа utmpx.
Функция getutxid(), начиная с текущей позиции, разыскивает запись  базы данных, в которой поле ut_type соответствует значению id->ut_type. Если элемент id->ut_type равен BOOT_TIME, OLD_TIME, или NEW_TIME, то требуется точное равенство типов. Если же id->ut_type равняется INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS или DEAD_PROCESS, то функция getutxid() вернет указатель на копию первой записи, тип которой равен одному из четырех перечисленных, и поле ut_id соответствует значению id->ut_id.
Функция getutxline() аналогичным образом разыскивает запись, тип которой равен LOGIN_PROCESS или USER_PROCESS, а поле ut_line соответствует значению line->ut_line.
Доступ к записям  базы данных учетной информации о пользователях может быть подвержен зависящему от реализации контролю прав доступа. В частности, система может не выдавать сведений о некоторых или всех пользователях, отличных от вызывающего.
Функция pututxline() записывает указанную utmpx-структуру в базу данных (если вызывающий процесс обладает соответствующими привилегиями). При этом для поиска нужного места используется функция getutxid(), если обнаруживается, что текущая позиция не является подходящей. В случае неудачи поиска  запись добавляется в конец базы данных.
Отметим, что запись  базы данных, к которой было последнее обращение, может сохраняться реализацией в статической структуре (и указатель на нее возвращаться в качестве результата функций), поэтому доступ к нескольким записям требует копирования структур. Далее, при обращении к getutxid() или getutxline() реализация имеет право сначала проанализировать упомянутую статическую структуру и, если та окажется подходящей, не производить поиск, а вернуть ее же. Следовательно, если приложению требуется найти несколько подходящих записей, то после очередного успешного поиска и копирования структуры ее необходимо очистить.
В свою очередь, согласно стандарту, приложение имеет право передать функции pututxline() указатель на статическую структуру, заполненную в результате обращения к getutxent(), getutxid() или getutxline(), предварительно изменив ее требуемым образом. Неявное чтение, осуществляемое функцией pututxline() для определения замещаемой записи, эту структуру не испортит.
Функция setutxent() устанавливает указатель текущей позиции на начало базы данных. Ее следует вызывать перед поиском каждой новой записи, если предполагается, что поиск должен проводиться во всей базе.
Функция endutxent() закрывает базу данных учетной информации о пользователях.
Приведем пример использования описанных функций). Прочитаем и выведем все записи  базы данных учетной информации о пользователях.
Листинг 9.8. Пример применения функций для работы с базой данных учетной информации о пользователях.)
Фрагмент возможных результатов выполнения приведенной программы на платформе ОС Linux показан на. Его изучение позволяет лучше уяснить смысл элементов структуры utmpx для разных типов записей и, попутно, увидеть некоторые нестандартности в поведении ОС Linux.
Листинг 9.9. Фрагмент возможных результатов выполнения программы, применяющей функции для работы с базой данных учетной информации о пользователях.)
Отметим, что нестандартный тип записи  1 соответствует смене уровня выполнения.
Обратим также внимание на то, что в шаблоне поиска, производимого с помощью функции getutxid(), значение поля ut_type задано как INIT_PROCESS, а в результате поиска оно оказалось равным USER_PROCESS (в полном соответствии со стандартом).

Функции для работы с простыми базами данных
Описываемые ниже функции полезны для реализации «игрушечных» баз данных при изготовлении прототипов приложений. Нет и речи о поддержке многопользовательского режима. Ключ в записи может быть только один. Суммарная длина ключа и данных (точнее, всех ключей, имеющих один хэш-код, и ассоциированных с ними данных) не должна превышать 1023 байта и т.д. и т.п.
Набор стандартизованных функций «традиционно минимален» (см.). Базу данных можно открыть (dbm_open()) и закрыть (dbm_close()), выбрать (dbm_fetch()), сохранить (dbm_store()) и удалить (dbm_delete()) запись по ключу, перебрать имеющиеся в базе ключи (dbm_firstkey(), dbm_nextkey()), опросить статус ошибки (dbm_error()) и очистить его (dbm_clearerr()).
#include <ndbm.h>

DBM *dbm_open (const char *file,
int open_flags, mode_t file_mode);

void dbm_close (DBM *db);

datum dbm_fetch (DBM *db, datum key);

int dbm_store (DBM *db, datum key,
datum content, int store_mode);

int dbm_delete (DBM *db, datum key);

datum dbm_firstkey (DBM *db);

datum dbm_nextkey (DBM *db);

int dbm_error (DBM *db);

int dbm_clearerr (DBM *db);
Листинг 9.10. Описание функций для работы с простыми базами данных.)
Функцию dbm_open() можно считать аналогом open(), только в роли возвращаемого в качестве результата дескриптора базы данных выступает указатель на объект типа  DBM (структура последнего скрыта от приложения), база хранится в двух файлах – file.dir и file.pag, ее нельзя открыть только на запись, а применение флага O_APPEND ведет к неспецифицированным последствиям. В случае ошибки результат вызова dbm_open() равняется (DBM *) NULL.
При работе с ключами и данными центральную роль играет структура типа datum, которая по стандарту должна содержать по крайней мере два поля.
void   *dptr;  
/* Указатель на прикладные данные  */
size_t  dsize; 
/* Размер прикладных данных        */
Функция dbm_fetch() служит для выборки  записи по ключу  key. Если таковая отсутствует или обнаруживается ошибка, то в возвращаемом объекте типа  datum элемент dptr равняется пустому указателю.
Функция dbm_store() позволяет поместить данные, заданные аргументом  content, в базу. Аргумент  store_mode определяет способ сохранения новых данных. Если в базе уже есть запись с заданным ключом  key, то в режиме DBM_REPLACE новая запись замещает старую, а в режиме DBM_INSERT она (новая запись) игнорируется (и результат вызова равняется единице). Если записи с ключом  key в базе нет, новая запись добавляется к базе.
Функция dbm_delete() предназначена для удаления данных и ключа  key.
Нормальным результатом функций dbm_store() и dbm_delete() является нуль; в случае ошибки возвращается отрицательное значение.
Функция dbm_nextkey() является итератором по ключам, хранящимся в базе, а dbm_firstkey() инициализирует этот итератор, возвращая первый ключ. При завершении перебора и при наличии ошибок возвращается объект типа  datum с пустым указателем в качестве значения элемента dptr. Если по ходу итераций содержимое базы менялось (вызывались функции dbm_store() и/или dbm_delete()), перебор ключей нужно начинать заново.
Функция dbm_error() возвращает ненулевое значение при установленном статусе ошибки, функция dbm_clearerr() делает статус «безошибочным». Таким образом, вся диагностика по сути сводится к одному биту, интерпретировать который должно приложение.
Несмотря на «игрушечность» описанного интерфейса, он находит определенное применение на Unix-системах. В качестве примера рассмотрим программу, которая распечатывает содержимое базы данных  aliases, расположенной в каталоге  /etc/mail на SPARC-станции (см. Листинг 9.11. Пример применения функций для работы с простыми базами данныхРезультаты работы этой программы могут выглядеть так, как показано на Листинг 9.12. Возможные результаты выполнения программы, применяющей функции для работы с простыми базами данных.

Поиск и сортировка
Поиск и сортировка данных, располагающихся в оперативной памяти, – типичная и очень важная часть многих приложений, качество реализации которой во многом определяет технические характеристики программной системы в целом.
Стандарт POSIX-2001 предлагает несколько способов поиска в таблицах разных форматов.
Бинарный поиск (см) является самым быстрым среди методов, основанных на сравнении ключей. Он применим к предварительно отсортированным одномерным массивам.
#include <stdlib.h>
void *bsearch (const void *key,
const void *base,
size_t nel, size_t width,
int (*compar) (const void *,
const void *));
Листинг 9.13. Описание функции бинарного поиска bsearch(). (
Функция bsearch() предназначена для выполнения бинарного поиска в соответствии с алгоритмом, описанным Д. Кнутом (см. в дополнительной литературе, пункт 6.2.1, алгоритм B).
Функция bsearch() возвращает указатель внутрь массива на искомые данные или NULL в случае неудачи поиска. Предварительно массив должен быть отсортирован в возрастающем порядке согласно предоставленной функции сравнения  compar().
Аргумент  key указывает на объект данных, разыскиваемый в массиве (ключ  поиска); base указывает на начало (первый элемент) массива; nel задает количество элементов в массиве; width специфицирует размер элемента в массиве.
Аргумент compar() – это функция сравнения, аргументами которой служат два указателя на сравниваемые объекты – ключ и элемент массива. В соответствии с тем, какое целое число она возвращает: меньшее нуля, равное нулю или большее нуля, ключ считается меньшим, равным или большим по отношению к элементу массива.
Для сортировки массивов целесообразно пользоваться функцией qsort() (см., реализующей метод быстрой сортировки (называемый также методом обменной сортировки с разделением, см. в дополнительной литературе, пункт 5.2.2, алгоритм Q). Ее аргументы имеют тот же смысл, что и одноименные аргументы функции bsearch().
#include <stdlib.h>
void qsort (void *base, size_t nel,
size_t width,
int (*compar) (const void *,
const void *));
Листинг 9.14. Описание функции быстрой сортировки qsort().Рассмотрим пример последовательного применения функций qsort() и bsearch()). Здесь в роли элементов массива выступают указатели на цепочки символов, которые размещены в области StringSpace; тот же тип имеет и ключ  поиска. Критерием сравнения служит алфавитная упорядоченность указуемых цепочек.
Листинг 9.15. Пример применения функций быстрой сортировки и бинарного поиска. Отметим, что при сортировке будут перемещаться элементы массива PtsTable [] – указатели на цепочки символов, но, конечно, не сами цепочки.
Если на компьютере, которым в данный момент пользуется автор, измерить время выполнения приведенной программы посредством утилиты  time с опцией  -p, результаты будут выглядеть следующим образом).
Листинг 9.16. Возможные результаты выполнения программы, применяющей функции быстрой сортировки и бинарного поиска. Читателю предлагается сравнить эти результаты с экспериментально полученными собственными (и с гордостью убедиться, что его компьютер гораздо мощнее), а также оценить зависимость длительности быстрой сортировки и бинарного поиска от размера массива (и подтвердить теоретические оценки из).
Стандартом POSIX-2001, помимо бинарного, предусматривается еще несколько способов поиска – последовательный, с помощью хэш-таблиц и бинарных деревьев. Соответствующие описания сосредоточены в заголовочном файле <search.h>.
В идейном плане самым простым является последовательный поиск. Он может производиться с вставкой (функция lsearch()) или без таковой (lfind())).
#include <search.h>

void *lsearch (const void *key,
void *base, size_t *nelp,
size_t width,
int (*compar) (const void *,
const void *));

void *lfind (const void *key,
const void *base, size_t *nelp,
size_t width, int (*compar) (const void *,
const void *));
Листинг 9.17. Описание функций последовательного поиска. (
Функции, реализующие последовательный поиск, по способу вызова напоминают bsearch(), только аргумент  nelp является указателем на число элементов в массиве, которое функция lsearch() может увеличить на единицу (если искомого элемента в массиве не было, его добавляют в конец). Разумеется, для последовательного поиска не требуется, чтобы массив был предварительно отсортирован. Упрощены и требования к функции сравнения  compar(): в случае неравенства ее результат должен быть отличен от нуля.
В качестве иллюстрации применения функций последовательного поиска рассмотрим программу, генерирующую случайные цепочки символов до первого повторения (см.
Листинг 9.18. Пример применения последовательного поиска с вставкой. (
Указатели на порождаемые случайные цепочки помещаются в массив PtsTable [] функцией lsearch(). В этой связи обратим внимание на нескольку вычурную организацию цикла for в функции main(). По сути здесь две переменные цикла – pss и nelst. Первая продвигается стандартным образом, в заголовке цикла, но проверяется на выход за допустимые границы в его теле; вторая, напротив, стандартно проверяется, но нестандартно продвигается (в результате вызова lsearch()).
Возможные результаты выполнения этой программы показаны на
Листинг 9.19. Возможные результаты выполнения программы, применяющей функцию последовательного поиска с вставкой. (
При экспериментах с приведенной программой следует соблюдать определенную осторожность, поскольку время ее работы квадратично зависит от величины TAB_SIZE.

Управление хэш-таблицами  поиска производится в соответствии с алгоритмом, описанным Д. Кнутом (см. [ в дополнительной литературе, раздел 6.4, алгоритм D). Предоставляются функции для создания (hcreate()) и ликвидации (hdestroy()) хэш-таблиц, а также для выполнения в них поиска (hsearch()), быть может, с вставкой (см. Сразу отметим, что в каждый момент времени может быть активна только одна хэш-таблица.
#include <search.h>

int hcreate (size_t nel);

void hdestroy (void);

ENTRY *hsearch (ENTRY item, ACTION action);
Пример 9.20. Описание функций управления хэш-таблицами поиска. Предполагается, что элементы таблицы поиска имеют тип ENTRY, определенный так, как показано на
typedef struct entry {
char *key;      /* Ключ поиска */
void *data;    
/* Дополнительные данные,   */
/* ассоциированные с ключом */
} ENTRY;
Листинг 9.21. Описание типа ENTRY. (
Функция hcreate() резервирует достаточное количество памяти для таблицы и должна вызываться перед обращением к hsearch(). Значением аргумента  nel является ожидаемое максимальное количество элементов в таблице. Это число можно взять с запасом, чтобы уменьшить среднее время поиска.
Нормальный для hcreate() результат отличен от нуля.
Функция hdestroy() ликвидирует таблицу поиска. За вызовом этой функции может следовать новое обращение к функции создания таблицы hcreate().
Функция hsearch() возвращает указатель внутрь таблицы на искомые данные. Аргумент  item – это структура типа ENTRY, содержащая два указателя: item.key указывает на сравниваемый ключ (функцией сравнения при поиске в хэш-таблице служит strcmp()), а item.data – на любые дополнительные данные, ассоциированные с этим ключом.
Аргумент  action имеет тип ACTION, определенный так, как показано на. Он задает способ действий в случае неудачного поиска: значение ENTER предписывает производить поиск с вставкой, то есть в случае неудачи искомый элемент следует поместить в таблицу; значение FIND предписывает в случае неудачи вернуть пустой указатель NULL. Пустой указатель возвращается и тогда, когда значение аргумента  action равно ENTER, и таблица заполнена.
enum {
FIND,
ENTER
} ACTION;
Листинг 9.22. Определение типа ACTION. (
В качестве примера применения функций, управляющих хэш-таблицами  поиска, рассмотрим программу, которая помещает в хэш-таблицу заданное число элементов с указателями на случайные цепочки символов, а затем выполняет в этой таблице поиск новых случайных цепочек, пока он не окажется успешным (см.
Листинг 9.23. Пример применения функций, управляющих хэш-таблицами поиска.
Обратим внимание на то, что размер хэш-таблицы выбран вдвое большим по сравнению с реально используемым числом элементов; это уменьшает число коллизий (случаев совпадения хэш-кодов разных ключей) и ускоряет их разрешение. При небольшом числе коллизий время поиска одного элемента в хэш-таблице ограничено константой (не зависит от количества элементов в таблице). Это значит, что время работы приведенной программы должно быть пропорционально размеру таблицы, то есть по порядку величины оно меньше, чем для рассмотренной выше комбинации быстрой сортировки и бинарного поиска (убирается множитель, равный логарифму числа элементов). Сделанный вывод подтверждается результатами измерения времени работы программы (см.
Листинг 9.24. Возможные результаты выполнения программы, применяющей функции управления хэш-таблицами поиска. (
Читателю предлагается измерить время работы этой программы на своем компьютере, сравнить его с аналогичным временем для быстрой сортировки и бинарного поиска, а также оценить зависимость среднего времени поиска от размера таблицы (и подтвердить теоретические оценки
Бинарные деревья  поиска – замечательное средство, позволяющее эффективно (за время, логарифмически зависящее от числа элементов) осуществлять операций поиска с вставкой (функция tsearch()) и без таковой (tfind()), удаления (tdelete()) и, кроме того, выполнять обход всех элементов (функция twalk()) (см. Функции реализуют алгоритмы T и D, описанные в пункте 6.2.2 книги Д. Кнута
#include <search.h>

void *tsearch (const void *key, void **rootp,
int (*compar) (const void *,
const void *));

void *tfind (const void *key,
void *const *rootp,
int (*compar) (const void *,
const void *));

void *tdelete (const void *restrict key,
void **restrict rootp,
int (*compar) (const void *,
const void *));

void twalk (const void *root,
void (*action) (const void *,
VISIT, int));
Листинг 9.25. Описание функций управления бинарными деревьями поиска. (
Функция tsearch() используется для построения дерева и доступа к нему. Аргумент  key является указателем на искомые данные (ключ). Если в дереве есть узел, первым полем которого является ссылка на данные, равные искомым, то результатом функции служит указатель на этот узел. В противном случае в дерево вставляется вновь созданный узел со ссылкой на искомые данные и возвращается указатель на него. Отметим, что копируются только указатели, поэтому прикладная программа сама должна позаботиться о хранении данных.
Аргумент  rootp указывает на переменную, которая является указателем на корень дерева. Ее значение, равное NULL, специфицирует пустое дерево; в этом случае в результате выполнения функции tsearch() переменная устанавливается равной указателю на единственный узел – корень вновь созданного дерева.
Подобно функции tsearch(), функция tfind() осуществляет поиск по ключу, возвращая в случае успеха указатель на соответствующий узел. Однако в случае неудачного поиска функция tfind() возвращает пустой указатель NULL.
Функция tdelete(), как и tfind(), сначала производит поиск, но не останавливается на этом, а удаляет найденный узел из бинарного дерева. Результатом tdelete() служит указатель на вышележащий по сравнению с удаляемым узел или NULL, если поиск оказался неудачным.
Функция twalk() осуществляет обход  бинарного дерева в глубину, слева направо (дерево строится функцией tsearch() так, что, в соответствии с функцией сравнения  compar(), все узлы левого поддерева предшествуют его корню, который, в свою очередь, предшествует узлам правого поддерева). Аргумент  root указывает на корень обрабатываемого (под)дерева (любой узел может быть использован в качестве корня для обхода соответствующего поддерева).
Очевидно, в процессе обхода все неконцевые узлы посещаются трижды (при спуске в левое поддерево, при переходе из левого поддерева в правое и при возвращении из правого поддерева), а концевые (листья) – один раз. Эти посещения обозначаются величинами типа VISIT с исключительно неудачными именами (см.
enum {
preorder,
postorder,
endorder,
leaf
} VISIT;
Листинг 9.26. Определение типа VISIT. (
Имена неудачны, потому что они совпадают с названиями разных способов обхода деревьев (см., например, в дополнительной литературе, пункт 2.3.1). В частности, порядок обхода, реализуемый функцией twalk(), называется в прямым (по-английски – preorder). Остается надеяться, что читатель не даст себя запутать и уверенно скажет, что в данном контексте postorder – это второе посещение неконцевого узла  бинарного дерева  поиска, а не какой-то там обратный порядок обхода.

Аргумент  action – это функция, которую twalk() вызывает при попадании в узел во время обхода. Она, в свою очередь, имеет три аргумента. Первым из них служит адрес текущего узла. Структура, на которую указывает этот аргумент, стандартом не специфицируется; оговаривается только, что указатель на узел можно привести к типу «указатель на указатель на хранимые данные» (то есть на данные, ассоциированные с узлом, и содержащие, в частности, ключ  поиска). Второй аргумент вызываемой функции – это значение определенного выше типа VISIT. Напомним еще раз, что оно показывает, который раз (первый, второй или третий) осуществляется доступ к неконцевому узлу во время обхода дерева в глубину и слева направо или свидетельствует, что узел является концевым (листом). Третий аргумент – это уровень узла в дереве (в предположении, что корень имеет уровень 0).
Читатель наверняка уже догадался, что далее последует пример программы, строящей бинарное дерево  поиска для случайных цепочек символов (см.. Впрочем, приведенная программа делает и еще кое-что: подсчитывает число узлов и высоту дерева, распечатывает несколько первых по алфавиту цепочек из числа помещенных в дерево и, конечно, генерирует новые случайные цепочки, пока их поиск в дереве не окажется успешным. Для разнообразия узел с найденной цепочкой удаляется.
Листинг 9.27. Пример применения функций управления бинарными деревьями поиска.
Отметим гибкость бинарных деревьев  поиска как структуры данных. Не требуется заранее резервировать определенное пространство – деревья растут не более, чем это необходимо. При добавлении и удалении узлов деревья динамически перестраиваются без специального переупорядочения или массовых передвижек.
Обратим внимание на частичный обход дерева при распечатке нескольких первых по алфавиту цепочек, реализуемый с использованием механизма нелокальных переходов. Сама по себе функция twalk() ориентирована, разумеется, на полный обход (также представленный в программе).
Возможные результаты выполнения приведенной программы показаны на Листинг 9.28. Возможные результаты выполнения программы, применяющей функции управления бинарными деревьями поиска
Отметим, что среди первых 1000000 случайных цепочек символов длины 10 повторов не оказалось. Дерево  поиска получилось весьма сбалансированным, с высотой, лишь немногим большей двоичного логарифма от числа узлов. Приведенные данные о времени выполнения подтверждают высокую эффективность бинарных деревьев как инструмента поиска.
Полноты ради упомянем еще о двух функциях, описанных в заголовочном файле <search.h>: insque() и remque() (см.. Они предназначены для выполнения операций над очередями, реализованными как двусвязанные списки.
#include <search.h>

void insque (void *element, void *pred);

void remque (void *element);
Листинг 9.29. Описание функций, выполняющих операции над очередями. Функция insque() осуществляет вставку элемента, на который указывает аргумент  element, после элемента pred. В качестве элемента должна выступать структура, первые два поля которой являются указателями на структуры того же типа – соответственно, следующий и предыдущий элементы очереди. Наличие и назначение других полей определяются нуждами приложения. Имя структурного типа и двух первых полей не стандартизуются.
Функция remque() удаляет заданный элемент из очереди.
Очередь может быть линейной или циклической. В первом случае она ограничена пустыми указателями, во втором крайние указатели должны быть зациклены. Вставка первого элемента в линейную очередь осуществляется вызовом insque (&element, NULL); при инициализации циклической очереди о ссылках должно позаботиться приложение (см#include <search.h>

        . . .
struct qelem {
struct qelem *q_forw;
struct qelem *q_back;
char *data;
. . .
};

struct qelem element1;
struct qelem element2;

. . .
element1.q_forw = &element1;
element1.q_back = &element1;

insque (&element2, &element1);

         . . .
Листинг 9.30. Пример инициализации циклической очереди и вставки в нее второго элемента. (
Трудно сказать, есть ли смысл в стандартизации функций, исходный текст которых занимает пару строк...
Из тех же соображений полноты вернемся к теме сортировки и упомянем служебную программу tsort:
tsort  [файл]
выполняющую топологическую сортировку элементов заданного файла (или стандартного ввода, если файл не указан), выдавая результаты на стандартный вывод. Подобная сортировка полезна, в частности, при создании библиотек объектных файлов, чтобы выполнять редактирование внешних связей за один проход.
Исходными данными для утилиты  tsort служат содержащиеся в файле пары элементов (непустых цепочек символов), разделенных пробелами. Частичная упорядоченность задается парами различных элементов. Пара одинаковых элементов означает лишь наличие элемента и никакой упорядоченности не задает.
Например, если применить утилиту  tsort к файлу, содержащему строки, показанные на листинге то можно получить результат, приведенный на листинге
a b
c d
d e
f g
e f
h h
Листинг 9.31. Пример исходных данных для служебной программы tsort. (a
c
h
b
d
e
f
g
Листинг 9.32. Возможный результат применения служебной программы tsort.

Манипулирование пользовательскими контекстами
Согласно стандарту POSIX-2001, пользовательский контекст  потока управления включает содержимое машинных регистров, маску сигналов и текущий стек выполнения. Эти данные сосредоточены в структуре типа ucontext_t, содержащей по крайней мере следующие поля.
ucontext_t     *uc_link;   
/* Указатель на контекст,          */
/* в котором будет возобновлено    */   
/* выполнение при выходе из        */
/* данного контекста               */
sigset_t    uc_sigmask; 
/* Набор сигналов, блокированных   */
/* в данном  контексте             */
stack_t     uc_stack;     
/* Стек, используемый в данном     */
/* контексте                       */
mcontext_t  uc_mcontext;
/* Машинно-зависимое представление */
/* сохраненного контекста          */
Стандарт POSIX-2001 предоставляет функции для опроса (getcontext()), модификации (makecontext()) и смены (setcontext() и swapcontext()) пользовательских контекстов (см.
#include <ucontext.h>

int getcontext (ucontext_t *ucp);

void makecontext (ucontext_t *ucp,
void (*func) (void), int argc, ...);

int setcontext (const ucontext_t *ucp);

int swapcontext (ucontext_t *restrict oucp,
const ucontext_t *restrict ucp);
Листинг 9.33. Описание функций, манипулирующих пользовательскими контекстами потоков управления. (
Функция getcontext() – штатное средство получения исходного материала для манипулирования контекстами. Она запоминает текущий контекст вызывающего потока управления в структуре, на которую указывает аргумент  ucp. Ее нормальный результат равен нулю.
Функция makecontext() модифицирует контекст, заданный аргументом  ucp. Когда (после вызовов setcontext() или swapcontext()) выполнение будет возобновлено в этом контексте, оно продолжится обращением к функции func() с передачей ей аргументов типа int в количестве argc, помещенных после argc при вызове makecontext(). Приложение должно позаботиться о том, чтобы модифицируемый контекст включал стек достаточного размера.
Элемент uc_link структуры типа ucontext_t определяет контекст, в котором будет возобновлено выполнение после выхода из модифицированного функцией makecontext() контекста. Приложение должно позаботиться об инициализации этого элемента до обращения к makecontext().
Функция setcontext() устанавливает пользовательский контекст вызывающего потока управления в соответствии с содержимым структуры, на которую указывает аргумент  ucp. После успешного вызова setcontext() возврата не происходит – выполнение возобновляется с точки, специфицированной новым контекстом, а именно: если этот контекст был сформирован в результате обращения к getcontext(), выполнение возобновляется возвратом из getcontext(); если контекст получен после makecontext(), вызывается функция func(), после возврата из которой поток управления продолжает работу во входном для makecontext() контексте.
Если значением элемента uc_link структуры, на которую указывает аргумент  ucp, служит пустой указатель, то данный контекст соответствует функции main(), после выхода из которой выполнение потока управления завершается.
Функция swapcontext() производит перестановку  пользовательских контекстов. Текущий контекст сохраняется в структуре, на которую указывает аргумент  oucp, а новый контекст формируется по значению аргумента  ucp.
Обратим внимание на следующую тонкость. Когда вызывается функция обработки сигнала, текущий пользовательский контекст запоминается, а для выполнения обработчика формируется новый контекст. Стандарт POSIX-2001 не гарантирует, что после нелокального перехода из функции обработки сигнала посредством longjmp() будет аккуратно восстановлен контекст соответствующего вызова setjmp(). В подобных ситуациях рекомендуется применять функции siglongjmp() или setcontext().
В качестве примера применения функций, манипулирующих пользовательскими контекстами, рассмотрим модифицированную программу из текста стандарта POSIX-2001 (см
Листинг 9.34. Пример применения функций, манипулирующих пользовательскими контекстами.
Обратим внимание на резервирование пространства под стек перед обращением к функции makecontext(), а также на связывание нескольких контекстов в список посредством поля uc_link.
Результаты выполнения приведенной программы показаны на
Листинг 9.35. Возможные результаты выполнения программы, применяющей функции манипулирования пользовательскими контекстами

Управление средой вещественной арифметики
Средства управления средой вещественной арифметики, включенные в стандарт POSIX-2001, позволяют выполнить требования двух других стандартов – вещественной арифметики (IEC 60559:1989) и языка C (ISO/IEC 9899:1999).
Среда вещественной арифметики включает сущности двух видов: флаги состояния и управляющие режимы.
Флаг состояния  вещественной арифметики – это системная переменная, значение которой устанавливается (но никогда не очищается) при возбуждении исключительной ситуации и содержит дополнительную информацию о случившемся исключении.
Под управляющим режимом  вещественной арифметики также понимается системная переменная, но ее значение может быть установлено приложением для воздействия на выполнение последующих операций с вещественными числами.
Тип данных fenv_t, определенный в заголовочном файле <fenv.h>, представляет всю среду, тип fexcept_t – совокупность флагов состояния, включая ассоциированную с флагами информацию.
Применительно к вещественной арифметике стандарт POSIX-2001 предусматривает следующие исключительные ситуации: FE_DIVBYZERO (деление на нуль), FE_INEXACT (потеря точности), FE_INVALID (некорректная операция), FE_OVERFLOW (переполнение), FE_UNDERFLOW (исчезновение порядка). Побитное ИЛИ перечисленных констант обозначается как FE_ALL_EXCEPT.
Стандартом специфицированы четыре режима (направления) округления: FE_DOWNWARD (вниз, то есть к минус бесконечности), FE_TONEAREST (к ближайшему представимому), FE_TOWARDZERO (к нулю), FE_UPWARD (вверх, то есть к плюс бесконечности).
Подразумеваемая среда вещественной арифметики (существующая на момент начала выполнения прикладной программы) обозначается константой FE_DFL_ENV, имеющей тип указателя на константный объект типа  fenv_t.
Если приложение проверяет флаги состояния, устанавливает собственные управляющие режимы или выполняется в режимах, отличных от подразумеваемого, то при компиляции необходимо воспользоваться управляющим комментарием (#pragma) FENV_ACCESS:
#pragma STDC FENV_ACCESS ON
Опросить и установить текущую среду вещественной арифметики можно с помощью функций fegetenv() и fesetenv() (см.
#include <fenv.h>

int fegetenv (fenv_t *fenvp);

int fesetenv (const fenv_t *fenvp);
Листинг 9.36. Описание функций опроса и установки текущей среды вещественной арифметики
Отметим, что функция fesetenv() не возбуждает исключительных ситуаций, она только задает значения флагов состояния. Нормальным для обеих функций является нулевой результат.
Сохранение текущей среды может сочетаться с ее изменением. Так, функция feholdexcept() (см. не только запоминает текущую среду по указателю fenvp, но также очищает флаги состояния и устанавливает «безостановочный» режим (продолжать выполнение при возникновении исключительных ситуаций  вещественной арифметики). Очевидно, пользоваться функцией feholdexcept() имеет смысл, если, помимо безостановочного, реализация предоставляет другие режимы обработки исключений.
#include <fenv.h>
int feholdexcept (fenv_t *fenvp);
Листинг 9.37. Описание функции feholdexcept(). (
Функция feupdateenv() (см. выполняет еще более сложные действия. Она сохраняет в своей локальной памяти информацию о текущей исключительной ситуации, устанавливает новую среду по аргументу  fenvp и затем пытается возбудить в ней сохраненное исключение. Подобные манипуляции полезны, когда массовые вычисления производятся в безостановочном режиме, а затем режим меняется и обрабатывается все то нехорошее, что накопилось за это время.
#include <fenv.h>
int feupdateenv (const fenv_t *fenvp);
Листинг 9.38. Описание функции feupdateenv(). (
Для опроса и установки флагов состояния стандартом POSIX-2001 предусмотрены функции fegetexceptflag() и fesetexceptflag() (см.
#include <fenv.h>

int fegetexceptflag (fexcept_t *flagp,
int excepts);

int fesetexceptflag (const fexcept_t *flagp,
int excepts);
Листинг 9.39. Описание функций опроса и установки флагов состояния среды вещественной арифметики
Функция fegetexceptflag() помещает по указателю flagp зависящее от реализации представление флагов, заданных аргументом  excepts, и ассоциированной с ними информации. Функция fesetexceptflag() выполняет обратную операцию. Как и в случае функции fesetenv(), исключительные ситуации при этом не возбуждаются.
Функции fetestexcept(), feclearexcept() и feraiseexcept() (см. служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.
#include <fenv.h>

int fetestexcept (int excepts);

int feclearexcept (int excepts);

int feraiseexcept (int excepts);
Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций. (
Функция fetestexcept() проверяет, какие из флагов, заданные аргументом  excepts, в данный момент установлены; результатом служит их побитное ИЛИ.
Функция feclearexcept() пытается сбросить, а feraiseexcept() – возбудить заданные исключительные ситуации. Побочным эффектом возбуждения ситуаций переполнения (FE_OVERFLOW) и исчезновения порядка (FE_UNDERFLOW) может стать потеря точности (FE_INEXACT).
Опросить и установить режим округления можно с помощью функций fegetround() и fesetround() (см.
#include <fenv.h>

int fegetround (void);

int fesetround (int round);
Листинг 9.41. Описание функций опроса и установки режима округления. (
Свидетельством неудачного завершения функции fegetround() служит отрицательный результат.
Продемонстрируем применение некоторых функций управления средой вещественной арифметики (см.
Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики. (
Возможные результаты выполнения приведенной программы показаны на Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики.
Отметим, что и для исключительных ситуаций, и для режима округления подразумеваемые значения равны нулю, но нули это разные: первый свидетельствует об отсутствии исключений, в то время как второй обозначает режим округления до ближайшего представимого числа.
Обратим внимание также на то, что вместе с исключительными ситуациями  переполнения и исчезновения порядка устанавливается также флаг потери точности.
В качестве второго примера рассмотрим программу, реализующую некоторые операции интервальной арифметики (см.
Листинг 9.44. Пример использования различных режимов округления. (
Программа переустанавливает режимы округления, поскольку при сложении нижних границ интервалов округлять нужно вниз, а при сложении верхних – вверх. Разумеется, в функции ditvl_add() для сохранения и восстановления режима округления можно было воспользоваться функциями fegetround()/fesetround(), а не fegetenv()/fesetenv().
На показаны возможные результаты выполнения приведенной программы.
Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики. (
Отметим, что в завершающей части программы подразумеваемая среда вещественной арифметики оказалась успешно восстановленной.

Обход файловой иерархии
Обход файловой иерархии – типовая задача, для решения которой стандартом POSIX-2001 предлагаются две сходные функции – ftw() и nftw() (см.
#include <ftw.h>

int ftw (const char *path,
int (*fn) (const char *,
const struct stat *, int),
int depth);

int nftw (const char *path,
int (*fn) (const char *,
const struct stat *, int, struct FTW *),
int depth, int flags);
Листинг 9.46. Описание функций обхода файловой иерархии. (
Функция ftw() рекурсивно обходит иерархию каталогов, имеющую своим корнем каталог с маршрутным именем, на которое указывает аргумент  path. Для каждого объекта иерархии ftw() вызывает функцию fn(), передавая ей три аргумента: указатель на цепочку символов, ограниченную нулевым байтом и содержащую имя объекта; указатель на структуру типа stat, содержащую информацию об объекте; тип объекта, представленный целым числом.
Возможны следующие значения типа объекта.
FTW_D
Каталог.
FTW_DNR
Каталог, недоступный на чтение.
FTW_F
Обычный файл.
FTW_SL
Символьная ссылка.
FTW_NS
Объект, отличный от символьной ссылки, для которого stat() не может выполниться успешно.
Если тип объекта есть FTW_DNR, элементы этого каталога не просматриваются. Если тип есть FTW_NS, то структура типа stat будет содержать неопределенные значения. Примером объекта, который вызовет передачу функции fn() типа FTW_NS, является файл в каталоге, доступном для чтения, но не для поиска.
Функция ftw() обрабатывает каталог перед обработкой его элементов.
Функция ftw() использует не более одного файлового дескриптора на обход каждого уровня иерархии. Аргумент  depth ограничивает количество используемых таким образом дескрипторов; его значение должно принадлежать диапазону [1, OPEN_MAX].
Обход завершится тогда, когда будет обойдена вся иерархия, или функция fn() вернет ненулевое значение, или возникнет ошибка, отличная от EACCES, при работе самой функции ftw() (например, ошибка ввода/вывода). Если дерево обойдено полностью, ftw() в качестве результата возвращает нуль. Если fn() вернет ненулевое значение, то ftw() прекратит обход и выдаст это значение. Если будет обнаружена ошибка при работе самой функции ftw(), то она вернет -1 и соответствующим образом установит значение переменной errno.
Обратим внимание на следующее (впрочем, довольно очевидное) обстоятельство. Функция ftw() во время своей работы временно резервирует некоторые ресурсы (оперативную память, файловые дескрипторы). Если прервать ее нештатным образом (например, посредством нелокального перехода из функции fn() или обработчика сигнала), эти ресурсы останутся неосвобожденными. Рекомендуемый способ обработки прерываний заключается в том, чтобы зафиксировать факт получения прерывания и при очередном вызове fn() заставить ее вернуть ненулевое значение.
Функция nftw() аналогична ftw(), однако и у нее самой, и у вызываемой ею функции fn() имеется по одному дополнительному аргументу. Дополнительный аргумент  flags управляет работой функции nftw(). Его значение формируется как побитное ИЛИ следующих флагов.
FTW_CHDIR
Делать текущим просматриваемый каталог. Если этот флаг не установлен, функция nftw() не изменит текущий каталог.
FTW_DEPTH
Обход вглубь: обрабатывать каталог после обработки его элементов.
FTW_MOUNT
Обрабатывать только файлы из той же файловой системы, что и path.
FTW_PHYS
Осуществлять физический обход без разрешения символьных ссылок.
Третий аргумент функции fn(), по сравнению с ftw() может принимать следующие дополнительные значения.
FTW_DP
Каталог, элементы которого уже обработаны (данное условие может быть истинным только при установленном флаге FTW_DEPTH).
FTW_SLN
Символьная ссылка, именующая отсутствующий файл.
Дополнительный, четвертый аргумент функции fn() является указателем на структуру типа FTW. Согласно стандарту, она должна содержать по крайней мере следующие поля.
int base;    
/* Смещение простого имени файла */
/* от начала маршрутного имени,  */
/* переданного fn() в качестве   */
/* первого аргумента             */
int level;   
/* Уровень текущего объекта      */
/* относительно корня иерархии   */
/* (у самого корня нулевой уровень) */
В качестве примера обхода файловой иерархии рассмотрим программу, подсчитывающую суммарный размер и высоту дерева файлов (см.
Листинг 9.47. Пример программы, осуществляющей обход файловой иерархии. Обратим внимание на возможность досрочного завершения обхода за счет возврата ненулевого результата функцией обработки, если последней передан файл, данные о котором получить не удалось, а также на использование флагов физического обхода и игнорирования файлов из других файловых систем.
Пусть в каталоге  /tmp существует непустой подкаталог gaga со следующим режимом доступа:
drw-r--r-- 2 galat sys 4096 May 7 17:26 gaga
Тогда результаты запуска приведенной программы с аргументом  /tmp могут выглядеть так, как показано на
Листинг 9.48. Возможные результаты выполнения программы, осуществляющей обход файловой иерархии. (

Формирование и выполнение командных строк
Служебная программа xargs позволяет формировать и выполнять командные строки, объединяя зафиксированный набор начальных аргументов с аргументами, прочитанными со стандартного ввода:
xargs     [-E логич_конец_файла]
[-I заменяемая_цепочка]
[-L число] [-n число] [-p] [-s размер]
[-t] [-x]
[утилита [начальный_аргумент ...]]
Программа xargs объединяет зафиксированный набор заданных начальных_аргументов с аргументами, прочитанными со стандартного ввода, и выполняет указанную утилиту (по умолчанию – echo) в рамках сформированной командной строки, ожидая завершения выполнения. Данный процесс повторяется до достижения физического или логического конца файла стандартного ввода или до возвращения выполненной командной строкой  кода завершения 255.
Аргументы, прочитанные со стандартного ввода, – это непрерывные цепочки символов, разделенные одним или несколькими пробелами или переводами строки; пустые строки игнорируются.
Опциям служебной программы xargs приписан следующий смысл.
-E логич_конец_файла
Цепочка символов  логич_конец_файла считается признаком логического конца файла  стандартного ввода.
-I заменяемая_цепочка
Режим вставки: утилита выполняется для каждой строки стандартного ввода, причем вся строка рассматривается как один аргумент и подставляется в начальные аргументы вместо каждого вхождения заменяемой цепочки. Допускается не более пяти начальных аргументов, содержащих одно или несколько подобных вхождений. Пробелы в начале вводимых строк отбрасываются. Сформированные аргументы не могут быть длиннее 255 символов. Опция  -I включает опцию  -x.
-L число
Выполнять утилиту для каждой группы из заданного числа непустых строк аргументов, прочитанных со стандартного ввода. Последний вызов утилиты может быть с меньшим числом строк аргументов. Считается, что строка заканчивается символом перевода строки, если только перед ним не стоит пробел; пробел в конце сигнализируют о том, что следующая непустая строка является продолжением данной.
-n число
Выполнить утилиту, используя максимально возможное количество аргументов, прочитанных со стандартного ввода, но не более заданного числа. Будет использовано меньше аргументов, если их общая длина превышает размер, специфицированный опцией  -s, или если для последнего вызова их осталось меньше, чем заданное число.
-p
Режим с приглашением: xargs перед каждым вызовом утилиты запрашивает подтверждение. Включается режим трассировки (-t), за счет чего в стандартный протокол выводится командная строка, которая должна быть выполнена, а за ней – приглашение «?...». Положительный ответ, прочитанный с устройства /dev/tty, приводит к выполнению утилиты; в противном случае данный вызов утилиты игнорируется.
-s размер
Максимальный общий размер (в символах) каждого списка аргументов установить равным заданной величине; в рамках этого ограничения со стандартного ввода берется максимально возможное число аргументов. Будет использовано меньше аргументов, если более сильными окажутся ограничения, наложенные опциями  -n или -L, или если встретится конец файла.
-t
Режим трассировки: каждая сформированная командная строка перед выполнением выдается в стандартный протокол.
-x
Завершить работу служебной программы xargs, если очередная сформированная командная строка, потребившая заданное опцией  -n число аргументов или заданное опцией  -L число строк, оказалась длиннее, чем специфицированный опцией  -s размер.
Приведем несколько примеров применения служебной программы xargs. На всякий случай подчеркнем, что никто не утверждал, что Linux или какая-либо иная операционная система соответствует стандарту POSIX-2001. Весьма вероятно, что перед исполнением примеров их придется немного подправить.
Следующая однострочная shell-процедура пересылает все файлы из каталога  $1 в каталог  $2 и сообщает о каждой пересылке, перед тем как ее выполнить:
ls $1 | xargs -I {} -t mv $1/{} $2/{}
Еще одна однострочная shell-процедура применяет служебную программу diff к последовательным парам своих аргументов.
echo $* | xargs -n 2 diff
Пользователя спрашивают, какие объектные файлы из текущего каталога должны быть занесены в библиотеку mylib.a. При выполнении первого конвейера (см. ниже) файлы заносятся по одному; при выполнении второго заносится сразу много файлов.
ls *.o | xargs -p -L 1 ar -r mylib.a
ls *.o | xargs -p -L 1 | xargs ar -r mylib.a
Отметим, что во втором звене второго конвейера в качестве подразумеваемой утилиты будет использована служебная программа echo.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика