355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Арнольд Роббинс » Linux программирование в примерах » Текст книги (страница 38)
Linux программирование в примерах
  • Текст добавлен: 6 мая 2017, 11:00

Текст книги "Linux программирование в примерах"


Автор книги: Арнольд Роббинс



сообщить о нарушении

Текущая страница: 38 (всего у книги 55 страниц)

12.6.2. Функции POSIX: random() и srandom()

BSD 4.3 ввело random() и сопровождающие ее функции. Эти функции используют намного более подходящий генератор случайных чисел, который возвращает 31-разрядное значение. Теперь они входят в расширение XSI, стандартизованное POSIX:

#include /* XSI */

long random(void);

void srandom(unsigned int seed);

char *initstate(unsigned int seed, char *state, size_t n);

char *setstate(char *state);

Первые две функции близко соответствуют rand() и srand() и могут использоваться сходным образом. Однако, вместо одного начального значения, которое дает последовательность псевдослучайных чисел, эти функции используют начальное значение вместе с массивом состояния: массивом байтов, который хранит сведения о состоянии для вычисления псевдослучайных чисел. Последние две функции дают возможность управлять массивом состояния.

long random(void);

Возвращает число в диапазоне от 0 до 231-1. (Хотя справочная страница GNU/Linux random(3) говорит между 0 и RAND_MAX, это верно лишь для систем GLIBC, где RAND_MAX равен 231-1. На других системах RAND_MAX может быть меньше. POSIX явно называет диапазон от 0 до 231-1.)

void srandom(unsigned int seed);

Устанавливает начальное число. Если srandom() никогда не вызывается, по умолчанию используется 1.

char *initstate(unsigned int seed, char *state, size_t n);

Инициализирует массив state информацией для использования при генерации случайных чисел, seed является начальным значением, как для srandom(), а n является числом байтов в массиве state.

n должен равняться одному из значений 8, 32, 64, 128 или 256. Большие значения дают лучшие последовательности случайных чисел. Значения меньше 8 заставляют random() использовать простой генератор случайных чисел, сходный с rand(). Значения больше 8, не равные одному из значений в списке, округляются до ближайшего подходящего значения.

char *setstate(char *state);

Устанавливает внутреннее состояние в соответствии с массивом state, который должен быть инициализирован посредством initstate(). Это позволяет переключаться по желанию между различными состояниями, предоставляя множество генераторов случайных чисел.

Если initstate() и setstate() никогда не вызывались, random() использует массив внутреннего состояния размером 128.

Массив state непрозрачен; вы инициализируете его с помощью initstate() и передается функции random() посредством setstate(), но в другом отношении вам не нужно заглядывать в него. Если вы используете initstate() и setstate(). srandom() вызывать не нужно, поскольку начальное значение включено в информацию о состоянии. ch12-random.c использует эти процедуры вместо rand(). Используется также обычная методика, которая заключается в использовании в качестве начального значения генератора случайных чисел времени дня, добавленного к PID.

1  /* ch12-random.c – генерация вращения костей с использованием random(). */

2

3  #include

4  #include

5  #include

6  #include

7

8  char *die_faces[] = { /* Управляет ASCII графика! */

    /* ... как раньше ... */

32 };

33

34 /* main – выводит N различных граней кубиков */

35

36 int main(int argc, char **argv)

37 {

38  int nfaces;

39  int i, j, k;

40  char state[256];

41  time_t now;

42

    /* ... проверка args, вычисление nfaces, как раньше ... */

55

56  (void)time(&now); /* В качестве начального значения используются время дня и PID */

57  (void) initstate((unsigned int)(now + getpid()), state, sizeof state);

58  (void)setstate(state);

59

60  for (i = 1; i <= nfaces; i++) {

61   j = random() % 6; /* использовать диапазон 0 <= j <= 5 */

62    printf("+–+n");

63    for (k = 0; k < 3; k++)

64     printf("|%s|n", die_faces[(j * 3) + k]);

65    printf("+–+nn");

66  }

67

68  return 0;

69 }

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

Поскольку она создает последовательности случайных чисел лучшего качества, random() является более предпочтительной по сравнению с rand(), и GNU/Linux и все современные системы Unix ее поддерживают.

12.6.3. Особые файлы /dev/random и /dev/urandom

Как rand(), так и srandom() являются генераторами псевдослучайных чисел. Их вывод для одного и того же начального значения является воспроизводимой последовательностью чисел. Некоторым приложениям, подобным криптографическим, необходимо, чтобы их случайные числа были действительно (более) случайными. С этой целью ядро Linux, также как различные BSD и коммерческие Unix системы предусматривают специальные файлы устройств, которые предоставляют доступ к «энтропийному пулу» случайных битов, которые ядро собирает от физических устройств и других источников. Из справочной страницы random(4):

