Книга: Разработка приложений в среде Linux. Второе издание
14.8. Уведомление о смене каталога
14.8. Уведомление о смене каталога
Иногда приложения желают получать уведомления об изменении оглавления каталога. Например, диспетчеры файлов могут выводить оглавление каталога в окне и обновлять это окно каждый раз при изменении каталога другими программами. В то время как приложение регулярно перепроверяет каталог, Linux может послать программе сигнал о модификации каталога, позволяя своевременные обновления без накладных расходов и задержек на страничный обмен.
Системный вызов fcntl()
используется для регистрации уведомлений об обновлениях каталога. В главе 11 уже говорилось о том, что этот системный вызов принимает три аргумента. Первый аргумент — это интересующий файловый дескриптор, второй — это команда, которую необходимо выполнить fcntl()
, а последний — это целое число, специфическое для этой команды. Для уведомлений каталогов первый аргумент является файловым дескриптором, относящимся к интересующему каталогу. Это единственный случай, при котором каталог следует открывать с помощью нормального системного вызова open()
вместо opendir()
. Командой регистрации уведомлений является F_NOTIFY
, а последний аргумент определяет, какие типы событий вызывают отправку сигнала. Это должен быть один или несколько перечисленных ниже флагов, объединенных по логическому "ИЛИ".
DN_ACCESS |
Файл в каталоге, который читается. |
DN_ATTRIB |
Права владения или доступа к файлу в каталоге были изменены. |
DN_CREATE |
В каталоге создан новый файл (включая новые жесткие ссылки на уже существующие файлы). |
DN_DELETE |
Файл удален из каталога. |
DN_MODIFY |
Файл в каталоге был модифицирован (тип модификации — усечение). |
DN_RENAME |
Файл в каталоге был переименован. |
Для отмены уведомления о событии вызовите fcntl()
с командой F_NOTIFY
и последним аргументом, равным нулю.
Обычно уведомление каталога автоматически отменяется после передачи одного сигнала. Для эффективного уведомления каталога окончательный аргумент для fcntl()
должен быть объединен операцией "ИЛИ" с DN_MULTISHOT
, что вызывает отправку сигналов для всех подходящих событий до отмены уведомления.
По умолчанию для уведомления каталога передается SIGIO
. Если приложение желает использовать для этого другой сигнал (например, для разных каталогов могут понадобиться разные сигналы), можно применить команду F_SETSIG
в fcntl()
, а в качестве последнего аргумента определить нужный сигнал. Если используется F_SETSIG
(даже если установлен сигнал SIGIO
), ядро также помещает файловый дескриптор на каталог в элементе si_fd
аргумента обработчика сигналов siginfo_t
[103], позволяя приложению узнать, какие из контролируемых каталогов обновились[104].
Если контролируется несколько каталогов и для всех каталогов выбран один сигнал, крайне необходимо использовать сигнал реального времени, чтобы убедиться, что ни одно из событий не затерялось.
Ниже приведена программа, использующая уведомление о смене каталога для вывода сообщений об удалении либо добавлении файлов в любые контролируемые ею каталоги (их количество указывается в командной строке). Она отказывается принять SIGRTMIN
при смене каталога и использует si_fd
, чтобы обнаружить, какой именно каталог был изменен. С целью предотвращения условий состязаний программа использует сигналы с очередизацией и блокирование сигналов. Сигнал может быть доставлен только один раз — при вызове sigsuspend()
в строке 203. Это обеспечивает повторное сканирование каталога в случае внесения изменений в каталог во время его сканирования; иначе эти изменения останутся незамеченными. Использование сигналов с очередизацией разрешает любые изменения каталога во время работы программы; эти сигналы доставляется при каждом новом вызове sigsuspend()
, гарантируя, что ничего не пропущено.
1: /* dirchange.с */
2:
3: #define _GNU_SOURCE
4: #include <dirent.h>
5: #include <errno.h>
6: #include <fcntl.h>
7: #include <signal.h>
8: #include <stdio.h>
9: #include <stdlib.h>
10: #include <string.h>
11: #include <unistd.h>
12:
13: /* Для сохранения имен файлов из каталога используется связный
14: список. Поле exists служит для хранения служебной информации
15: при проверке изменений. */
16: struct fileInfo {
17: char * name;
18: struct fileInfo * next;
19: int exists;
20: };
21:
22: /* Это глобальный массив. Он отображает файловые дескрипторы на пути
23: каталогов, сохраняет список файлов в каталоге и предоставляет
24: обработчику сигналов место для отображения того факта, что каталог
25: должен сканироваться повторно. Последний элемент имеет path,
26: равный NULL, обозначающий конец массива. */
27:
28: struct directoryInfo {
29: char * path;
30: int fd;
31: int changed;
32: struct fileInfo * contents;
33: } * directoryList;
34:
35: /* Это никогда не возвращает пустой список; любой каталог содержит,
36: по крайней мере, "." и ".." */
37: int buildDirectoryList(char * path, struct fileInfo ** listPtr) {
38: DIR * dir;
39: struct dirent * ent;
40: struct fileInfo * list = NULL;
41:
42: if (!(dir = opendir(path))) {
43: perror("opendir");
44: return 1;
45: }
46:
47: while ((ent = readdir(dir))) {
48: if (!list) {
49: list = malloc(sizeof(*list));
50: list->next = NULL;
51: *listPtr = list;
52: } else {
53: list->next = malloc(sizeof(*list));
54: list = list->next;
55: }
56:
57: list->name = strdup(ent->d_name);
58: }
59:
60: if (errno) {
61: perror("readdir");
62: closedir(dir);
63: return 1;
64: }
65:
66: closedir(dir);
67:
68: return 0;
69: }
70:
71: /* Сканирует путь каталога в поисках изменений предыдущего
72: содержимого, как указано *listPtr. Связанный список
73: обновляется новым содержимым, и выводятся сообщения,
74: описывающие произошедшие изменения. */
75: int updateDirectoryList(char * path, struct fileInfo ** listPtr) {
76: DIR * dir;
77: struct dirent * ent;
78: struct fileInfo * list = *listPtr;
79: struct fileInfo * file, * prev;
80:
81: if (!(dir = opendir(path))) {
82: perror("opendir");
83: return 1;
84: }
85:
86: for (file = list; file; file = file->next)
87: file->exists = 0;
88:
89: while ((ent = readdir(dir))) {
90: file = list;
91: while (file && strcmp(file->name, ent->d_name))
92: file = file->next;
93:
94: if (!file) {
95: /* новый файл, добавить его имя в список */
96: printf("%s создан в %sn", ent->d_name, path);
97: file = malloc(sizeof(*file));
98: file->name = strdup(ent->d_name);
99: file->next = list;
100: file->exists = 1;
101: list = file;
102: } else {
103: file->exists = 1;
104: }
105: }
106:
107: closedir(dir);
108:
109: file = list;
110: prev = NULL;
111: while (file) {
112: if (!file->exists) {
113: printf("%s удален из %sn", file->name, path);
114: free(file->name);
115:
116: if (!prev) {
117: /* удалить головной узел */
118: list = file->next;
119: free(file);
120: file = list;
121: } else {
122: prev->next = file->next;
123: free(file);
124: file = prev->next;
125: }
126: } else {
127: prev = file;
128: file = file->next;
129: }
130: }
131:
132: *listPtr = list;
133:
134: return 0;
135: }
136:
137: void handler(int sig, siginfo_t * siginfo, void * context) {
138: int i;
139:
140: for (i = 0; directoryList[i].path; i++) {
141: if (directoryList[i].fd == siginfo->si_fd) {
142: directoryList[i].changed = 1;
143: return;
144: }
145: }
146: }
147:
148: int main(int argc, char ** argv) {
149: struct sigaction act;
150: sigset_t mask, sigio;
151: int i;
152:
153: /* Блокировать SIGRTMIN. Мы не хотим получать его нигде,
154: кроме как внутри системного вызова sigsuspend(). */
155: sigemptyset(&sigio);
156: sigaddset(&sigio, SIGRTMIN);
157: sigprocmask(SIG_BLOCK, &sigio, &mask);
158:
159: act.sa_sigaction = handler;
160: act.sa_flags = SA_SIGINFO;
161: sigemptyset(&act.sa_mask);
162: sigaction(SIGRTMIN, &act, NULL);
163:
164: if (!argv[1]) {
165: /* ни одного аргумента не передано, привести argc/argv
166: к виду ".", как будто передается единственный аргумент */
167: argv[1] = ".";
168: argc++;
169: }
170:
171: /* каждый аргумент представляет собой отслеживаемый каталог */
172: directoryList = malloc(sizeof(*directoryList) * argc);
173: directoryList[argc - 1].path = NULL;
174:
175: for (i = 0; i < (argc - 1); i++) {
176: directoryList[i].path = argv[i + 1];
177: if ((directoryList[i].fd =
178: open(directoryList[i].path, O_RDONLY)) < 0) {
179: fprintf(stderr, "ошибка при открытии %s: %sn",
180: directoryList[i].path, strerror(errno));
181: return 1;
182: }
183:
184: /* Отслеживание каталога перед первым сканированием;
185: это гарантирует, что мы захватим файлы, созданные кем-то
186: во время сканирования каталога. Если кто-то изменит его,
187: будет сгенерирован сигнал (и заблокирован, пока
188: мы не будем готовы принять его) */
189: if (fcntl(directoryList[i].fd, F_NOTIFY, DN_DELETE |
190: DN_CREATE | DN_RENAME | DN_MULTISHOT) ) {
191: perror("fcntl F_NOTIFY");
192: return 1;
193: }
194:
195: fcntl(directoryList[i].fd, F_SETSIG, SIGRTMIN);
196:
197: if (build DirectoryList(directoryList[i].path,
198: &directoryList[i].contents))
199: return 1;
200: }
201:
202: while (1) {
203: sigsuspend(&mask);
204:
205: for (i = 0; directoryList[i].path; i++)
206: if (directoryList[i].changed)
207: if (updateDirectoryList(directoryList[i].path,
208: &directoryList[i].contents))
209: return 1;
210: }
211:
212: return 0;
213: }
- 14.1. Текущий рабочий каталог
- 14.2. Смена корневого каталога
- 14.3. Создание и удаление каталогов
- 14.4. Чтение содержимого каталога
- 14.5. Универсализация файловых имен
- 14.6. Добавление к ladsh возможностей работы с каталогами и универсализацией
- 14.7. Обход деревьев файловых систем
- 14.8. Уведомление о смене каталога
- Глава 14 Операции с каталогами
- Можно ли указать использование по умолчанию вместо C:Program Files другого каталога для установки программ?
- У файла и каталога есть атрибуты (например: Скрытый, Только чтение). Как ими управлять из командной строки?
- Удаление ключа из вашего каталога ключей.
- Концепции активного каталога
- 8.4.1. Смена каталога: chdir() и fchdir()
- Архитектура активного каталога
- 5.1. Просмотр содержимого каталога
- 5.1.2. Содержимое каталога
- 6.2.1.2. Пример: сортировка содержимого каталога
- 8.4.2. Получение текущего каталога: getcwd()
- 8.6. Изменение корневого каталога: chroot()