Б.1.5. Перемещение по файлу
В файловом дескрипторе запоминается текущая позиция в файле. При чтении или записи данных указатель текущей позиции перемещается на то количество байтов, которое было прочитано или записано. Но иногда нужно осуществлять простое перемещение по файлу (позиционирование). Например, может потребоваться вернуться в начало файла и прочитать его заново, не открывая повторно.
Позиционирование указателя текущей позиции файла осуществляет функция lseek(). Она принимает дескриптор файла и два дополнительных аргумента, определяющих новую позицию указателя.
■ Если третий аргумент равен SEEK_SET, функция lseek() интерпретирует второй аргумент как смещение (в байтах) от начала файла.
■ Если третий аргумент равен SEEK_CUR, функция lseek() интерпретирует второй аргумент как смещение (положительное или отрицательное) от текущей позиции.
■ Если третий аргумент равен SEEK_END, функция lseek() интерпретирует второй аргумент как смещение (в байтах) от конца файла.
Функция lseek() возвращает смещение новой позиции от начала файла. Тип этого значения — off_t. В случае ошибки возвращается -1. Функция неприменима к файлам некоторых типов, например к сокетам.
Если требуется узнать текущую позицию файла, задайте смещение 0:
off_t position = lseek(free_descriptor, 0, SEEK_CUR);
ОС Linux позволяет перемещать указатель текущей позиции за пределы файла. Обычно, если текущая позиция находится за концом файла и выполняется операция записи, операционная система автоматически увеличивает файл, чтобы вместить в него новые данные. "Промежуток" между старым признаком конца файла и указателем текущей позиции не записывается на диск. Linux лишь помечает его длину. Если впоследствии попытаться прочесть файл, окажется, что данный промежуток заполнен нулевыми байтами.
Благодаря данной особенности функции lseek() можно создавать файлы огромного размера, практически не занимающие места на диске. Это продемонстрировано в листинге Б.5. В качестве аргументов командной строки программа принимает имя файла и требуемый размер в мегабайтах. Программа создает файл, перемещается с помощью функции lseek() на нужное расстояние и записывает нулевой байт, после чего закрывает файл.
Листинг Б.5. (
lseek-huge.c) Создание огромных файлов с помощью функции lseek()
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main (int argc, char* argv[]) {
int zero = 0;
const int megabyte = 1024 * 1024;
char* filename = argv[1];
size_t length = (size_t)atoi(argv[2]) * megabyte;
/* Создание нового файла. */
int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
/* Перемещение в точку, где должен быть записан последний байт
файла. */
lseek(fd, length - 1, SEEK_SET);
/* Запись нулевого байта. */
write(fd, &zero, 1);
/* Конец работы. */
close(fd);
return 0;
}
Давайте теперь создадим файл размером 1 Гбайт. Обратите внимание на объем свободного места на диске до и после выполнения программы.
% df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/hda5 2.9G 2.1G 655M 76% /
% ./lseek-huge bigfile 1024 % ls -l bigfile
-rw-r----- 1 samuel samuel 1073741824 Feb 5 16:29 bigfile
% df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/hda5 2.9G 2.1G 655M 76% /
Как видите, файл практически не занимает место на диске, несмотря на свой огромный размер. Но если открыть его и попытаться прочитать данные, окажется, что в нем находится 1 Гбайт нулей. Давайте, к примеру, проверим это с помощью программы hexdump:
% ./hexdump bigfile / head -10
0x000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x000030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x000040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x000050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
Чтобы не наблюдать, как по экрану проносятся 230 нулей, нажмите <Ctrl+C>.
"Волшебные промежутки" в файлах являются особенностью файловых систем типа ext2, обычно создаваемых на жестких дисках Linux. Если попытаться с помощью программы lseek-huge создать файл в файловой системе типа fat или vfat, то он займет весь указанный объем диска.
ОС Linux не позволяет функции lseek() ставить указатель текущей позиции перед началом файла.
Функция read() позволяет прочесть только содержимое файла. Но как насчет остальной информации? Например, команда ls -l сообщает такие сведения о файлах в текущем каталоге, как размер, время последнего обновления, права доступа, владелец и пр. Аналогичную информацию об отдельном файле можно получить с помощью функции stat(). Ей необходимо передать путевое имя файла и указатель на структуру типа stat. В случае успешного завершения функция возвращает 0 и заполняет поля структуры данными о файле, иначе возвращается -1.
Перечислим наиболее полезные поля структуры stat.
■ В поле st_mode содержится код доступа к файлу. О правах доступа к файлам рассказывалось в разделе 10.3. "Права доступа к файлам". В старшем бите поля закодирован тип файла. Об этом пойдет речь ниже.
■ В полях st_uid и st_gid содержатся идентификаторы соответственно пользователя и группы, которым принадлежит файл. Назначение идентификатора описывалось в разделе 10.1, "Пользователи и группы".
■ В поле st_size хранится размер файла в байтах.
■ В поле st_atime записано время последнего обращения к файлу (для чтения или записи).
■ В поле st_mtime записано время последней модификации файла.
Следующие макросы проверяют поле st_mode, чтобы определить, для файла какого типа была вызвана функция stat. Макросы возвращают ненулевое значение, если их догадка о типе файла подтвердилась.
■ S_ISBLK(код доступа) — блочное устройство:
■ S_ISCHR(код доступа) — символьное устройство;
■ S_ISDIR(код доступа) — каталог;
■ S_ISFIFO(код доступа) — FIFO-файл (именованный канал):
■ S_ISLNK(код доступа) — символическая ссылка.
■ S_ISREG(код доступа) — обычный файл;
■ S_ISSOCK(код доступа) — сокет.
В поле st_dev содержатся старший и младший номера аппаратного устройства, в котором расположен файл (о номерах устройств рассказывалось в главе 6, "Устройства"). Старший номер находится в старшем байте поля, а младший — в младшем. В поле st_infо содержится номер индексного дескриптора файла, определяющий местоположение файла в файловой системе.
Если вызвать функцию stat() для символической ссылки, функция проследит, куда указывает ссылка, и вернет информацию о том файле, а не о самой ссылке. Таким образом, в случае функции stat() макрос S_ISLNK() всегда будет возвращать значение 0. Есть другая функция, lstat(), которая не пытается отслеживать символические ссылки. Во всем остальном она эквивалентна функции stat(). Если вызвать функцию stat() для поврежденной ссылки (которая указывает на несуществующий или недоступный файл), возникнет ошибка, тогда как функция lstat() в подобной ситуации выполнится успешно.
Если файл уже открыт для чтения или записи, лучше пользоваться функцией fstat(). В качестве первого аргумента она принимает не путевое имя, а дескриптор.
В листинге Б.6 показана функция которая создает буфер достаточного размера и загружает в него содержимое указанного файла. Размер файла определяется с помощью функции fstat(). Она же позволяет проверить, соответствует ли заданное имя обычному файлу.
Листинг Б.6. (
read-file.c) Загрузка файла в буфер
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* Загрузка содержимого файла FILENAME в память.
Размер буфера записывается в аргумент LENGTH.
Создаваемый буфер должен удаляться в вызывающей функции.
Если аргумент FILENAME не соответствует обычному файлу,
возвращается NULL. */
char* read_file(const char* filename, size_t* length) {
int fd;
struct stat file_info;
char* buffer;
/* Открытие файла. */
fd = open(filename, O_RDONLY);
/* Получение информации о файле. */