/dev/random

[Байты, прочитанные из этого файла, находятся] внутри предполагаемого числа шумовых битов в энтропийном пуле, /dev/random должен подходить для использования в случаях, когда необходим высокий уровень случайности, таких, как одноразовая генерация ключа или блока памяти. Когда энтропийный пул пустой, чтение /dev/random будет блокироваться до тех пор, пока не будет собран дополнительный шум окружения.

/dev/urandom

[Это устройство будет] возвращать столько байтов, сколько затребовано. В результате, если нет достаточной энтропии в энтропийном пуле, возвращаемые значения теоретически уязвимы для криптографической атаки алгоритма, использованного драйвером. Знание того, как это сделать, недоступно в современной не секретной литературе, но теоретически возможно существование подобной атаки. Если для вашего приложения это представляет проблему, вместо этого используйте /dev/random.

Для большинства приложений чтения из /dev/urandom должно быть вполне достаточно. Если вы собираетесь написать криптографические алгоритмы высокого качества, следует сначала почитать о криптографии и случайности; не полагайтесь здесь на поверхностное представление! Вот еще одна наша программа для бросания костей, использующая /dev/urandom:

1  /* ch12-devrandom.с – генерирует бросание костей, используя /dev/urandom. */

2

3  #include

4  #include

5  #include

6

7  char *die_faces[] = { /* Управляет ASCII графика! */

    /* ... как ранее ... */

31 };

32

33 /* myrandom – возвращает данные из /dev/urandom в виде unsigned long */

34

35 unsigned long myrandom(void)

36 {

37  static int fd = -1;

38  unsigned long data;

39

40  if (fd == -1)

41  fd = open("/dev/urandom", O_RDONLY);

42

43  if (fd == -1 || read(fd, &data, sizeof data) <= 0)

44   return random(); /* отступить */

45

46  return data;

47 }

48

49 /* main – вывести N различных граней кубиков */

50

51 int main(int argc, char **argv)

52 {

53  int nfaces;

54  int i, j, k;

55

    /* ...проверка args, вычисление nfaces, как ранее... */

68

69  for (i = 1; i <= nfaces; i++) {

70   j = myrandom() % 6; /* обеспечить диапазон 0 <= j <= 5 */

71   printf("+–+n");

72   for (k = 0; k < 3; k++)

73    printf("|%s|n", die_faces[(j * 3) + k]);

74   printf("+–+n");

75   putchar('n');

76  }

77

78  return 0;

79 }

Строки 35–47 предоставляют интерфейс вызова функции для /dev/urandom, читая каждый раз данные unsigned long. Издержками является один дескриптор файла, который остается открытым в течение жизни программы.

12.7. Расширения метасимволов

Три набора функции возрастающей сложности предусматривают возможность сопоставления с шаблонами групповых символов оболочки. Многим программам нужны такие библиотечные функции. Одним примером является find: 'find . -name '*.с' -print'. Другим примером является опция –exclude во многих программах, которая принимает шаблон файлов с групповыми символами для исключения из того или иного действия. В данном разделе по очереди рассматривается каждый набор функций.

12.7.1. Простое сопоставление с шаблоном: fnmatch()

Мы начинаем с функции fnmatch() («filename match» сопоставление имени файла»).

#include /* POSIX */

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

Эта функция сопоставляет string с pattern, который является обычным шаблоном групповых символов оболочки. Значение флагов (которое вскоре будет описано) изменяет поведение функции. Возвращаемое значение равно 0, если string соответствует pattern, FNM_NOMATCH, если не соответствует, и ненулевое значение, если возникла ошибка. К сожалению, POSIX не определяет каких-либо специфических ошибок; соответственно, вы можете лишь сказать, что что-то пошло не так, но не можете сказать, что.

Переменная flags является побитовым ИЛИ одного или более флагов, перечисленных в табл. 12.1.

Таблица 12.1. Значения флагов для fnmatch()


FNM_CASEFOLD Сопоставление с учетом регистра
FNM_FILE_NAME Синоним GNU для FNM_PATHNAME
FNM_LEADING_DIR Флаг для внутреннего использования GLIBC; не используйте его в своих программах. Подробности см. в fnmatch(3)
FNM_NOESCAPE Обратный слеш является обычным символом, а не знаком перехода
FNM_PATHNAME Слеш в string должен соответствовать слешу в pattern, он не может быть подставлен через *, ? или '[...]'
FNM_PERIOD Начальная точка в string подходит, лишь если в pattern также есть начальная точка. Точка должна быть первым символом в string. Однако, если также установлен FNM_PATHNAME, точка, которая идет за слешем, также рассматривается как начальная

fnmatch() работает со строками из любого источника; сопоставляемые строки не обязательно должны быть действительными именами файлов. Хотя на практике fnmatch() используется в коде, читающем каталог с помощью readdir() (см раздел 5.3.1 «Базовое чтение каталогов»):

struct dirent dp;

DIR *dir;

char pattern[100];

/* ...заполнить шаблон, открыть каталог, проверить ошибки... */

while ((dp = readdir(dir)) != NULL) {

 if (fnmatch(pattern, dir->d_name, FNM_PERIOD) == 0)

  /* имя файла соответствует шаблону */

 else

  continue; /* не соответствует */

}

GNU ls использует fnmatch() для реализации своей опции –ignore. Вы можете предоставить несколько игнорируемых шаблонов (с помощью нескольких опций). ls сопоставляет каждое имя файла со всеми шаблонами. Она делает это с помощью функции file_interesting() в ls.с:

2269 /* Возвращает не ноль, если файл в 'next' должен быть перечислен. */

2270

2271 static int

2272 file_interesting(const struct dirent *next)

2273 {

2274  register struct ignore_pattern* ignore;

2275

2276  for (ignore = ignore_patterns; ignore; ignore = ignore->next)

2277   if (fnmatch(ignore->pattern, next->d_name, FNM_PERIOD) == 0)

2278    return 0;

2279

2280  if (really_all_files

2281   || next->d_name[0] !=

2282   || (all_files

2283   && next->d_name[1] != ' '

2284   && (next->d_name[1] || next->d_name[2] != '')))

2285   return 1;

2286

2287  return 0;

2288 }

Цикл в строках 2276–2278 сопоставляет имя файла со списком шаблонов для игнорируемых файлов. Если один из шаблонов подходит, файл не интересен и file_interesting() возвращает false (то есть 0).

Переменная all_files соответствует опции , которая показывает файлы, имена которых начинаются с точки, но не являются '.' и '..'. Переменная really_all_files соответствует опции , которая предполагает , а также показывает '.' и '..'. При наличии таких сведений, условие в строках 228–2284 может быть представлено следующим псевдокодом:

if (/* показать все файлы независимо от их имени (-а) */

 OR /* первый символ имени не точка */

 OR (/* показать файлы с точкой (-А) */

  AND /* в имени файла несколько символов */

  AND (/* второй символ не точка */

   OR /* третий символ не завершает имя */)))

 return TRUE;

ЗАМЕЧАНИЕ. fnmatch() может оказаться дорогостоящей функцией, если она используется в локали с многобайтным набором символов. Обсудим многобайтные наборы символов в разделе 13.4 «Можете произнести это для меня по буквам?»

12.7.2. Раскрытие имени файла: glob() и globfree()

Функции glob() и globfree() более разработанные, чем fnmatch():

#include /* POSIX */

int glob(const char *pattern, int flags,

int (*errfunc)(const char *epath, int eerrno), glob_t *pglob);

void globfree(glob_t *pglob);

Функция glob() осуществляет просмотр каталога и сопоставление с шаблонами, возвращая список всех путей, соответствующих pattern. Символы подстановки могут быть включены в нескольких местах пути, а не только в качестве последнего компонента (например, '/usr/*/*.so'). Аргументы следующие:

const char *pattern

Шаблон для раскрывания.

int flags

Флаги, управляющие поведением glob(), вскоре будут описаны.

int (*errfunc)(const char *epath, int eerrno)

Указатель на функцию для использования при сообщениях об ошибках. Это значение может равняться NULL. Если нет и если (*errfunc)() возвращает ненулевое значение или в flags установлен GLOB_ERR, glob() прекращает обработку. Аргументами (*errfunc)() являются путь, вызвавший проблему, и значение errno, установленное функциями opendir(), readdir() или stat().

glob_t *pglob

Указатель на структуру glob_t, использующуюся для хранения результатов. Структура glob_t содержит список путей, которые выдает glob():

typedef struct {  /* POSIX */

 size_t gl_pathc; /* Число найденных подходящих путей */

 char **gl_pathv; /* Список подходящих путей */

 size_t gl_offs;  /* Слоты для резервирования в gl_pathv */

} glob_t;

size_t gl_pathc

Число путей, которые подошли.

char **gl_pathv

Массив подходящих путей. gl_pathv[gl_pathc] всегда равен NULL.

size_t gl_offs

