Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 13 (всего у книги 55 страниц)
Хотя мы только что сказали, что вам следует использовать лишь члены d_ino
и d_name
структуры struct dirent
, стоит знать о члене d_type
в struct dirent
BSD и Linux. Это значение unsigned char
, в котором хранится тип файла, имя которого находится в элементе каталога:
struct dirent {
...
ino_t d_ino; /* Как ранее */
char d_name[...]; /* Как ранее */
unsigned char d_type; /* Linux и современная BSD */
...
};
d_type
может принимать любые значения, описанные в табл. 5.1.
Таблица 5.1. Значения для d_type
DT_BLK | Файл блочного устройства |
DT_CHR | Файл символьного устройства |
DT_DIR | Каталог |
DT_FIFO | FIFO или именованный канал |
DT_LNK | Символическая ссылка |
DT_REG | Обычный файл |
DT_SOCK | Сокет |
DT_UNKNOWN | Неизвестный тип файла |
DT_WHT | Нет элемента (только системы BSD) |
Знание типа файла просто путем чтения элемента каталога очень удобно; это может сэкономить на возможно дорогом системном вызове stat()
. (Вызов stat()
вскоре будет описан в разделе 5.4.2 «Получение информации о файле».)
Иногда полезно отметить текущее положение в каталоге для того, чтобы иметь возможность позже к нему вернуться. Например, вы пишете код, обходящий дерево каталога, и хотите рекурсивно входить в каждый подкаталог, когда его проходите. (Как отличить файлы от каталогов обсуждается в следующем разделе). По этой причине первоначальный интерфейс BSD включал две дополнительные процедуры:
#include
/* Предупреждение: POSIX XSI использует для обеих функций long, а не off_t */
off_t telldir(DIR *dir); /* Вернуть текущее положение */
void seekdir(DIR *dir, off_t offset); /* Переместиться в данное положение */
Эти процедуры подобны функциям ftell()
и fseek()
и
. Они возвращают текущее положение в каталоге и устанавливают текущее положение в ранее полученное значение соответственно.
Эти процедуры включены в часть XSI стандарта POSIX, поскольку они имеют смысл лишь для каталогов, которые реализованы с линейным хранением элементов каталога
Помимо предположений, сделанных относительно лежащей в основе структуры каталога, эти процедуры рискованнее использовать, чем простые процедуры чтения каталога. Это связано с тем, что содержание каталога может изменяться динамически: когда файлы добавляются или удаляются из каталога, операционная система приводит в порядок содержание каталога. Поскольку элементы каталога имеют различный размер, может оказаться, что сохраненное ранее абсолютное смещение больше не представляет начало элемента каталога! Поэтому мы не рекомендуем вам использовать эти функции, если вам они действительно не нужны[56]56
Стоит внимательно подумать прежде чем использовать эти функции – Примеч. науч. ред.
[Закрыть].
Чтение каталога для получения имен файлов лишь половина дела. Получив имя файла, нужно знать, как получить остальную информацию, связанную с файлом, такую, как тип файла, права доступа к нему, владельца и т.д.
Linux (и Unix) поддерживает следующие различные типы файлов:
Обычные файлы
Как предполагает имя, данный тип используется для данных, исполняемых программ и всего прочего, что вам может понравиться. В листинге 'ls -l
' они обозначаются в виде первого символа '-
' поля прав доступа (режима).
Каталоги
Специальные файлы для связывания имен файлов с индексами. В листинге 'ls -l
' они обозначаются первым символом d
поля прав доступа.
Символические ссылки
Как описано ранее в главе. В листинге 'ls -l
' обозначаются первым символом l
(буква «эль», не цифра 1) поля прав доступа.
Устройства
Файлы, представляющие как физические аппаратные устройства, так и программные псевдоустройства. Есть две разновидности:
Блочные устройства
Устройства, ввод/вывод которых осуществляется порциями некоторого фиксированного размера физической записи, такие, как дисковые и ленточные приводы. Доступ к таким устройствам осуществляется через буферный кэш ядра. В листинге 'ls -l
' они обозначаются первым символом b
поля прав доступа.
Символьные устройства
Известны также как непосредственные (raw) устройства. Первоначально символьными устройствами были те, в которых ввод/вывод осуществлялся по несколько байтов за раз, как в терминалах. Однако, символьное устройство используется также для непосредственного ввода/вывода на блочные устройства, такие, как ленты и диски, минуя буферный кэш[57]57
Linux использует блочные устройства исключительно для дисков. Другие системы используют оба типа – Примеч. автора.
[Закрыть]. В листинге 'ls -l
' они отображаются первым символом с
поля прав доступа.
Именованные каналы (named pipes)
Известны также файлы FIFO («first-in first-out» – «первым вошел, первым обслужен»). Эти специальные файлы действуют подобно конвейерам (pipes); данные, записанные в них одной программой, могут быть прочитаны другой; данные не записываются на диск и не считываются с диска. FIFO создаются с помощью команды mkfifo
; они обсуждаются в разделе 9.3.2 «FIFO». В листинге 'ls -l
' они отображаются первым символом p
поля прав доступа.
Сокеты
Сходные по назначению с именованными каналами[58]58
Именованные каналы и сокеты были разработаны независимо группами Unix System V и BSD соответственно. Когда системы Unix вновь сошлись, обе разновидности файлов стали доступными универсально – Примеч. автора.
[Закрыть], они управляются системными вызовами межпроцессных взаимодействий (IPC) сокетов, и мы не будем в данной книге иметь с ними дело в других отношениях. В листинге 'ls -l
' они отображаются первым символом s
поля прав доступа.
Три системных вызова возвращают информацию о файлах:
#include
#include
#include
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *file_name, struct stat *buf);
Функция stat()
получает имя файла с путем и возвращает сведения о данном файле. Она следует по символическим ссылкам; т. е. при применении к символической ссылке stat()
возвращает информацию об указываемом файле, а не о самой ссылке. В тех случаях, когда вам нужно знать, является ли файл символической ссылкой, используйте вместо нее функцию lstat()
; последняя не следует по символическим ссылкам.
Функция fstat()
получает сведения об уже открытом файле. Это особенно полезно для дескрипторов файлов 0, 1 и 2 (стандартных ввода, вывода и ошибки), которые уже открыты при запуске процесса. Однако, она может использоваться с любым открытым файлом. (Дескриптор открытого файла никогда не будет относиться к символической ссылке; убедитесь, что понимаете, почему.)
Значение, переданное в качестве второго параметра, должно быть адресом struct stat
, объявленной в
. Как в случае с struct dirent
, struct stat
содержит по крайней мере следующие члены:
struct stat {
...
dev_t st_dev; /* устройство */
ino_t st_ino; /* индекс */
mode_t st_mode; /* тип и защита */
nlink_t st_nlink; /* число прямых (hard) ссылок */
uid_t st_uid; /* ID владельца */
gid_t st_gid; /* ID группы */
dev_t st_rdev; /* тип устройства (блочное или символьное) */
off_t st_size; /* общий размер в байтах */
blksize_t st_blksize; /* размер блока для ввода/вывода файл, с-мы */
blkcnt_t st_blocks; /* число выделенных блоков */
time_t st_atime; /* время последнего доступа */
time_t st_mtime; /* время последнего изменения */
time_t st_ctime; /* время последнего изменения индекса */
...
};
(Размещение на разных системах может быть разное.) Эта структура использует ряд определенных через typedef
типов. Хотя они все (обычно) целые типы, использование специально определенных типов позволяет использовать для них различные размеры на разных системах. Это сохраняет переносимость кода пользователя, который их использует. Вот более полное описание каждого поля.
st_dev
Устройство для монтируемой файловой системы. У каждой монтируемой файловой системы уникальное значение st_dev
.
st_ino
Номер индекса файла в пределах файловой системы. Пара (st_dev
, st_ino
) уникально идентифицирует файл.
st_mode
Тип файла и права доступа к нему, закодированные в одном поле. Вскоре мы рассмотрим, как извлечь эту информацию.
st_nlink
Число прямых ссылок на файл (счетчик ссылок). Может равняться нулю, если файл был удален после открытия.
st_uid
UID файла (номер владельца).
st_gid
GID файла (номер группы).
st_rdev
Тип устройства, если файл является блочным или символьным устройством. st_rdev
содержит закодированную информацию об устройстве. Вскоре мы увидим, как извлечь эту информацию. Это поле не имеет смысла, если файл не является блочным или символьным устройством.
st_size
Логический размер файла. Как упоминалось в разделе 4.5 «Произвольный доступ: перемещение внутри файла», файл может содержать в себе дыры, в этом случае размер может не отражать истинного значения занимаемого им места.
st_blksize
«Размер блока» файла. Представляет предпочтительный размер блока данных для ввода/вывода данных в или из файла. Почти всегда превышает размер физического сектора диска. У более старых систем Unix нет этого поля (или поля st_blocks
) в struct stat
. Для файловых систем Linux ext2 и ext3 это значение составляет 4096.
st_blocks
Число «блоков», используемых файлом. В Linux это значение представлено в единицах 512-байтных блоков. На других системах размер блока может быть различным, проверьте свою локальную страницу справки для stat(2). (Это число происходит от константы DEV_BSIZE
в
. Эта константа не стандартизована, но довольно широко используется в системах Unix.)
Число блоков может быть больше, чем 'st_size / 512
'; кроме блоков данных, файловая система может использовать дополнительные блоки для хранения размещений блоков данных. Это особенно необходимо для больших файлов.
st_atime
Время доступа к файлу; т.е. когда в последний раз читались данные файла.
st_mtime
Время модификации файла; т е. когда в последний раз данные файла записывались или урезались.
st_ctime
Время изменения индекса файла. Указывает время последнего изменения служебных данных файла, таких, как права доступа или владелец.
ЗАМЕЧАНИЕ. Поле
st_ctime
не является «временем создания»! В системе Linux или Unix нет такой вещи. Часть более ранней документации называла полеst_ctime
временем создания. Это была вводящая в заблуждение попытка упростить представление служебных данных файла
Тип time_t
, использованный для полей st_atime
, st_mtime
и st_ctime
, представляет дату и время. Эти относящиеся ко времени значения иногда называют временными метками (timestamps). Обсуждение того, как использовать значение time_t
, отложено до раздела 6.1 «Время и даты». Подобным же образом типы uid_t
и gid_t
представляют номера владельца и группы, которые обсуждаются в разделе 6.3 «Имена пользователя и группы». Большинство других типов не представляют широкого интереса.
Ядра Linux 2.6 и более поздние предоставляют в struct stat
три дополнительных поля. Они предусматривают точность файлового времени до наносекунд:
st_atime_nsec
Наносекундная компонента времени доступа к файлу.
st_mtime_nsec
Наносекундная компонента времени изменения файла
st_ctime_nsec
Наносекундная компонента времени изменения служебных данных файла.
Некоторые другие системы также предоставляют такие поля с повышенной точностью времени, но имена соответствующих членов структуры struct stat
не стандартизованы, что затрудняет написание переносимого кода, использующего эти времена. (Связанные с этим расширенные системные вызовы см. в разделе 14.3.2 «Файловое время в микросекундах: utimes()
».)
Вспомните, что в поле st_mode
закодированы как тип файла, так и права доступа к нему.
определяет ряд макросов, которые определяют тип файла. В частности, эти макросы возвращают true
или false
при использовании с полем st_mode
. У каждого описанного ранее типа файла есть свой макрос. Предположим, выполняется следующий код:
struct stat stbuf;
char filename[PATH_МАХ]; /* PATH_MAX из
/* ... поместить имя файла в filename ... */
if (stat(filename, &stbuf) < 0) {
/* обработать ошибку */
}
Когда система заполнила stbuf
, можно вызывать следующие макросы, причем в качестве аргумента передается stbuf.st_mode
:
S_ISREG(stbuf.st_mode)
Возвращает true
, если filename
является обычным файлом.
S_ISDIR(stbuf.st_mode)
Возвращает true
, если filename
является каталогом.
S_ISCHR(stbuf.st_mode)
Возвращает true
, если filename
является символьным устройством. Устройства вскоре будут обсуждены более подробно.
S_ISBLK(stbuf.st_mode)
Возвращает true
, если filename
является блочным устройством.
S_ISFIFO(stbuf.st_mode)
Возвращает true
, если filename
является FIFO.
S_ISLNK(stbuf.st_mode)
Возвращает true
, если filename
является символической ссылкой. (Это может никогда не вернуть true
, если вместо lstat()
использовались stat()
или fstat()
.)
S_ISSOCK(stbuf.st_mode)
Возвращает true
, если filename
является сокетом.
ЗАМЕЧАНИЕ. В GNU/Linux эти макросы возвращают 1 для
true
и 0 дляfalse
. Однако, на других системах возможно, что они будут возвращать дляtrue
вместо 1 произвольное неотрицательное число. (POSIX определяет лишь ненулевое значение в противоположность нулевому). Поэтому всегда следует использовать эти макросы как автономные тесты вместо проверки возвращаемого значения.
/* Корректное использование */
if (S_ISREG(stbuf.st_mode)) ...
/* Heкорректное использование */
if (S_ISREG(stbuf.st_mode) ==1) ...
Наряду с макросами
предоставляет два набора битовых масок. Один набор для проверки прав доступа, а другой – для проверки типа файла. Мы видели маски прав доступа в разделе 4.6 «Создание файлов», когда обсуждали тип mode_t
и значения для open()
и creat()
. Битовые маски, их числовые значения для GNU/Linux и смысл приведены в табл. 5.2.
Таблица 5.2. Битовые маски POSIX для типов файлов и прав доступа в
S_IFMT | 0170000 | Маска для битовых полей типа файла |
S_IFSOCK | 0140000 | Сокет. |
S_IFLNK | 0120000 | Символическая ссылка |
S_IFREG | 0100000 | Обычный файл. |
S_IFBLK | 0060000 | Блочное устройство. |
S_IFDIR | 0040000 | Каталог. |
S_IFCHR | 0020000 | Символьное устройство. |
S_IFIFO | 0010000 | FIFO. |
S_ISUID | 0004000 | Бит setuid. |
S_ISGID | 0002000 | Бит setgid |
S_ISVTX | 0001000 | «Липкий» (sticky) бит. |
S_IRWXU | 0000700 | Маска для прав доступа владельца. |
S_IRUSR | 0000400 | Доступ на чтение для владельца. |
S_IWUSR | 0000200 | Доступ на запись для владельца. |
S_IXUSR | 0000100 | Доступ на исполнение для владельца. |
S_IRWXG | 0000070 | Маска для прав доступа группы. |
S_IRGRP | 0000040 | Доступ на чтение для группы. |
S_IWGRP | 0000020 | Доступ на запись для группы. |
S_IXGRP | 0000010 | Доступ на исполнение для группы. |
S_IRWXO | 0000007 | Маска для прав доступа остальных. |
S_IROTH | 0000004 | Доступ на чтение для остальных. |
S_IWOTH | 0000002 | Доступ на запись для остальных. |
S_IXOTH | 0000001 | Доступ на исполнение для остальных. |
Некоторые из этих масок служат цели изолирования различных наборов битов, закодированных в поле st_mode
:
• S_IFMT
представляет биты 12–15, которыми закодированы различные типы файлов.
• S_IRWXU
представляет биты 6–8, являющиеся правами доступа владельца (на чтение, запись, исполнение для User).
• S_IRWXG
представляет биты 3–5, являющиеся правами доступа группы (на чтение, запись, исполнение для Group).
• S_IRWXO
представляет биты 0–2, являющиеся правами доступа для «остальных» (на чтение, запись, исполнение для Other).
Биты прав доступа и типа файла графически изображены на рис. 5.3.
Рис. 5.3. Биты прав доступа и типа файлов
Маски типов файлов стандартизованы главным образом для совместимости со старым кодом; они не должны использоваться непосредственно, поскольку такой код менее читаем, чем соответствующие макросы. Случается, что макрос реализован с использованием масок: довольно логично, но это не подходит для кода уровня пользователя.
Стандарт POSIX явным образом констатирует; что в будущем не будут стандартизированы новые битовые маски и что тесты для любых дополнительных разновидностей типов файлов, которые могут быть добавлены, будут доступны лишь в виде макросов S_ISxxx()
.
Стандарт POSIX не определяет значение типа dev_t
, поскольку предполагалось его использование на не-Unix системах также, как на Unix-системах. Однако стоит знать, что находится в dev_t
.
Когда истинно S_ISBLK(sbuf.st_mode)
или S_ISCHR(sbuf.st_mode)
, сведения об устройстве находятся в поле sbuf.st_rdev
. В противном случае это поле не содержит никакой полезной информации.
Традиционно файлы устройств Unix кодируют старший и младший номера устройства в значении dev_t
. По старшему номеру различают тип устройства, такой, как «дисковый привод» или «ленточный привод». Старшие номера различают также разные типы устройств, такие, как диск SCSI в противоположность диску IDE. Младшие номера различают устройства данного типа, например, первый диск или второй. Вы можете увидеть эти значения с помощью 'ls -l
':
$ ls -l /dev/hda /dev/hda? /* Показать номера для первого жесткого диска */
brw-rw– 1 root disk 3, 0 Aug 31 2002 /dev/hda
brw-rw– 1 root disk 3, 1 Aug 31 2002 /dev/hda1
brw-rw– 1 root disk 3, 2 Aug 31 2002 /dev/hda2
brw-rw– 1 root disk 3, 3 Aug 31 2002 /dev/hda3
brw-rw– 1 root disk 3, 4 Aug 31 2002 /dev/hda4
brw-rw– 1 root disk 3, 5 Aug 31 2002 /dev/hda5
brw-rw– 1 root disk 3, 6 Aug 31 2002 /dev/hda6
brw-rw– 1 root disk 3, 7 Aug 31 2002 /dev/hda7
brw-rw– 1 root disk 3, 8 Aug 31 2002 /dev/hda8
brw-rw– 1 root disk 3, 9 Aug 31 2002 /dev/hda9
$ ls -l /dev/null /* Показать сведения также для /dev/null */
crw-rw-rw– 1 root root 1, 3 Aug 31 2002 /dev/null
Вместо размера файла ls
отображает старший и младший номера. В случае жесткого диска /dev/hda
представляет диск в целом, /dev/hda1
, /dev/hda2
и т.д. представляют разделы внутри диска. У них у всех общий старший номер устройства (3), но различные младшие номера устройств.
Обратите внимание, что дисковые устройства являются блочными устройствами, тогда как /dev/null
является символьным устройством. Блочные и символьные устройства являются отдельными сущностями; даже если символьное устройство и блочное устройство имеют один и тот же старший номер устройства, они необязательно связаны
Старший и младший номера устройства можно извлечь из значения dev_t
с помощью функций major()
и minor()
, определенных в
:
#include
#include
int major(dev_t dev); /* Старший номер устройства */
int minor(dev_t dev); /* Младший номер устройства */
dev_t makedev(int major, int minor); /* Создать значение dev_t */
(Некоторые системы реализуют их в виде макросов.)
Функция makedev()
идет другим путем; она принимает отдельные значения старшего и младшего номеров и кодирует их в значении dev_t
. В других отношениях ее использование выходит за рамки данной книги; патологически любопытные должны посмотреть mknod(2).
Следующая программа, ch05-devnum.c
, показывает, как использовать системный вызов stat()
, макросы проверки типа файла и, наконец, макросы major()
и minor()
.
/* ch05-devnum.c – Демонстрация stat(), major(), minor(). */
#include
#include
#include
#include
#include
int main(int argc, char **argv) {
struct stat sbuf;
char *devtype;
if (argc != 2) {
fprintf(stderr, "usage: %s pathn", argv[0]);
exit(1);
}
if (stat(argv[1], &sbuf) < 0) {
fprintf(stderr, "%s: stat: %sn", argv[1], strerror(errno));
exit(1);
}
if (S_ISCHR(sbuf.st_mode))
devtype = "char";
else if (S_ISBLK(sbuf.st_mode))
devtype = "block";
else {
fprintf(stderr, "%s is not a block or character devicen",
argv[1]);
exit(1);
}
printf("%s: major: %d, minor: %dn", devtype,
major(sbuf.st_rdev), minor(sbuf.st_rdev));
exit(0);
}
Вот что происходит при запуске программы:
$ ch05-devnum /tmp /* Попробовать не устройство */
/tmp is not a block or character device
$ ch05-devnum /dev/null /* Символьное устройство */
char: major: 1, minor: 3
$ ch05-devnum /dev/hda2 /* Блочное устройство */
block: major: 3, minor: 2
К счастью, вывод согласуется с выводом ls
, давая нам уверенность[59]59
Технический термин warm fuzzy – Примеч. автора.
[Закрыть], что мы в самом деле написали правильный код.
Воспроизведение вывода ls замечательно и хорошо, но действительно ли это полезно? Ответ – да. Любое приложение, работающее с иерархиями файлов, должно быть способно различать различные типы файлов. Подумайте об архиваторе, таком как tar
или cpio
. Было бы пагубно, если бы такая программа рассматривала файл дискового устройства как обычный файл, пытаясь прочесть его и сохранить его содержимое в архиве! Или подумайте о find
, которая может выполнять произвольные действия, основываясь на типе и других атрибутах файлов, с которыми она сталкивается, (find
является сложной программой; посмотрите find(1), если вы с ней не знакомы.) Или даже нечто простое, как пакет, оценивающий свободное дисковое пространство, тоже должно отличать обычные файлы от всего остального.