Книга: Linux программирование в примерах

8.4.3.2. Функция обратного вызова nftw()

8.4.3.2. Функция обратного вызова nftw()

После запуска nftw() она вызывает функцию, указатель для которой предоставляете вы. (Такие функции называются функциями обратного вызова (callback functions), поскольку они «вызываются обратно» из библиотечного кода.) Функция обратного вызова получает четыре аргумента:

const char *file

Имя текущего обрабатываемого файла (каталога, символической ссылки и т.д.).

const struct stat *sb

Указатель на struct stat для файла.

int flag

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

struct FTW *s

Эта структура предоставляет две отдельные части информации:

struct FTW {
 int base;  /* Индекс в файле базовой части имени файла */
 int level; /* Глубина этого элемента относительно точки отсчета */
};

Параметр flag имеет одно из перечисленных в табл. 8.4 значений.

Таблица 8.4. Значения флагов для функции обратного вызова nftw()

Флаг Значение
FTW_F Объект является обычным файлом
FTW_D Объект является каталогом
FTW_DNR Объект является каталогом, который нельзя прочесть
FTW_SL Объект является символической ссылкой
FTW_NS Объект не является символической ссылкой, а stat() потерпела неудачу
FTW_DP Объект является каталогом, элементы которого были уже обработаны. Это может случиться, лишь когда в вызове nftw() использовался FTW_DEPTH
FTW_SLN Объект является символической ссылкой, указывающей на несуществующий файл. Это может случиться, лишь когда в вызове nftw() не используется FTW_PHYS

struct FTW* s предоставляет дополнительную информацию, которая может быть полезной. s->base действует в качестве индекса в file;file является полным путем обрабатываемого объекта (относительно точки отсчета), 'file + s->base' указывает на первый символ компонента имени файла.

s->level указывает текущую глубину иерархии; считается, что первоначальная точка отсчета находится на уровне 0.

Функция обратного вызова должна вернуть 0, если все нормально. Любое ненулевое возвращенное значение заставляет nftw() прекратить свою обработку и вернуть то самое ненулевое значение. Справочная страница отмечает, что функция обратного вызова должна останавливать обработку только путем возвращаемого значения, чтобы у nftw() был шанс произвести очистку: т.е. освободить динамически выделенную память, закрыть открытые дескрипторы файлов и т.д. Функции обратного вызова не следует использовать longjmp(), если только программа не завершается немедленно, (longjmp() является продвинутой функцией, которую мы опишем в разделе 12.5 «Нелокальные goto».) Рекомендуемой методикой обработки ошибок является установка глобальной переменной, указывающей на наличие проблем, возвращение 0 из функции обратного вызова и обработка ошибок после завершения перемещения nftw() по иерархии файлов. (GNU du это делает, как мы вскоре увидим.)

Давайте свяжем все это воедино в примере программы. ch08-nftw.c обрабатывает каждый файл или каталог, указанный в командной строке, запуская для них nftw(). Функция, обрабатывающая каждый файл, выводите отступом имя и тип файла, показывая иерархическое положение каждого файла. Для разнообразия мы сначала покажем результаты, а затем покажем и обсудим программу:

$ pwd /* Где мы находимся */
/ home/аrnold/work/prenhall/progex
$ code/ch08/ch08-nftw code /* Обойти каталог 'code' */
code (directory) /* Каталог верхнего уровня */
  ch02 (directory) /* Подкаталоги с отступом на один уровень */
    ch02-printenv.c (file) /* Файлы в подкаталоге с отступом
                              на два уровня */
  ch03 (directory)
    ch03-memaddr.c (file)
  ch04 (directory)
    ch04-holes.c (file)
    ch04-cat.с (file)
    ch04-maxfds.c (file)
    v7cat.c (file)
...

Вот сама программа:

1  /* ch08-nftw.c --- демонстрирует nftw() */
2
3  #define _XOPEN_SOURCE 1 /* Требуется под GLIBC для nftw() */
4  #define _XOPEN_SOURCE_EXTENDED 1 /* To же */
5
6  #include <stdio.h>
7  #include <errno.h>
8  #include <getopt.h>
9  #include <ftw.h> /* получает для нас <sys/types.h> и <sys/stat.h> */
10 #include <limits.h> /* для PATH_MAX */
11 #include <unistd.h> /* для объявлений getdtablesize(), getcwd() */
12
13 #define SPARE_FDS 5 /* fds для использования другими функциями, см. текст */
14
15 extern int process(const char *file, const struct stat *sb,
16  int flag, struct FTW *s);
17
18 /* usage --- print message and die */
19
20 void usage(const char *name)
21 {
22  fprintf(stderr, "usage: %s (-c) directory ...n", name);
23  exit(1);
24 }
25
26 /* main --- вызвать nftw() для каждого аргумента командной строки */
27
28 int main(int argc, char **argv)
29 {
30  int i, c, nfds;
31  int errors = 0;
32  int flags = FTW_PHYS;
33  char start[PATH_MAX], finish[PATH_MAX];
34
35  while ((c = getopt(argc, argv, "с")) != -1) {
36   switch (c) {
37   case 'c':
38    flags |= FTW_CHDIR;
39    break;
40   default:
41    usage(argv[0]);
42    break;
43   }
44  }
45
46  if (optind == argc)
47   usage(argv[0]);
48
49  getcwd(start, sizeof start);
50
51  nfds = getdtablesize() - SPARE_FDS; /* оставить несколько запасных дескрипторов */
52  for (i = optind; i < argc; i++) {
53   if (nftw(argv[i], process, nfds, flags) != 0) {
54    fprintf(stderr, "%s: %s: stopped earlyn",
55     argv[0], argv[i]);
56    errors++;
57   }
58  }
59
60  if ((flags & FTW_CHDIR) != 0) {
61   getcwd(finish, sizeof finish);
62   printf("Starting dir: %sn", start);
63   printf("Finishing dir: %sn", finish);
64  }
65

66  return (errors != 0);
67 }