«Зарезервированные слоты» в gl_pathv. Идея заключается в резервировании слотов спереди от gl_pathv для заполнения их приложением впоследствии, как в случае с именем команды и опциями. Список затем может быть передан непосредственно execv() или execvp() (см. раздел 9.1.4 «Запуск новой программы: семейство exec()»). Зарезервированные слоты устанавливаются в NULL. Чтобы все это работало, в flags должен быть установлен GLOB_DOOFFS.

В табл. 12.2 перечислены стандартные флаги для glob().

Таблица 12.2. Флаги для glob()


GLOB_APPEND Добавить результаты текущего вызова к предыдущим
GLOB_DOOFFS Зарезервировать места gl_offs спереди в gl_pathv
GLOB_MARK Добавлять символ / в конец каждого имени, которое обозначает каталог
GLOB_NOCHECK Если шаблон не соответствует имени какого-нибудь файла, вернуть его без изменений
GLOB_NOESCAPE Рассматривать обратный слеш как обычный символ. Это делает невозможным обозначать метасимволы подстановок
GLOB_NOSORT Не сортировать результаты, по умолчанию они сортируются

GLIBC версия структуры glob_t содержит дополнительные члены:

typedef struct { /* GLIBC */

 /* Компоненты POSIX: */

 size_t gl_pathc; /* Число подходящих путей */

 char **gl_pathv; /* Список подходящих путей */

 size_t gl_offs; /* Резервируемые в gl_pathv слоты */

 /* Компоненты GLIBC: */

 int gl_flags; /* Копия флагов, дополнительные флаги GLIBC */

 void (*gl_closedir)(DIR *); /* Частная версия closedir() */

 struct dirent *(*gl_readdir)(DIR *); /* Частная версия readdir)) */

 DIR *(*gl_opendir)(const char *); /* Частная версия opendir)) */

 int (*gl_lstat)(const char *, struct stat *);

  /* Частная версия lstat() */

 int (*gl_stat)(const char *, struct stat *); /* Частная версия stat() */

} glob_t;

Члены структуры следующие:

int gl_flags

Копия флагов. Включает также GLOB_MAGCHAR, если pattern включал какие-либо метасимволы.

void (*gl_closedir)(DIR *)

Указатель на альтернативную версию closedir().

struct dirent *(*gl_readdir)(DIR *)

Указатель на альтернативную версию readdir().

DIR *(*gl_opendir)(const char *)

Указатель на альтернативную версию opendir().

int (*gl_lstat)(const char *, struct stat*)

Указатель на альтернативную версию lstat().

int (*gl_stat)(const char*, struct stat*)

Указатель на альтернативную версию stat().

Указатели на альтернативные версии стандартных функций предназначены главным образом для использования в реализации GLIBC; крайне маловероятно, что вы когда-нибудь их используете. Поскольку GLIBC предусматривает поле gl_flags и дополнительные значения флагов, справочная страница и руководство Info документируют оставшуюся часть структуры GLIBC glob_t. В табл. 12.3 перечислены дополнительные флаги.

Таблица 12.3. Дополнительные флаги GLIBC для glob()


GLOB_ALTDIRFUNC Использовать для доступа к каталогам альтернативные функции (см. текст)
GLOB_BRACE Выполнить раскрытие фигурных скобок в стиле csh и Bash.
GLOB_MAGCHAR Вставить gl_flags, если были найдены метасимволы.
GLOB_NOMAGIC Вернуть шаблон, если он не содержит метасимволов
GLOB_ONLYDIR По возможности сопоставлять лишь каталоги. См. текст.
GLOB_PERIOD Разрешить соответствие метасимволов наподобие * и ? начальной точке
GLOB_TILDE Выполнить раскрывание тильды в стиле оболочки.
GLOB_TILDE_CHECK Подобно GLOB_TILDE, но если есть проблемы с указанным домашним каталогом, вернуть GLOB_NOMATCH вместо помещения pattern в список.

Флаг GLOB_ONLYDIR действует в качестве подсказки реализации, потому что вызывающий интересуется лишь каталогами. Главным его предназначением является использование другими функциями в GLIBC, а вызывающий по-прежнему должен быть готов обрабатывать файлы, не являющиеся каталогами. Вам не следует использовать этот флаг в своих программах.

glob() может быть вызвана более одного раза: при первом вызове флаг GLOB_APPEND не должен быть указан, при всех последующих вызовах он должен быть указан. Вы не можете между вызовами изменять gl_offs, а если вы изменили какие-нибудь значения в gl_pathv или gl_pathc, нужно их восстановить перед последующим вызовом glob().

Возможность многократного вызова glob() позволяет накапливать результаты в одном списке. Это довольно практично, приближается к мощным возможностям раскрывания групповых символов оболочки, но на уровне языка программирования С.

glob() возвращает 0, если не было проблем, или одно из значений из табл. 12.4, если были.

Таблица 12.4. Возвращаемые glob() значения


GLOB_ABORTED Просмотр остановлен раньше времени, поскольку был установлен GLOB_ERR или функция (*errfunc)() возвратила ненулевой результат
GLOB_NOMATCH Ни одно имя файла не соответствовало pattern, а флаг GLOB_NOCHECK не был установлен
GLOB_NOSPACE Была проблема с выделением динамической памяти

globfree() освобождает всю память, которую динамически выделила glob() Следующая программа, ch12-glob.с, демонстрирует glob():

1  /* ch12-glob.c – демонстрирует glob(). */

2

3  #include

4  #include

5  #include

6

7  char *myname;

8

9  /* globerr – выводит сообщение об ошибке для glob() */

10

11 int globerr(const char *path, int eerrno)

12 {

13  fprintf(stderr, "%s: %s: %sn", myname, path, strerror(eerrno));

14  return 0; /* let glob() keep going */

15 }

16

17 /* main() – раскрывает символы подстановки в командной строке и выводит результаты */

18

19 int main(int argc, char **argv)

20 {

21  int i;

22  int flags = 0;

23  glob_t results;

24  int ret;

25

26  if (argc == 1) {

27   fprintf(stderr, "usage: %s wildcard ...n", argv[0]);

28   exit(1);

29  }

30

31  myname = argv[0]; /* для globerr() */

32

33  for (i = 1; i < argc; i++) {

34   flags |= (i > 1 ? GLOB_APPEND : 0);

35   ret = glob(argv[i], flags, globerr, &results);

36   if (ret != 0) {

37    fprintf(stderr, "%s: problem with %s (%s),

38     stopping earlyn", myname, argv[i],

39     /* опасно: */ (ret == GLOB_ABORTED ? "filesystem problem" :

40     ret == GLOB_NOMATCH ? "no match of pattern" :

41     ret == GLOB_NOSPACE ? "no dynamic memory" :

42     "unknown problem"));

43    break;

44   }

45  }

46

47  for (i = 0; i < results.gl_pathc; i++)

48   printf("%sn", results.gl_pathv[i]);

49

50  globfree(&results);

51  return 0;

52 }

Строка 7 определяет myname, которая указывает на имя программы; эта переменная для сообщений об ошибках от globerr(), определенной в строках 11–15.

Строки 33–45 являются основой программы. Они перебирают в цикле шаблоны, приведенные в командной строке, вызывая для каждого glob() для добавления к списку результатов. Большую часть цикла составляет обработка ошибок (строки 36–44). Строки 47–48 выводят результирующий список, а строки 50–51 проводят завершающую очистку и возвращаются.

Строки 39–41 не являются хорошими; нужно было использовать отдельную функцию, преобразующую целые константы в строки; мы сделали это главным образом ради экономии места. Код наподобие этого может быть сносным для небольших программ, но более крупные должны использовать функцию.

Если вы подумаете о работе, происходящей под капотом (открытие и чтение каталогов, сопоставление шаблонов, динамическое выделение памяти для увеличения списка, сортировка списка), можете качать ценить, как много для вас делает glob()! Вот некоторые результаты:

$ ch12-glob '/usr/lib/x*.so' '../../*.texi'

/usr/lib/xchat-autob5.so

/usr/lib/xchat-autogb.so

../../00-preface.texi

../../01-intro.texi

../../02-cmdline.texi

../../03-memory.texi

...

Обратите внимание, что нам пришлось взять аргументы в кавычки, чтобы предотвратить их разворачивание оболочкой!

Универсализация имен? Что это?

В былые времена, около V6 Unix, для осуществления разворачивания символов подстановки оболочка использовала за кулисами отдельную программу. Эта программа называлась /etc/glob, и согласно исходному коду V6[130]130
  См /usr/source/s1/glob.c в дистрибутиве V6


[Закрыть]
, имя «glob» было сокращением от «global».

Таким образом глагол «to glob» проник в лексикон Unix со значением «осуществлять разворачивание символов подстановки». Это, в свою очередь, дает нам имена функций glob() и globfree(). Так что обычно недооцениваемое чувство юмора, время от времени проглядывающее из руководства Unix, все еще живо, официально сохраненное в стандарте POSIX. (Можете ли вы представить кого-нибудь в IBM в 70-х или 80-х годах XX века, называющего системную процедуру glob()?)


    Ваша оценка произведения:

Популярные книги за неделю