Строки 3–11 включают заголовочные файлы. По крайней мере в GLIBC 2.3.2 перед включением любого заголовочного файла необходимы #define для _XOPEN_SOURCE и _XOPEN_SOURCE_EXTENDED. Они дают возможность получить объявления и значения флагов, которые nftw() предоставляет свыше предоставляемых ftw(). Это специфично для GLIBC. Потребность в этом в конечном счете исчезнет, когда GLIBC станет полностью совместимой со стандартом POSIX 2001.

Строки 35–44 обрабатывают опции. Опция добавляет к флагам nftw() FTW_CHDIR. Это эксперимент с целью увидеть, сможете ли вы оказаться где-то в другом месте от того, где начинали. Кажется, это возможно, если nftw() завершается неудачей, в противном случае вы заканчиваете там же, где начинали. (POSIX не документирует это явным образом, но целью, похоже, было действительно заканчивать там же, где начинали. Стандарт не говорит, что функция обратного вызова не должна менять текущий каталог.)

Строка 49 сохраняет начальный каталог для дальнейшего использования, пользуясь getcwd().

Строка 51 вычисляет число дескрипторов, которые может использовать nftw(). Мы не хотим, чтобы она использовала все доступные дескрипторы файлов, если функция обратного вызова также хочет открывать файлы. В вычислении используется getdtablesize() (см. раздел 4.4.1 «Понятие о дескрипторах файлов») для получения максимально возможного числа и вычета из него SPARE_FDS, который был вычислен ранее в строке 13.

Эта процедура служит основанием для больших объяснений. В обычном случае по крайней мере три дескриптора уже используются для стандартного ввода, стандартного вывода и стандартной ошибки. nftw() нужно некоторое количество дескрипторов файлов для открытия и чтения каталогов; внутри себя opendir() использует open() при открытии каталогов для чтения. Если функции обратного вызова также нужно открывать файлы, мы должны предотвратить израсходование функцией nftw() всех доступных дескрипторов файлов для открывания каталогов. Мы делаем это, вычитая некоторое число из максимально допустимого. Для данного примера мы выбрали пять, но если функции обратного вызова нужно открывать файлы, должно использоваться большее число, (nftw() знает, как восстановиться при израсходовании дескрипторов файлов; мы не должны беспокоиться о таком случае.)

Строки 52–58 являются главным циклом над нашими аргументами; строки 53–57 проверяют ошибки; когда они появляются, код выводит диагностическое сообщение и увеличивает значение переменной errors.

Строки 60–64 являются частью эксперимента с FTW_CHDIR, выводящего начальный и конечный каталоги, если было использовано .

По-настоящему интересной функцией является process(); это функция обратного вызова, которая обрабатывает каждый файл. Она использует базовый шаблон для функции обратного вызова nftw(), который является оператором switch для значения flag.

69  /* process --- выводит каждый файл на нужном уровне */
70
71  int process(const char "file, const struct stat *sb,
72  int flag, struct FTW *s)
73  {
74   int retval = 0;
75   const char *name = file + s->base;
76
77   printf("%*s", s->level * 4, ""); /* сделать отступ */
78
79   switch (flag) {
80   case FTW_F:
81    printf("%s (file)n", name);
82    break;
83   case FTW_D:
84    printf("%s (directory)n", name);
85    break;
86   case FTW_DNR:
87    printf("%s (unreadable directory)n", name);
88    break;
89   case FTW_SL:
90    printf("%s (symbolic link)n", name);
91    break;
92   case FTW_NS:
93    printf("%s (stat failed): %sn", name, strerror(errno));
94    break;
95   case FTW_DP:
96   case FTW_SLN:
97    printf("%s: FTW_DP or FTW_SLN: can't happen'n", name);
98    retval = 1;
99    break;
100  default:
101   printf("%s: unknown flag %d: can't happen'n", name, flag);
102   retval = 1;
103   break;
104  }
105
106  return retval;
107 }

Строка 75 использует 'file + s->base' для получения имени из полного пути. Это значение указателя сохраняется в переменной name для повторного использования в функции.

Строка 77 делает отступ нужного размера, используя красивый трюк. Используя %*s, printf() получает от первого аргумента ширину поля. Это вычисляется динамически как 'level * 4'. Строка, которая должна быть выведена — «», пустая строка. Конечным результатом является то, что printf() создает для нас отступ нужного размера без необходимости запуска цикла.

Строки 79–104 являются оператором switch. В данном случае он не делает ничего весьма интересного, кроме вывода имени файла и его типа (файл, каталог и т.д.)

Хотя эта программа не использует struct stat, должно быть ясно, что вы могли бы сделать в функции обратного вызова все, что хотите.

ЗАМЕЧАНИЕ. Джим Мейеринг (Jim Meyering), сопроводитель GNU Coreutils, замечает, что дизайн nftw() несовершенен из-за ее рекурсивной природы. (Она рекурсивно вызывает себя при обработке подкаталогов.) Если иерархия каталогов становится действительно глубокой, в диапазоне уровней 20 000–40 000 (!), nftw() может выйти за пределы размера стека, уничтожив программу. Есть также и другие проблемы, связанные с дизайном nftw(). Версия GNU Coreutils после 5.0 исправляет это путем использования набора процедур fts() (см. fts(3)).

Оглавление книги


Генерация: 1.470. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз